Установка в Kubernetes#
Helm-чарт IncidentRelay публикуется как OCI-артефакт в GHCR и также хранится в репозитории в каталоге helm/incidentrelay. Он развёртывает веб-приложение и фоновые воркеры, формирует конфигурацию приложения в Secret и подключает пробы /healthz и /readyz к Kubernetes.
Требования#
Kubernetes 1.23+
Helm 3.8+ или Helm 4
StorageClass, если используется стандартная конфигурация SQLite
Что развёртывает чарт#
Deployment <release>-web приложение Gunicorn + Flask
Deployment <release>-scheduler напоминания, эскалации, периодические задания
Deployment <release>-telegram воркер обратных вызовов Telegram (необязательно)
Deployment <release>-slack воркер Slack Socket Mode (необязательно)
Service <release> ClusterIP на порту 8080
Secret <release>-config сформированный incidentrelay.conf
PersistentVolumeClaim <release>-data /var/lib/incidentrelay
ServiceAccount и Ingress, если он включён
Все компоненты используют один образ и выбираются переменной INCIDENTRELAY_SERVICE — точно так же, как при установке через Docker Compose.
Быстрый старт#
helm install incidentrelay \
oci://ghcr.io/roxy-wi/incidentrelay-charts/incidentrelay \
--version 2.1.0 \
--set-string config.main.secret_key="$(openssl rand -hex 32)"
Поддержка OCI включена по умолчанию в Helm 3.8 и новее, поэтому helm repo add не требуется. Чарт загружает ghcr.io/roxy-wi/incidentrelay, а tag image по умолчанию берётся из appVersion чарта. Чтобы явно зафиксировать image:
helm upgrade --install incidentrelay \
oci://ghcr.io/roxy-wi/incidentrelay-charts/incidentrelay \
--version 2.1.0 \
--set image.repository=ghcr.io/roxy-wi/incidentrelay \
--set image.tag=2.1 \
--set-string config.main.secret_key="$(openssl rand -hex 32)"
Установка из исходного кода#
Для разработки чарта или проверки ещё не выпущенных изменений установите встроенный чарт напрямую:
helm upgrade --install incidentrelay ./helm/incidentrelay \
--set-string config.main.secret_key="$(openssl rand -hex 32)"
Следите за развёртыванием:
kubectl get pods -l app.kubernetes.io/instance=incidentrelay -w
Конфигурация#
IncidentRelay читает все настройки из одного INI-файла, смонтированного по пути /etc/incidentrelay/incidentrelay.conf. Чарт формирует этот файл из объекта config в values.yaml: ключи верхнего уровня становятся разделами INI, а вложенные ключи — параметрами.
config:
main:
secret_key: ""
auth:
api_auth_required: true
rbac_enforced: true
jwt_secret: ""
server:
host: 0.0.0.0
port: 8080
public_base_url: https://incidentrelay.example.com
превращается в:
[main]
secret_key =
[auth]
api_auth_required = true
rbac_enforced = true
jwt_secret = <общий secret, если значение оставлено пустым в values.yaml>
[server]
host = 0.0.0.0
port = 8080
public_base_url = https://incidentrelay.example.com
Таким способом можно задать всё, что допустимо в incidentrelay.conf. Список доступных параметров см. в разделе Конфигурация.
Укажите в public_base_url адрес, по которому пользователи действительно открывают приложение. Он используется в создаваемых ссылках и обратных вызовах.
При конфигурации, создаваемой самим чартом, config.main.secret_key обязателен. В IncidentRelay 2.0 он используется как общий fallback для main.secret_encryption_key, auth.jwt_secret, mattermost.action_secret и voice.callback_secret, если соответствующие значения оставлены пустыми. Это нужно, чтобы все pod'ы использовали стабильные общие ключи, особенно при PostgreSQL, когда /var/lib/incidentrelay не является общим томом. При необходимости каждый из этих секретов можно задать отдельным случайным значением.
Собственный Secret#
Сформированный файл содержит учётные данные, поэтому чарт хранит его в Secret. Если вы хотите управлять Secret самостоятельно, создайте его, поместив полную конфигурацию в ключ incidentrelay.conf, и укажите его в чарте:
kubectl create secret generic incidentrelay-config \
--from-file=incidentrelay.conf=./incidentrelay.conf
existingConfigSecret: incidentrelay-config
Когда задан existingConfigSecret, объект config игнорируется, а чарт не создаёт собственный Secret.
Чарт добавляет к pod'ам аннотацию checksum/config, чтобы при изменении конфигурации они перезапускались автоматически. При использовании existingConfigSecret чарт не видит его содержимое, поэтому аннотация не добавляется — после изменения Secret перезапустите pod'ы самостоятельно.
Секреты из существующих Secret и файлов#
Чтобы не хранить учётные данные в values.yaml, возьмите любой параметр из Secret, ConfigMap или файла через configFrom. Ключи имеют вид <секция>.<параметр>, а в каждой записи задаётся ровно один из secretKeyRef, configMapKeyRef или file:
configFrom:
main.secret_key:
secretKeyRef:
name: incidentrelay-secrets
key: secret-key
database.password:
secretKeyRef:
name: incidentrelay-db
key: password
smtp.password:
file: /mnt/secrets-store/smtp-password
Чарт передаёт их всем компонентам как переменные окружения INCIDENTRELAY__<SECTION>__<OPTION>, которые имеют приоритет над incidentrelay.conf (см. Переопределение через переменные окружения). Если main.secret_key задан через configFrom, указывать его в config больше не нужно, а пустые общие ключи (main.secret_encryption_key, auth.jwt_secret, mattermost.action_secret, voice.callback_secret) берут его значение — в том числе с existingConfigSecret, — поэтому во всех pod'ах ключи одинаковые. Не задавайте один и тот же параметр одновременно в configFrom и в extraEnv компонента: в Deployment не может быть одной и той же переменной дважды, поэтому чарт откажется его рендерить.
file работает с любым томом, смонтированным в pod'ы, например с Secrets Store CSI Driver и SecretProviderClass для AWS Systems Manager Parameter Store, Secrets Manager или Vault:
extraVolumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: incidentrelay
extraVolumeMounts:
- name: secrets-store
mountPath: /mnt/secrets-store
readOnly: true
Доступ pod'ов к облачному провайдеру настраивается через serviceAccount.annotations, например с помощью IAM-ролей для service account в EKS.
Pod'ы не перезапускаются при изменении Secret или файла, на которые ссылается configFrom. После смены значения перезапустите их.
База данных#
SQLite (по умолчанию)#
SQLite работает без дополнительной настройки. Все компоненты монтируют один PersistentVolumeClaim в /var/lib/incidentrelay.
persistence:
enabled: true
accessModes:
- ReadWriteOnce
size: 1Gi
storageClass: ""
SQLite поддерживается только при persistence.enabled=true и web.replicaCount=1. Для конфигурации SQLite, создаваемой самим чартом, чарт автоматически добавляет обязательный pod affinity для scheduler/Telegram/Slack, чтобы они запускались на том же узле, что и web pod, и монтировали один ReadWriteOnce claim. SQLite на сетевом ReadWriteMany-хранилище (например NFS) всё равно использовать нельзя. Для многоузловой или горизонтально масштабируемой конфигурации используйте PostgreSQL.
PVC создаётся чартом и поэтому удаляется командой helm uninstall. Чтобы сохранить данные, создайте claim самостоятельно и укажите ссылку на него:
persistence:
existingClaim: incidentrelay-data
PostgreSQL#
Для production-нагрузки подключите чарт к PostgreSQL и отключите постоянное хранилище:
config:
database:
type: postgresql
host: postgres.example.svc
port: 5432
name: incidentrelay
user: incidentrelay
password: <database-password>
persistence:
enabled: false
Чтобы не хранить пароль в values.yaml, задайте database.password через configFrom.
Миграции и масштабирование веб-компонента#
По умолчанию веб-pod запускает миграции в entrypoint перед запуском Gunicorn:
web:
runMigrations: true
replicaCount: 1
Пока этот параметр включён, оставьте replicaCount равным 1: несколько одновременно запускающихся pod'ов будут конкурировать при выполнении миграций. Чтобы запустить более одной реплики веб-компонента, отключите этот параметр и выполняйте миграции отдельно:
kubectl exec deploy/incidentrelay-web -- python manage.py migrate
web:
runMigrations: false
replicaCount: 3
strategy:
type: RollingUpdate
RollingUpdate подходит только для PostgreSQL. При использовании общего тома SQLite оставьте стандартную стратегию Recreate: она не позволяет старому и новому pod'ам одновременно записывать один файл базы данных во время развёртывания.
Пробы состояния#
Веб-deployment использует следующие эндпоинты проб без аутентификации:
/healthz liveness возвращает 200, пока процесс обслуживает запросы; не обращается к базе данных
/readyz readiness возвращает 200, только если база данных доступна и все миграции применены
Startup-проба даёт первому запуску до пяти минут — этого достаточно для выполнения миграций на новой базе данных.
Метрики#
Веб-компонент умеет отдавать метрики Prometheus по адресу /metrics (см. Секция metrics). Включите эндпоинт в конфигурации и закройте его токеном из Secret:
kubectl create secret generic incidentrelay-metrics \
--namespace incidentrelay \
--from-literal=token="$(openssl rand -hex 32)"
config:
metrics:
enabled: true
configFrom:
metrics.auth_token:
secretKeyRef:
name: incidentrelay-metrics
key: token
Включайте метрики через config или configFrom, а не только через web.extraEnv: эту настройку читают и планировщик, и воркеры чатов, а планировщик записывает heartbeat, только пока метрики включены.
Если используется Prometheus Operator, чарт может создать ServiceMonitor, который собирает метрики с веб-Service. Токен он берёт из того же Secret, из которого его берёт веб-pod: через secretKeyRef в configFrom или в web.extraEnv:
serviceMonitor:
enabled: true
interval: 30s
labels:
release: kube-prometheus-stack # лейблы, по которым ваш Prometheus выбирает ServiceMonitor
Если чарт не может определить, в каком Secret хранится токен, например с existingConfigSecret, токеном из файла или заданным открытым текстом, укажите ServiceMonitor ключ Secret, в котором он хранится:
serviceMonitor:
enabled: true
bearerTokenSecret:
name: incidentrelay-metrics
key: token
Кроме того, Prometheus должен искать ServiceMonitor в namespace, куда установлен IncidentRelay: лейблы выше проверяются только его serviceMonitorSelector, а в каких namespace искать, решает serviceMonitorNamespaceSelector.
Чарт не создаст ServiceMonitor, который точно не сможет собирать метрики: если метрики выключены в config.metrics.enabled или переменной INCIDENTRELAY__METRICS__ENABLED в web.extraEnv, если задан metrics.auth_token, но чарт не может определить, в каком Secret он хранится, или если bearerTokenSecret указывает не на тот Secret, который использует веб-pod. Проверка не всеобъемлющая: значения из Secret, ConfigMap, файлов и existingConfigSecret чарту не видны, поэтому успешный сбор метрик она не гарантирует.
/metrics отдаёт только веб-pod, поэтому счётчики, которые пишут планировщик и воркеры чатов, не собираются; метрики, которые вычисляются из базы данных, собираются. Каждый pod хранит файлы счётчиков в собственном emptyDir в /var/lib/incidentrelay/metrics, поэтому несколько веб-реплик не делят их между собой.
Доступ#
По умолчанию Service имеет тип ClusterIP. Для быстрой проверки:
kubectl port-forward svc/incidentrelay 8080:8080
http://127.0.0.1:8080/login
Для постоянного доступа включите Ingress:
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt
hosts:
- host: incidentrelay.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- incidentrelay.example.com
secretName: incidentrelay-tls
Значение config.server.public_base_url должно соответствовать хосту Ingress.
Создание первого администратора#
kubectl exec -it deploy/incidentrelay-web -- \
python manage.py create-admin \
--username admin \
--password 'change-me-123' \
--email admin@example.com
Перед использованием в production смените пароль, затем перейдите к разделу Первый вход и настройка.
Воркеры#
Планировщик обрабатывает ротации, напоминания и эскалации. Без него напоминания и эскалации вообще не работают:
scheduler:
enabled: true
Воркер Telegram обрабатывает кнопки обратных вызовов. Если бот не настроен, воркер безвредно простаивает:
telegram:
enabled: true
Воркер Slack поддерживает WebSocket-соединение Socket Mode, через которое работают интерактивные кнопки Acknowledge, Resolve, Shelve 1h и Unshelve. Сами сообщения Slack отправляет веб-компонент, поэтому без этого воркера уведомления продолжат поступать — не будут работать только кнопки:
slack:
enabled: true
Без настроенного канала Slack воркер простаивает. Он самостоятельно получает конфигурацию канала из базы данных, поэтому после добавления канала перезапускать pod не нужно. Для Socket Mode не требуется публичный Request URL, поэтому этот режим обычно выбирают для кластеров, не доступных из интернета. Настройка приложения Slack описана в разделе Slack.
Для каждого компонента доступны обычные параметры размещения и ресурсов:
scheduler:
resources:
requests:
cpu: 100m
memory: 256Mi
nodeSelector: {}
tolerations: []
affinity: {}
extraEnv: []
Логи#
Приложение записывает JSON-логи в файлы в каталоге /var/log/incidentrelay, а не в стандартный вывод, поэтому kubectl logs показывает только сообщение entrypoint. Читайте файлы напрямую:
kubectl exec deploy/incidentrelay-web -- tail -f /var/log/incidentrelay/incidentrelay.log
kubectl exec deploy/incidentrelay-scheduler -- tail -f /var/log/incidentrelay/incidentrelay-scheduler.log
Для логов используется том emptyDir, поэтому после перезапуска pod'а файлы не сохраняются. Структура файлов описана в разделе Логирование.
Пользовательские голосовые провайдеры#
Подключите плагины провайдеров ко всем компонентам с помощью общих дополнительных томов:
extraVolumes:
- name: voice-providers
configMap:
name: incidentrelay-voice-providers
extraVolumeMounts:
- name: voice-providers
mountPath: /usr/local/lib/incidentrelay/voice_providers
readOnly: true
Обновление и удаление#
Обновление с 1.2 на 2.1 или новее#
IncidentRelay 2.1 блокирует private/loopback/link-local/reserved адреса
исходящих HTTP-запросов, если они не разрешены явно. Внутренние OIDC
metadata/JWKS endpoints и исходящие webhook/API-интеграции, работавшие в
1.2, после обновления Helm-чарта могут перестать работать.
Для конфигурации, создаваемой чартом, до обновления добавьте необходимые внутренние CIDR/IP:
config:
security:
outbound_private_network_allowlist: "10.20.0.0/16,192.168.50.10/32"
Если используется existingConfigSecret, измените содержащийся в нём incidentrelay.conf:
[security]
outbound_private_network_allowlist = 10.20.0.0/16,192.168.50.10/32
Разрешите внутренние DNS-имена из кластера и добавьте только те адреса, которые действительно нужны IncidentRelay. Поведение fail-closed для DNS и дополнительные примеры описаны в разделе Политика исходящих HTTP-подключений.
Обновление с 1.x до 2.0#
Чарт 2.0 умеет повторно использовать values от 1.x. При рендеринге он добавляет новые безопасные настройки авторизации и общие JWT/encryption/callback secrets до формирования incidentrelay.conf, поэтому старые values не приводят к генерации разных runtime-ключей в разных pod'ах. config.main.secret_key при этом должен быть задан уникальным случайным значением.
При использовании existingConfigSecret Helm не может нормализовать внешний файл. До обновления на 2.0 убедитесь, что в нём задан корректный main.secret_key, включены нужные настройки [auth] и используется стабильный auth.jwt_secret (либо параметр отсутствует/пустой и приложение использует main.secret_key).
Для SQLite оставьте persistence.enabled=true и web.replicaCount=1. Для PostgreSQL и многоузловой установки можно установить persistence.enabled=false, когда все security secrets уже стабильно заданы в сгенерированной или внешней конфигурации.
helm upgrade incidentrelay \
oci://ghcr.io/roxy-wi/incidentrelay-charts/incidentrelay \
--version 2.1.0 \
--reuse-values
helm uninstall incidentrelay
Команда helm uninstall также удаляет созданный чартом PersistentVolumeClaim, а вместе с ним и базу данных SQLite. Используйте persistence.existingClaim, если данные должны сохраняться после удаления релиза.