Grafana 5.1 provisioning: дашборды в Git, автозагрузка при деплое
Grafana 5.1 умеет загружать дашборды и datasources из файлов при старте - решена проблема потери дашбордов при пересоздании контейнера.
Grafana 5.1 - provisioning дашбордов и datasources через конфигурационные файлы при старте контейнера
Несколько недель назад мы писали про Prometheus Operator и monitoring-as-code в Kubernetes. Там оставалась одна незакрытая деталь: Prometheus складно управляет своими конфигами через CRD, а Grafana по-прежнему хранила дашборды в SQLite внутри контейнера. Grafana 5.1 закрывает этот пробел - теперь есть официальный provisioning API.
Проблема, которую мы хотели решить
С Grafana в Docker у нас был один стойкий раздражитель. Кто-нибудь из команды строит дашборд вручную через UI - красиво, с панелями, thresholds, annotations. Потом в рамках планового обновления или какого-нибудь инцидента контейнер пересоздаётся. Если не был смонтирован persistent volume под /var/lib/grafana - дашборд исчез. Был смонтирован - надо помнить об этом явно и не потерять volume при переезде между нодами.
В Kubernetes это болит особенно заметно: StatefulSet для Grafana ради одного SQLite с дашбордами выглядит как избыточная конструкция. Мы какое-то время экспортировали дашборды вручную через «Export JSON» и коммитили в git - но это ручной процесс, про него забывали, и репозиторий регулярно отставал от реальности.
Provisioning в Grafana 5.1 меняет механику: дашборды и datasources описываются файлами, которые Grafana читает при старте. Файлы живут в git. Контейнер stateless.
Как устроен provisioning
Grafana ищет конфигурационные файлы в директориях, которые указаны в основном grafana.ini:
[paths]
provisioning = /etc/grafana/provisioning
В этой директории два поддиректория: datasources/ и dashboards/. Каждый содержит YAML-файлы с конфигурацией.
Datasource выглядит так:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
Поле editable: false - важное. Если его не выставить, кто-нибудь поправит datasource через UI, а при следующем рестарте provisioning перезапишет изменения файлом. Явный запрет снимает эту неоднозначность.
Для дашбордов нужен двухуровневый конфиг. Сначала dashboards/default.yaml - описывает откуда брать JSON-файлы дашбордов:
apiVersion: 1
providers:
- name: default
orgId: 1
folder: ''
type: file
disableDeletion: false
options:
path: /etc/grafana/dashboards
Потом в /etc/grafana/dashboards/ лежат сами JSON-файлы дашбордов. Их можно экспортировать из Grafana через UI один раз, или написать с нуля - формат документированный.
Как мы это раскатали
На managed-кластерах у нас Grafana работает в Kubernetes как Deployment с одной репликой. Добавили ConfigMap с provisioning-конфигами и volume с JSON-дашбордами, смонтировали в нужные пути. Dockerfile при этом не меняется - Grafana читает всё из примонтированных файлов.
Структура в git-репозитории получилась такая:
monitoring/
grafana/
provisioning/
datasources/
prometheus.yaml
dashboards/
default.yaml
dashboards/
kubernetes-nodes.json
kubernetes-pods.json
postgresql.json
Всё это упаковывается в два ConfigMap: один под provisioning-конфиги, второй под JSON-файлы дашбордов. При деплое - kubectl apply, Grafana поднимается с актуальными дашбордами автоматически.
Что поймали в процессе
Первое - uid в JSON-файлах дашборда. Grafana 5.x добавила UID для дашбордов - уникальный идентификатор, не зависящий от числового id. Если экспортировать дашборд через UI и сохранить JSON с UID, при следующем импорте через provisioning Grafana опознает его как тот же дашборд и обновит, а не создаст дубликат. Без UID при каждом рестарте Grafana создавала новую копию - в итоге накапливались дубли. Лечится добавлением "uid": "some-stable-string" в JSON вручную или через экспорт в версии 5.x.
Второе - disableDeletion: false с осторожностью. С таким флагом Grafana при рестарте удаляет дашборды, которых нет в файлах, но которые кто-то создал через UI. Для нас это желаемое поведение - git является source of truth. Но важно объяснить команде: если создал дашборд в UI и не закоммитил JSON - при следующем деплое он исчезнет.
Третье - переменные в datasource через env. Иногда URL Prometheus отличается между окружениями. Жёстко кодить его в YAML неудобно. Grafana 5.1 поддерживает подстановку переменных окружения в provisioning-файлах через синтаксис ${VAR_NAME}. Для нас это удобно: один и тот же YAML-файл, разные значения URL через переменные окружения в Kubernetes Deployment.
Что пока не закрыто
Provisioning работает на старте контейнера - это одновременно и достоинство, и ограничение. Если нужно добавить новый дашборд без рестарта Grafana, механика не предусмотрена. Grafana следит за директорией с файлами и подхватывает изменения без рестарта только для дашбордов (не для datasources) - но это работает когда файловая система меняется «живьём», что в случае ConfigMap в Kubernetes немного отличается от простого обновления файла на диске. Мы это место пока не проверяли на production и не делаем на него ставку.
Отдельный вопрос - alerting-правила Grafana. Алерты, созданные через UI, никак не участвуют в provisioning-механике 5.1. Они по-прежнему живут в SQLite. Для нашего стека critical alerting идёт через Prometheus Alertmanager, поэтому мы не завязаны на Grafana-алерты - но если у кого-то они настроены, это нужно учитывать.
Итого
Переход занял один рабочий день - большую часть времени съело приведение дашбордов в порядок: добавление UID, удаление дублей, чистка устаревших панелей которые накопились за время ручного управления. Теперь Grafana stateless, дашборды в git, новый контейнер поднимается с нужным состоянием без ручного вмешательства. Именно то, чего хотелось.