diff --git a/.github/workflows/molecule.yml b/.github/workflows/molecule.yml index 97885ee..5fcb13d 100644 --- a/.github/workflows/molecule.yml +++ b/.github/workflows/molecule.yml @@ -82,6 +82,10 @@ jobs: run: ansible-galaxy collection install community.general shell: micromamba-shell {0} + - name: "Install community.grafana collection" + run: ansible-galaxy collection install community.grafana + shell: micromamba-shell {0} + - name: "Run Molecule tests" if: ${{ matrix.role != '__no_role__' }} working-directory: ${{ matrix.role }} diff --git a/README.md b/README.md index cfc2e2d..9c2dc02 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ - [alertmanager](roles/alertmanager/README.md) - [dnsmasq](roles/dnsmasq/README.md) +- [grafana](roles/grafana/README.md) - [mount_device](roles/mount_device/README.md) - [prometheus](roles/prometheus/README.md) - [ufw](roles/ufw/README.md) diff --git a/galaxy.yml b/galaxy.yml index 02c97f7..aa819d3 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: genlab name: common -version: 0.7.0 +version: 0.8.0 readme: README.md authors: - Alexander Gorelyshev (corvus-migratorius@proton.me) diff --git a/roles/grafana/README.md b/roles/grafana/README.md new file mode 100644 index 0000000..a700863 --- /dev/null +++ b/roles/grafana/README.md @@ -0,0 +1,107 @@ +genlab.grafana +========= + +This ansible role installs [Grafana](https://github.com/grafana/grafana) - the open-source platform for monitoring and observability. It can produce charts, graphs, and alerts for the web when connected to supported data sources. + +This role installs and configures Grafana from a binary distribution. It also: + +- Uploads custom dashboards + +- Installs plugins + +- Imports public dashboards and data sources + +- Allows admin password changes + +Supports user creation + Grafana service requires an environment file at startup, where you can set custom paths for logs (`grafana_log_dir`), data (`grafana_data_dir`), and plugins (`grafana_plugins_dir`). This allows flexible control over Grafana's data storage. + + Requirements +------------ + +You need `community.grafana` module. + +Role Variables +-------------- + +``` +grafana_user: "grafana" # user name +grafana_group: "grafana" # group name +grafana_version: 11.5.0 # version +grafana_port: 3000 # port + +# Directory paths +grafana_dashboard_dir: "/tmp/grafana/dashboards" # where to copy dashboards from source +grafana_plugins_dir: "/var/lib/grafana/plugins" # where to store plugins on target +grafana_datasource_dir: "/etc/grafana/provisioning/datasources" # where to store data sources on target +grafana_log_dir: "/var/log/grafana" # where to write logs +grafana_data_dir: "/var/lib/grafana" # where to store Grafana DB + +# Optional configurations +grafana_users: [] # array of user names, passwords, and statuses +grafana_plugins: [] # array of plugins to install +grafana_public_dashboards: [] # array of public dashboards to import + +# Admin credentials +admin_api_username: "secret" # Grafana admin username +admin_api_password: "secret" # Grafana admin password + +# Source paths +dashboard_source_path: "mydir/dashboards" # path to dashboards on source server +datasource_source_path: "mydir/datasources" # path to data sources on source server +``` + +Dependencies +------------ + +None + +Example Playbook +---------------- + +```yaml +--- +- name: Converge + hosts: all + vars: + grafana_users: + - name: "test" + user_login: "test" + user_password: "test" + user_email: "test@mail.ru" + is_admin: false + grafana_plugins: + version: 2.1.8 + - name: aceiot-svg-panel + version: 0.1.5 + grafana_public_dashboards: + - name: Node Full Exporter + id: 1860 + revision: 36 + vars_files: + - secrets/admin_cred.yml + + roles: + - role: genlab.grafana + grafana_version: 11.5.0 + admin_api_username: "{{ grafana.admin_api_username }}" + admin_api_password: "{{ grafana.admin_api_password }}" + users: "{{ grafana_users }}" + plugins: "{{ grafana_plugins }}" + public_dashboards: "{{ grafana_public_dashboards }}" + dashboard_source_path: "molecule/default/dashboards" + datasource_source_path: "molecule/default/datasources" + grafana_log_dir: "/opt/grafana/data" + grafana_data_dir: "/opt/grafana/lib" + +``` + +License +------- + +BSD + +Author Information +------------------ + +corvus-migratorius@proton.me diff --git a/roles/grafana/defaults/main.yml b/roles/grafana/defaults/main.yml new file mode 100644 index 0000000..535854f --- /dev/null +++ b/roles/grafana/defaults/main.yml @@ -0,0 +1,10 @@ +--- +grafana_user: "grafana" +grafana_group: "grafana" +grafana_version: 11.5.0 +grafana_port: 3000 +grafana_dashboard_dir: "/tmp/grafana/dashboards" +grafana_plugins_dir: "/var/lib/grafana/plugins" +grafana_datasource_dir: "/etc/grafana/provisioning/datasources" +grafana_log_dir: "/var/log/grafana" +grafana_data_dir: "/var/lib/grafana" diff --git a/roles/grafana/handlers/main.yml b/roles/grafana/handlers/main.yml new file mode 100644 index 0000000..0b67d92 --- /dev/null +++ b/roles/grafana/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: "(Re)start and enable Grafana" + ansible.builtin.systemd_service: + name: grafana-server + state: restarted + enabled: true + daemon_reload: true diff --git a/roles/grafana/meta/main.yml b/roles/grafana/meta/main.yml new file mode 100644 index 0000000..cec0dd3 --- /dev/null +++ b/roles/grafana/meta/main.yml @@ -0,0 +1,17 @@ +--- +galaxy_info: + role_name: "grafana" + namespace: genlab + author: "Alexander Gorelyshev" + company: "Genlab, LLC" + description: "" + license: "MIT" + min_ansible_version: "2.1" + + platforms: + - name: "Ubuntu" + versions: ["focal", "jammy", "noble"] + + galaxy_tags: [] + +dependencies: [] diff --git a/roles/grafana/molecule/default/converge.yml b/roles/grafana/molecule/default/converge.yml new file mode 100644 index 0000000..a73b0da --- /dev/null +++ b/roles/grafana/molecule/default/converge.yml @@ -0,0 +1,44 @@ +--- +- name: Converge + hosts: all + vars: + grafana_users: + - name: "test" + user_login: "test" + # kics-scan ignore-line + user_password: "test" + user_email: "test@mail.ru" + is_admin: false + - name: "test2" + user_login: "test2" + # kics-scan ignore-line + user_password: "test2" + user_email: "test2@mail.ru" + is_admin: true + grafana_plugins: + - name: grafana-metricsdrilldown-app + version: 1.0.0 + - name: grafana-clock-panel + version: 2.1.8 + - name: aceiot-svg-panel + version: 0.1.5 + grafana_public_dashboards: + - name: Node Full Exporter + id: 1860 + revision: 36 + vars_files: + - secrets/admin_cred.yml + + roles: + # kics-scan ignore-block + - role: genlab.common.grafana + grafana_version: 11.5.0 + admin_api_username: "{{ grafana.admin_api_username }}" + admin_api_password: "{{ grafana.admin_api_password }}" + users: "{{ grafana_users }}" + plugins: "{{ grafana_plugins }}" + public_dashboards: "{{ grafana_public_dashboards }}" + dashboard_source_path: "molecule/default/dashboards" + datasource_source_path: "molecule/default/datasources" + grafana_log_dir: "/opt/grafana/data" + grafana_data_dir: "/opt/grafana/lib" diff --git a/roles/grafana/molecule/default/dashboards/test_dashboard.json b/roles/grafana/molecule/default/dashboards/test_dashboard.json new file mode 100644 index 0000000..595932e --- /dev/null +++ b/roles/grafana/molecule/default/dashboards/test_dashboard.json @@ -0,0 +1,700 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__requires": [ + { + "type": "panel", + "id": "bargauge", + "name": "Bar gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "7.1.5" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 29, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 15, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "valueMode": "color" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", namespace=~\"$namespace\", status=~\"fail|error\" } > 0) by (namespace)", + "instant": true, + "interval": "", + "legendFormat": "{{namespace}}", + "refId": "A" + } + ], + "title": "Failing Policies by Namespace", + "type": "bargauge" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 3 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 15, + "y": 0 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "vertical", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(cluster_policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", status=~\"fail|error\" } > 0) by (status)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{ status }}", + "refId": "A" + } + ], + "title": "Failing ClusterPolicies", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 8 + }, + "hiddenSeries": false, + "id": 11, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "hideEmpty": true, + "hideZero": true, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "10.1.5", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(cluster_policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", status=~\"fail|error\" } > 0) by (policy)", + "interval": "", + "legendFormat": "{{ policy }}", + "refId": "A" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", namespace=~\"$namespace\", status=~\"fail|error\" } > 0) by (policy)", + "interval": "", + "legendFormat": "{{ policy }}", + "refId": "B" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Failing Policies Graph", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 7, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", namespace=~\"$namespace\", status=~\"fail|error\" }) by (namespace,policy,rule,kind,name,status,category,severity,source)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "{{namespace}}: {{ policy }}", + "refId": "A" + } + ], + "title": "Failing PolicyRules", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true + }, + "indexByName": { + "category": 1, + "kind": 4, + "name": 5, + "namespace": 3, + "policy": 6, + "rule": 7, + "severity": 2, + "source": 0, + "status": 8 + }, + "renameByName": { + "namespace": "namespace" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 28 + }, + "id": 9, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(cluster_policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", status=~\"fail|error\" }) by (policy,rule,kind,name,status,category,severity,source)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "{{ kind }}: {{ name }} - {{ policy }}", + "refId": "A" + } + ], + "title": "Failing ClusterPolicyRules", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "__name__": true, + "container": true, + "endpoint": true, + "instance": true, + "job": true, + "namespace": true, + "pod": true, + "report": true, + "service": true + }, + "indexByName": { + "category": 1, + "kind": 3, + "name": 4, + "policy": 5, + "rule": 6, + "severity": 2, + "source": 0, + "status": 7 + }, + "renameByName": {} + } + } + ], + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Policy Reporter" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "prometheus" + }, + "hide": 0, + "includeAll": false, + "label": "Datasource", + "multi": false, + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allValue": ".*", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, policy)", + "hide": 0, + "includeAll": true, + "label": "Policy", + "multi": true, + "name": "policy", + "options": [], + "query": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, policy)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, category)", + "hide": 0, + "includeAll": true, + "label": "Category", + "multi": true, + "name": "category", + "options": [], + "query": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, category)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, severity)", + "hide": 0, + "includeAll": true, + "label": "Severity", + "multi": true, + "name": "severity", + "options": [], + "query": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, severity)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values({__name__= \"policy_report_result\", status=~\"fail|error\"}, namespace)", + "hide": 0, + "includeAll": true, + "label": "Namespace", + "multi": true, + "name": "namespace", + "options": [], + "query": "label_values({__name__= \"policy_report_result\", status=~\"fail|error\"}, namespace)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, kind)", + "hide": 0, + "includeAll": true, + "label": "Kind", + "multi": true, + "name": "kind", + "options": [], + "query": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, kind)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, source)", + "hide": 0, + "includeAll": true, + "label": "Source", + "multi": true, + "name": "source", + "options": [], + "query": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, source)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "PolicyReports", + "uid": "ZkwXrUMnk", + "version": 1, + "gnetId": 13968 +} \ No newline at end of file diff --git a/roles/grafana/molecule/default/dashboards/test_dashboard2.json b/roles/grafana/molecule/default/dashboards/test_dashboard2.json new file mode 100644 index 0000000..a100af0 --- /dev/null +++ b/roles/grafana/molecule/default/dashboards/test_dashboard2.json @@ -0,0 +1,2081 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__requires": [ + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "7.5.6" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart v2", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "A dashboard for the CoreDNS DNS server with updated metrics for version 1.7.0+. Based on the CoreDNS 1.7.0+ dashboard by ejkinger", + "editable": true, + "gnetId": 14981, + "graphTooltip": 0, + "id": null, + "iteration": 1632672605392, + "links": [ + { + "$$hashKey": "object:94", + "icon": "external link", + "tags": [], + "targetBlank": true, + "title": "CoreDNS.io", + "type": "link", + "url": "https://coredns.io" + } + ], + "panels": [ + { + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 44, + "title": "Global stats", + "type": "row" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 39, + "links": [], + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "value" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "text": {} + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_dns_requests_total[5m])) by (instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{instance}}", + "refId": "A", + "step": 60 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Requests (by instance)", + "type": "piechart" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 41, + "panels": [], + "repeat": "instance", + "title": "Health: $instance", + "type": "row" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 10 + }, + "id": 42, + "links": [], + "maxPerRow": 2, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "7.5.6", + "repeat": null, + "repeatDirection": "v", + "targets": [ + { + "exemplar": true, + "expr": "coredns_build_info{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{version}}", + "refId": "A", + "step": 60 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Version", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 6, + "y": 10 + }, + "id": 35, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_forward_healthcheck_broken_total{instance=~\"$instance\"}[5m]))", + "interval": "", + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Upstream Health Check Fails", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 9, + "y": 10 + }, + "id": 36, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_forward_max_concurrent_rejects_total{instance=~\"$instance\"}[5m]))", + "interval": "", + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "title": "Upstream Rejected Queries", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 12, + "y": 10 + }, + "id": 81, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_panics_total{instance=~\"$instance\"}[5m]))", + "interval": "", + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Panics", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 15, + "y": 10 + }, + "id": 92, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_reload_failed_total{instance=~\"$instance\"}[5m]))", + "interval": "", + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Failed Reloads", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 0.03, + "min": 0, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 60 + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 18, + "y": 10 + }, + "id": 119, + "options": { + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(process_cpu_seconds_total{instance=~\"$instance\"}[5m]))", + "interval": "", + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "CPU Time", + "type": "gauge" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "super-light-blue", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 21, + "y": 10 + }, + "id": 134, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "go_memstats_alloc_bytes{instance=~\"$instance\"}", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Memory Usage", + "type": "stat" + }, + { + "collapsed": false, + "datasource": "${DS_PROMETHEUS}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 26, + "panels": [], + "repeat": null, + "title": "Local", + "type": "row" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 31 + }, + "id": 2, + "links": [], + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_dns_requests_total{instance=~\"$instance\"}[5m])) by (server)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{server}}", + "refId": "A", + "step": 60 + }, + { + "exemplar": true, + "expr": "sum(rate(coredns_cache_requests_total{instance=~\"$instance\"}[5m]))", + "hide": false, + "interval": "", + "legendFormat": "cache", + "refId": "B" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Requests (total)", + "type": "timeseries" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 31 + }, + "id": 6, + "links": [], + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_dns_requests_total{instance=~\"$instance\"}[5m])) by (zone)", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{zone}}", + "refId": "A", + "step": 60 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Requests (by zone)", + "type": "timeseries" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 38 + }, + "id": 32, + "links": [], + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "histogram_quantile(0.99, sum(rate(coredns_dns_request_duration_seconds_bucket{instance=~\"$instance\", zone=\".\"}[5m])) by (le))", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "99%", + "refId": "A", + "step": 40 + }, + { + "exemplar": true, + "expr": "histogram_quantile(0.90, sum(rate(coredns_dns_request_duration_seconds_bucket{instance=~\"$instance\", zone=\".\"}[5m])) by (le))", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "90%", + "refId": "B", + "step": 40 + }, + { + "exemplar": true, + "expr": "histogram_quantile(0.50, sum(rate(coredns_dns_request_duration_seconds_bucket{instance=~\"$instance\", zone=\".\"}[5m])) by (le))", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "50%", + "refId": "C", + "step": 40 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Responses (latency, internet zone)", + "type": "timeseries" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 38 + }, + "id": 4, + "links": [], + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_dns_requests_total{instance=~\"$instance\"}[5m])) by (type)", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{type}}", + "refId": "A", + "step": 60 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Requests (by type)", + "type": "timeseries" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 45 + }, + "id": 24, + "links": [], + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_cache_hits_total{instance=~\"$instance\", type=\"success\"}[5m])) / sum(rate(coredns_cache_requests_total{instance=~\"$instance\"}[5m]))", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "hits: success", + "refId": "A", + "step": 40 + }, + { + "exemplar": true, + "expr": "sum(rate(coredns_cache_hits_total{instance=~\"$instance\", type=\"denial\"}[5m])) / sum(rate(coredns_cache_requests_total{instance=~\"$instance\"}[5m]))", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "hits: denial", + "refId": "B", + "step": 40 + }, + { + "exemplar": true, + "expr": "(sum(rate(coredns_cache_requests_total{instance=~\"$instance\"}[5m])) - sum(rate(coredns_cache_hits_total{instance=~\"$instance\", type=\"success\"}[5m]))) / sum(rate(coredns_cache_requests_total{instance=~\"$instance\"}[5m]))", + "hide": false, + "interval": "", + "legendFormat": "misses", + "refId": "C" + }, + { + "exemplar": true, + "expr": "sum(rate(coredns_dnssec_cache_hits_total{instance=~\"$instance\"}[5m])) / sum(rate(coredns_cache_requests_total{instance=~\"$instance\"}[5m]))", + "hide": false, + "interval": "", + "legendFormat": "hits: DNSSEC", + "refId": "D" + }, + { + "exemplar": true, + "expr": "(sum(rate(coredns_cache_requests_total{instance=~\"$instance\"}[5m])) - sum(rate(coredns_dnssec_cache_hits_total{instance=~\"$instance\"}[5m]))) / sum(rate(coredns_cache_requests_total{instance=~\"$instance\"}[5m]))", + "hide": false, + "interval": "", + "legendFormat": "misses: DNSSEC", + "refId": "E" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Cache (hitrate)", + "type": "timeseries" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 45 + }, + "id": 8, + "links": [], + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_dns_do_requests_total{instance=~\"$instance\"}[5m])) by (zone)", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{zone}}", + "refId": "A", + "step": 40 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Requests (DNSSEC by zone)", + "type": "timeseries" + }, + { + "cacheTimeout": null, + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 52 + }, + "id": 14, + "interval": null, + "links": [], + "options": { + "displayLabels": [], + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "values": [ + "value", + "percent" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "text": {} + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_dns_responses_total{instance=~\"$instance\"}[5m])) by (rcode)", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{rcode}}", + "refId": "A", + "step": 40 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Responses (by code)", + "type": "piechart" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 52 + }, + "id": 18, + "links": [], + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "histogram_quantile(0.99, sum(rate(coredns_dns_request_size_bytes_bucket{instance=~\"$instance\", zone=\".\"}[5m])) by (le))", + "interval": "", + "intervalFactor": 2, + "legendFormat": "99%", + "refId": "A", + "step": 40 + }, + { + "exemplar": true, + "expr": "histogram_quantile(0.90, sum(rate(coredns_dns_request_size_bytes_bucket{instance=~\"$instance\", zone=\".\"}[5m])) by (le))", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "90%", + "refId": "B", + "step": 40 + }, + { + "exemplar": true, + "expr": "histogram_quantile(0.50, sum(rate(coredns_dns_request_size_bytes_bucket{instance=~\"$instance\", zone=\".\"}[5m])) by (le))", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "50%", + "metric": "", + "refId": "C", + "step": 40 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Requests (size, internet zone)", + "type": "timeseries" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 52 + }, + "id": 33, + "links": [], + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "histogram_quantile(0.99, sum(rate(coredns_dns_response_size_bytes_bucket{instance=~\"$instance\", zone=\".\"}[5m])) by (le))", + "interval": "", + "intervalFactor": 2, + "legendFormat": "99%", + "refId": "A", + "step": 40 + }, + { + "exemplar": true, + "expr": "histogram_quantile(0.90, sum(rate(coredns_dns_response_size_bytes_bucket{instance=~\"$instance\", zone=\".\"}[5m])) by (le))", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "90%", + "refId": "B", + "step": 40 + }, + { + "exemplar": true, + "expr": "histogram_quantile(0.50, sum(rate(coredns_dns_response_size_bytes_bucket{instance=~\"$instance\", zone=\".\"}[5m])) by (le))", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "50%", + "metric": "", + "refId": "C", + "step": 40 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Responses (size, internet zone)", + "type": "timeseries" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 59 + }, + "id": 22, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(coredns_cache_entries{instance=~\"$instance\"}) by (type)", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{type}}", + "refId": "A", + "step": 40 + }, + { + "exemplar": true, + "expr": "sum(coredns_dnssec_cache_entries{instance=~\"$instance\"})", + "hide": false, + "interval": "", + "legendFormat": "DNSSEC", + "refId": "B" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Cache (size)", + "type": "stat" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 66 + }, + "id": 63, + "panels": [], + "title": "Upstream", + "type": "row" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 67 + }, + "id": 72, + "links": [], + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_forward_requests_total{instance=~\"$instance\"}[5m]))", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "upstream", + "refId": "A", + "step": 60 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Requests (total)", + "type": "timeseries" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 67 + }, + "id": 38, + "links": [], + "maxPerRow": 6, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.6", + "repeat": null, + "repeatDirection": "h", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_forward_conn_cache_hits_total{instance=~\"$instance\"}[5m])) / sum(rate(coredns_forward_requests_total{instance=~\"$instance\"}[5m]))", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "hits", + "refId": "A", + "step": 40 + }, + { + "exemplar": true, + "expr": "(sum(rate(coredns_forward_requests_total{instance=~\"$instance\"}[5m])) - sum(rate(coredns_forward_conn_cache_hits_total{instance=~\"$instance\"}[5m]))) / sum(rate(coredns_forward_requests_total{instance=~\"$instance\"}[5m]))", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "misses", + "refId": "B", + "step": 40 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Cache (hitrate)", + "type": "timeseries" + }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 74 + }, + "id": 37, + "links": [], + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "histogram_quantile(0.99, sum(rate(coredns_forward_request_duration_seconds_bucket{instance=~\"$instance\"}[5m])) by (le))", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "99%", + "refId": "A", + "step": 40 + }, + { + "exemplar": true, + "expr": "histogram_quantile(0.90, sum(rate(coredns_forward_request_duration_seconds_bucket{instance=~\"$instance\"}[5m])) by (le))", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "90%", + "refId": "B", + "step": 40 + }, + { + "exemplar": true, + "expr": "histogram_quantile(0.50, sum(rate(coredns_forward_request_duration_seconds_bucket{instance=~\"$instance\"}[5m])) by (le))", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "50%", + "refId": "C", + "step": 40 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Responses (latency)", + "type": "timeseries" + }, + { + "cacheTimeout": null, + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 74 + }, + "id": 105, + "interval": null, + "links": [], + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "values": [ + "value" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "text": {} + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_forward_requests_total{instance=~\"$instance\"}[5m])) by (to)", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{to}}", + "refId": "A", + "step": 40 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Requests (by upstream)", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "Time", + "1.0.0.1:853", + "1.1.1.1:853", + "8.8.4.4:853", + "8.8.8.8:853" + ] + } + } + } + ], + "type": "piechart" + }, + { + "cacheTimeout": null, + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 74 + }, + "id": 53, + "interval": null, + "links": [], + "options": { + "displayLabels": [], + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "values": [ + "value", + "percent" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "text": {} + }, + "pluginVersion": "7.5.6", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(coredns_forward_responses_total{instance=~\"$instance\"}[5m])) by (rcode)", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{rcode}}", + "refId": "A", + "step": 40 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Responses (by code)", + "type": "piechart" + } + ], + "refresh": "5s", + "schemaVersion": 27, + "style": "dark", + "tags": [ + "dns", + "coredns" + ], + "templating": { + "list": [ + { + "allValue": ".*", + "current": {}, + "datasource": "${DS_PROMETHEUS}", + "definition": "label_values(up{job=\"coredns\"}, instance)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Instance", + "multi": true, + "name": "instance", + "options": [], + "query": { + "query": "label_values(up{job=\"coredns\"}, instance)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 3, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "CoreDNS", + "uid": "wY4blRMGz", + "version": 134 +} \ No newline at end of file diff --git a/roles/grafana/molecule/default/datasources/test_datasource_loki.yaml b/roles/grafana/molecule/default/datasources/test_datasource_loki.yaml new file mode 100644 index 0000000..9c3ede0 --- /dev/null +++ b/roles/grafana/molecule/default/datasources/test_datasource_loki.yaml @@ -0,0 +1,16 @@ +apiVersion: 1 + +datasources: + - name: Loki + type: loki + access: proxy + basicAuth: false + basicAuthPassword: false + url: http://localhost:3100 + jsonData: + maxLines: 1000 + httpHeaderName1: "Authorization" + secureJsonData: + httpHeaderValue1: "Bearer your_token_here" + version: 1 + editable: false diff --git a/roles/grafana/molecule/default/datasources/test_datasource_prometheus.yaml b/roles/grafana/molecule/default/datasources/test_datasource_prometheus.yaml new file mode 100644 index 0000000..56ec0c6 --- /dev/null +++ b/roles/grafana/molecule/default/datasources/test_datasource_prometheus.yaml @@ -0,0 +1,50 @@ +# config file version +apiVersion: 1 + +# list of datasources that should be deleted from the database +# deleteDatasources: +# - name: Prometheus +# orgId: 1 + +# list of datasources to insert/update depending +# whats available in the database +datasources: + # name of the datasource. Required + - name: Prometheus + # datasource type. Required + type: prometheus + # access mode. direct or proxy. Required + access: proxy + # org id. will default to orgId 1 if not specified + orgId: 1 + # url + url: http://localhost:9090 + # database password, if used + password: + # database user, if used + user: + # database name, if used + database: + # enable/disable basic auth + basicAuth: false + # basic auth username, if used + basicAuthUser: + # basic auth password, if used + basicAuthPassword: + # enable/disable with credentials headers + withCredentials: + # mark as default datasource. Max one per org + isDefault: true + # fields that will be converted to json and stored in json_data + jsonData: + graphiteVersion: "1.1" + tlsAuth: false + tlsAuthWithCACert: false + # json object of data that will be encrypted. + secureJsonData: + tlsCACert: "..." + tlsClientCert: "..." + tlsClientKey: "..." + version: 1 + # allow users to edit datasources from the UI. + editable: true diff --git a/roles/grafana/molecule/default/molecule.yml b/roles/grafana/molecule/default/molecule.yml new file mode 100644 index 0000000..1e73d79 --- /dev/null +++ b/roles/grafana/molecule/default/molecule.yml @@ -0,0 +1,29 @@ +--- +dependency: + name: galaxy + +driver: + name: docker + +platforms: + - name: ubuntu + image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest + pre_build_image: true + command: ${MOLECULE_DOCKER_COMMAND:-""} + published_ports: + - 127.0.0.1:3001:3000 + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true + +provisioner: + name: ansible + +verifier: + name: ansible + +lint: | + set -e + yamllint . + ansible-lint . diff --git a/roles/grafana/molecule/default/secrets/admin_cred.yml b/roles/grafana/molecule/default/secrets/admin_cred.yml new file mode 100644 index 0000000..7d48371 --- /dev/null +++ b/roles/grafana/molecule/default/secrets/admin_cred.yml @@ -0,0 +1,3 @@ +grafana: + admin_api_username: "admin" + admin_api_password: "changme23" diff --git a/roles/grafana/molecule/default/verify.yml b/roles/grafana/molecule/default/verify.yml new file mode 100644 index 0000000..fd0a907 --- /dev/null +++ b/roles/grafana/molecule/default/verify.yml @@ -0,0 +1,41 @@ +--- +- name: Verify + hosts: all + gather_facts: false + any_errors_fatal: true + vars_files: + - admin_cred.yml + + tasks: + + - name: "Include default vars" + ansible.builtin.include_vars: + dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/" + extensions: ['yml'] + + - name: "Check if Grafana is installed" + changed_when: false + ansible.builtin.command: "grafana-server -v" + register: grafana_installed_version + + - name: "Check Grafana version" + ansible.builtin.assert: + that: "grafana_installed_version.stdout is regex('{{ grafana_version }}')" + success_msg: "grafana version {{ grafana_version }} is installed and working" + fail_msg: "grafana version {{ grafana_version }} is not installed or not working correctly" + + # kics-scan ignore-block + - name: "Check if Grafana login page is reachable" + ansible.builtin.uri: + url: "http://localhost:{{ grafana_port }}/api/health" + return_content: true + status_code: 200 + method: GET + body_format: json + register: grafana_health + + - name: "Debug Grafana health status" + ansible.builtin.assert: + that: grafana_health.json.database == "ok" + success_msg: "Grafana is healthy" + fail_msg: "Grafana is not healthy" diff --git a/roles/grafana/tasks/dashboards.yml b/roles/grafana/tasks/dashboards.yml new file mode 100644 index 0000000..79c0604 --- /dev/null +++ b/roles/grafana/tasks/dashboards.yml @@ -0,0 +1,40 @@ +--- +- name: "Create dashboard directory" + ansible.builtin.file: + path: "{{ grafana_dashboard_dir }}" + state: directory + owner: "{{ grafana_user }}" + group: "{{ grafana_group }}" + mode: "0755" + +- name: "Copy dashboard files from source to target" + ansible.builtin.copy: + src: "{{ item }}" + dest: "{{ grafana_dashboard_dir }}/{{ item | basename }}" + mode: "0644" + owner: "{{ grafana_user }}" + group: "{{ grafana_group }}" + with_fileglob: + - "{{ dashboard_source_path }}/*.json" + +- name: "Import Grafana dashboards to Grafana" + community.grafana.grafana_dashboard: + grafana_url: "http://127.0.0.1:{{ grafana_port }}" + url_username: "{{ admin_api_username }}" + url_password: "{{ admin_api_password }}" + state: present + commit_message: Updated by ansible + overwrite: false + path: "{{ grafana_dashboard_dir }}/{{ item | basename }}" + with_fileglob: + - "{{ dashboard_source_path }}/*.json" + +- name: "Run | reload Grafana provisioned dashboard configurations" + ansible.builtin.uri: + # kics-scan ignore-line + url: "http://127.0.0.1:{{ grafana_port }}/api/admin/provisioning/dashboards/reload" + method: POST + force_basic_auth: true + user: "{{ admin_api_username }}" + password: "{{ admin_api_password }}" + status_code: 200 diff --git a/roles/grafana/tasks/datasources.yml b/roles/grafana/tasks/datasources.yml new file mode 100644 index 0000000..94025c0 --- /dev/null +++ b/roles/grafana/tasks/datasources.yml @@ -0,0 +1,28 @@ +--- +- name: "Create datasource directory" + ansible.builtin.file: + path: "{{ grafana_datasource_dir }}" + state: directory + owner: "{{ grafana_user }}" + group: "{{ grafana_group }}" + mode: "0755" + +- name: "Configure | provision datasources for Grafana" + ansible.builtin.copy: + src: "{{ datasource_source_path }}/{{ item | basename }}" + dest: "{{ grafana_datasource_dir }}/{{ item | basename }}" + owner: "{{ grafana_user }}" + group: "{{ grafana_group }}" + mode: "0660" + with_fileglob: + "{{ datasource_source_path }}/*.y*ml" + +- name: "Run | reload Grafana datasource provisioned configurations" + ansible.builtin.uri: + # kics-scan ignore-line + url: "http://127.0.0.1:{{ grafana_port }}/api/admin/provisioning/datasources/reload" + method: POST + force_basic_auth: true + user: "{{ admin_api_username }}" + password: "{{ admin_api_password }}" + status_code: 200 diff --git a/roles/grafana/tasks/import_pub_dashboard.yml b/roles/grafana/tasks/import_pub_dashboard.yml new file mode 100644 index 0000000..13aead0 --- /dev/null +++ b/roles/grafana/tasks/import_pub_dashboard.yml @@ -0,0 +1,11 @@ +--- +- name: "Import public dashboard - '{{ public_dashboard.name }}''" + community.grafana.grafana_dashboard: + grafana_url: "http://127.0.0.1:{{ grafana_port }}" + state: "{{ public_dashboard.state | default('present') }}" + overwrite: false + dashboard_id: "{{ public_dashboard.id }}" + dashboard_revision: "{{ public_dashboard.revision }}" + commit_message: "Add public dashboard '{{ public_dashboard.name }}''" + url_username: "{{ admin_api_username }}" + url_password: "{{ admin_api_password }}" diff --git a/roles/grafana/tasks/install.yml b/roles/grafana/tasks/install.yml new file mode 100644 index 0000000..0f7d33d --- /dev/null +++ b/roles/grafana/tasks/install.yml @@ -0,0 +1,78 @@ +--- +- name: "Create Grafana system group" + ansible.builtin.group: + name: grafana + system: true + state: present + +- name: "Create Grafana system user" + ansible.builtin.user: + name: grafana + group: grafana + system: true + shell: "/sbin/nologin" + create_home: false + state: present + +- name: "Install Grafana deb package" + block: + - name: "Check Grafana version" + changed_when: false + ansible.builtin.command: + cmd: "grafana-server --version" + register: grafana_ver + + - name: "Assert version correctness" + ansible.builtin.assert: + that: "grafana_ver.stdout is regex('{{ grafana_version }}')" + success_msg: "grafana version {{ grafana_version }} is installed and working" + fail_msg: "grafana version {{ grafana_version }} is not installed or not working correctly" + + rescue: + - name: "Ensure that directories exist" + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ grafana_user }}" + group: "{{ grafana_group }}" + mode: '0775' + with_items: + - "{{ grafana_log_dir }}" + - "{{ grafana_data_dir }}" + - "{{ grafana_plugins_dir }}" + + - name: "Download Grafana DEB package" + ansible.builtin.get_url: + url: "https://dl.grafana.com/oss/release/grafana_{{ grafana_version }}_amd64.deb" + dest: "/tmp/grafana-{{ grafana_version }}_amd64.deb" + owner: "{{ grafana_user }}" + group: "{{ grafana_group }}" + mode: "0644" + register: grafana_package_tmp + + - name: "Install DEB package" + notify: "(Re)start and enable Grafana" + ansible.builtin.apt: + deb: "{{ grafana_package_tmp.dest }}" + state: present + update_cache: true + + - name: "Cleanup downloaded file" + ansible.builtin.file: + path: "/tmp/grafana-{{ grafana_version }}_amd64.deb" + state: absent + + - name: "Create env file for systemd service unit" + notify: "(Re)start and enable Grafana" + ansible.builtin.template: + src: grafana-server.env.j2 + dest: "{{ item }}" + owner: "{{ grafana_user }}" + group: "{{ grafana_group }}" + mode: "0660" + with_items: + - "/etc/default/grafana-server" + - "/etc/default/grafana" + +- name: "Flush handlers" + ansible.builtin.meta: "flush_handlers" diff --git a/roles/grafana/tasks/main.yml b/roles/grafana/tasks/main.yml new file mode 100644 index 0000000..75cba65 --- /dev/null +++ b/roles/grafana/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: "Include grafana installation tasks" + ansible.builtin.include_tasks: install.yml + +- name: "Wait for the Grafana server to become available" + ansible.builtin.wait_for: + host: "127.0.0.1" + port: "{{ grafana_port }}" + state: started + delay: 10 + +- name: "Include user creation tasks" + when: users is defined + loop: "{{ users }}" + loop_control: + loop_var: user + ansible.builtin.include_tasks: user.yml + +- name: "Configure custom dashboards" + when: dashboard_source_path is defined + ansible.builtin.include_tasks: dashboards.yml + +- name: "Configure public dashboards" + when: public_dashboards is defined + loop: "{{ public_dashboards }}" + loop_control: + loop_var: public_dashboard + ansible.builtin.include_tasks: import_pub_dashboard.yml + +- name: "Configure plugins" + when: plugins is defined + loop: "{{ plugins }}" + loop_control: + loop_var: plugin + ansible.builtin.include_tasks: plugins.yml + +- name: "Configure datasources" + when: datasource_source_path is defined + ansible.builtin.include_tasks: datasources.yml diff --git a/roles/grafana/tasks/plugins.yml b/roles/grafana/tasks/plugins.yml new file mode 100644 index 0000000..96af00c --- /dev/null +++ b/roles/grafana/tasks/plugins.yml @@ -0,0 +1,16 @@ +--- +- name: "Create plugin directory" + ansible.builtin.file: + path: "{{ grafana_plugins_dir }}" + state: directory + owner: "{{ grafana_user }}" + group: "{{ grafana_group }}" + mode: "0755" + +- name: "Install Grafana plugins" + community.grafana.grafana_plugin: + name: "{{ plugin.name }}" + version: "{{ plugin.version }}" + grafana_plugins_dir: "{{ grafana_plugins_dir }}" + state: "{{ plugin.state | default('present') }}" + notify: "(Re)start and enable Grafana" diff --git a/roles/grafana/tasks/user.yml b/roles/grafana/tasks/user.yml new file mode 100644 index 0000000..1998fc9 --- /dev/null +++ b/roles/grafana/tasks/user.yml @@ -0,0 +1,19 @@ +--- + +- name: "Reset default admin password" + ansible.builtin.command: > + grafana-cli admin reset-admin-password "{{ admin_api_password }}" + no_log: true + changed_when: false + +- name: "Create | update a Grafana user" + community.grafana.grafana_user: + url: "http://127.0.0.1:{{ grafana_port }}" + url_username: "{{ admin_api_username }}" + url_password: "{{ admin_api_password }}" + name: "{{ user.name }}" + email: "{{ user.user_email }}" + login: "{{ user.user_login }}" + password: "{{ user.user_password }}" + is_admin: "{{ user.is_admin | default(false) }}" + state: present diff --git a/roles/grafana/templates/grafana-server.env.j2 b/roles/grafana/templates/grafana-server.env.j2 new file mode 100644 index 0000000..0abd646 --- /dev/null +++ b/roles/grafana/templates/grafana-server.env.j2 @@ -0,0 +1,24 @@ +GRAFANA_USER=grafana + +GRAFANA_GROUP=grafana + +GRAFANA_HOME=/usr/share/grafana + +LOG_DIR={{ grafana_log_dir }} + +DATA_DIR={{ grafana_data_dir }} + +MAX_OPEN_FILES=10000 + +CONF_DIR=/etc/grafana + +CONF_FILE=/etc/grafana/grafana.ini + +RESTART_ON_UPGRADE=true + +PLUGINS_DIR={{ grafana_plugins_dir }} + +PROVISIONING_CFG_DIR=/etc/grafana/provisioning + +# Only used on systemd systems +PID_FILE_DIR=/run/grafana \ No newline at end of file diff --git a/roles/grafana/vars/main.yml b/roles/grafana/vars/main.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/roles/grafana/vars/main.yml @@ -0,0 +1 @@ +---