Развёртывание в Kubernetes через Helm
Чарт ActionPulse для Kubernetes — состав, обязательные значения без секретов, установка и обновление одной командой, ingress и TLS на стороне оператора, масштабирование.
На этой странице
Чарт лежит в поставляемом комплекте по пути helm/actionpulse и разворачивает
только сервисы приложения. PostgreSQL, ClickHouse, Redis, NATS и объектное
хранилище вы подключаете снаружи — как внешние сервисы или как отдельные чарты.
Это записано в описании самого чарта, а не выведено из умолчаний.
Страница описывает установку и обновление релиза. Подготовка к переходу между версиями stateful-хранилищ, резервные копии до обновления и порядок откатов описаны отдельно в Обновлении.
Что чарт разворачивает и чего в нём нет
Метаданные чарта: apiVersion: v2, имя actionpulse, тип application,
аннотация actionpulse.io/images-lock. Версия чарта и appVersion совпадают с
версией релиза — сборка комплекта переписывает оба поля в Chart.yaml, поэтому
чарт из комплекта всегда описывает ровно тот релиз, с которым приехал.
Чарт создаёт:
| Объект | Что это |
|---|---|
четыре Deployment |
actionpulse-ingest-api, actionpulse-query-api, actionpulse-ch-writer, actionpulse-worker |
Job <release>-migrate |
хук pre-install,pre-upgrade с миграциями схемы |
PersistentVolumeClaim |
actionpulse-import-uploads для файлов исторического импорта |
| внутренний стек наблюдаемости | Prometheus, Alertmanager, Grafana, node-exporter, экспортёры PostgreSQL, Redis и NATS, Elasticsearch, Kibana, Filebeat как DaemonSet, Jaeger, шесть Service типа ClusterIP, шесть NetworkPolicy и один bootstrap-Job — при observability.enabled |
| раннер резервного копирования | PersistentVolumeClaim и один Deployment с планировщиком, изолированной проверкой восстановления и sidecar-экспортёром — при observability.backup.enabled |
Чего в чарте нет — проверено по шаблонам, а не предположено:
- Ни одного
Serviceи ни одногоIngressдля сервисов приложения.Serviceесть только у стека наблюдаемости. ЗначитService,IngressилиGatewayи завершение TLS дляquery-apiиingest-apiсоздаёте вы. - Никаких баз данных, очередей и кэшей. Ни PostgreSQL, ни ClickHouse, ни Redis, ни NATS чарт не разворачивает и не настраивает.
- Никаких учётных данных. Чарт не создаёт
Secretи не генерирует ключи; он только читает значения из уже существующегоSecret. - Никаких сертификатов. CA, сертификаты и приватные ключи для внутреннего
Elasticsearch вы кладёте в отдельный
Secretсами. - Никакой лицензии. Ключ лицензии загружается в работающую установку через API после первого входа, а не через значения чарта.
Что подготовить до установки
- Внешние PostgreSQL, ClickHouse, Redis и NATS. Роли с минимальными правами
создаются файлом
postgres/roles.sqlиз комплекта, пользователи Redis и учётные записи NATS — по шаблонамredis-users.acl.exampleиnats-server.conf.exampleоттуда же. Secretс учётными данными — по умолчаниюactionpulse-secrets.Secretдля наблюдаемости — по умолчаниюactionpulse-observability-secrets— иSecretс TLS для Elasticsearch — по умолчаниюactionpulse-elasticsearch-tlsс ключамиca.crt,tls.crt,tls.key. Три учётные записи Elasticsearch должны уже существовать с минимальными правами.- Класс хранения с режимом
ReadWriteManyдля тома импорта:query-apiиworkerмонтируют его одновременно. Либо передайте свой готовый claim черезimportUploads.existingClaim. - Если включаете резервное копирование — бакет S3, ключи доступа в том же
Secretи named collectionactionpulse_backup_s3в вашем ClickHouse.
Обязательные значения
Значения ниже не имеют работающего значения по умолчанию: при пустом значении
Helm падает до создания любых объектов и печатает имя значения. Это сделано
осознанно — иначе установка молча выдала бы cookie аутентификации без флага
Secure или запустила бы сервисы под общей учётной записью.
| Значение | Назначение | Если не задать |
|---|---|---|
public.appBaseUrl |
origin кабинета; управляет флагом Secure у cookie |
public.appBaseUrl is required |
public.publicIngestUrl |
origin приёма событий | public.publicIngestUrl is required |
public.adminAppBaseUrl |
задаётся явно, чтобы среда не унаследовала небезопасное значение по умолчанию | public.adminAppBaseUrl is required |
postgres.dsnSecretKeys.<сервис> |
имя ключа Secret с полным DSN для каждого из четырёх сервисов |
postgres.dsnSecretKeys.<сервис> is required |
clickhouse.migrateDsn |
DSN миграций; обязан начинаться с clickhouse://pp_migrator:$(CH_MIGRATE_PASSWORD)@ |
отказ с требованием использовать pp_migrator и подстановку из Secret |
clickhouse.dsnSecretKeys.read, .write, .console |
имена ключей Secret с DSN чтения, записи и SQL-консоли; имя обязательно всегда, а сам ключ консоли в Secret необязателен |
clickhouse.dsnSecretKeys.read is required |
clickhouse.passwordSecretKeys.migrate и .console |
имена ключей Secret с паролями миграций и SQL-консоли |
clickhouse.passwordSecretKeys.migrate is required |
mail.from |
адрес отправителя, когда задан mail.host |
mail.from is required when mail.host is set |
Значения public.* должны быть только origin — без пути, запроса и фрагмента.
При включённом контроле digest’ов (image.requireDigests: true, именно так
задано в релизном профиле) дополнительно обязательны:
image.digests.<сервис>для всех четырёх сервисов — формат строгоsha256:и 64 шестнадцатеричных символа;nats.urlSecretKeys.<сервис>иredis.addrSecretKeys.<сервис>для всех четырёх сервисов; для миграционной задачи они намеренно пустые — она не ходит ни в NATS, ни в Redis.
При включённом резервном копировании обязательны
postgres.backupDsnSecretKey, postgres.verifyAdminDsnSecretKey,
postgres.verifyRestoreDsnSecretKey, clickhouse.passwordSecretKeys.backup,
observability.backup.digest под контролем digest’ов и оба ключа подписи
манифеста. Отдельно проверяется, что clickhouse.backup.user — ровно
pp_backup, что адреса ClickHouse не содержат встроенных учётных данных, что
observability.backup.allowUnsignedManifest остался false и что
clickHouseHashMode равен full или metadata.
Установка и обновление
Установка и обновление — одна и та же команда. Выполняйте её из каталога helm/
внутри распакованного комплекта:
helm upgrade --install actionpulse ./actionpulse \
--atomic --wait --wait-for-jobs --timeout 20m \
-f ./actionpulse/values-release.yaml \
-f ./actionpulse/values-release-images.yaml
values-release.yaml — релизный профиль: включает контроль digest’ов и
требует отдельный ключ Secret на каждый сервис.
values-release-images.yaml подставляет digest’ы образов этого релиза. Свои
переопределения добавляйте следующим -f после них.
Таймаут обязан превышать migration.activeDeadlineSeconds — по умолчанию 900
секунд, поэтому 20m подходит. Миграции выполняются хуком
pre-install,pre-upgrade с весом -10, до изменения любого обслуживающего
Deployment. Helm ждёт завершения задачи: неудачная миграция роняет установку
или обновление и оставляет работающий релиз без изменений. Задача использует тот
же тег и digest query-api, что и релиз, и это единственная нагрузка, получающая
CH_MIGRATE_DSN, CH_MIGRATE_PASSWORD и CH_CONSOLE_PASSWORD.
Успешные задачи удаляются сразу, неудачные остаются для чтения журналов до
истечения migration.ttlSecondsAfterFinished (по умолчанию 3600 секунд) или до
следующей попытки хука:
kubectl logs job/actionpulse-migrate
kubectl describe job/actionpulse-migrate
Оба выполнителя миграций идемпотентны и защищены advisory-локами, поэтому после устранения причины повторяйте ту же операцию Helm. Хук блокирует развёртывание, но не может атомарно откатить изменения, уже зафиксированные в двух базах: после диагностики идут вперёд либо восстанавливают снимок, сделанный до обновления, в новый том.
Как задать секреты
Чарт читает значения из Secret, имя которого задаёт existingSecret (по
умолчанию actionpulse-secrets). Секреты создавайте своими средствами —
kubectl create secret, внешний менеджер секретов, оператор секретов. В
values.yaml секретов быть не должно, и в git они попадать не должны.
Значения генерируйте свои, независимые для каждого ключа.
Ключи, которые ожидает чарт: JWT_SECRET, MFA_ENCRYPTION_KEY,
IMPORT_ENC_KEY, IMPORT_HMAC_CURRENT_VERSION, IMPORT_HMAC_CURRENT_SECRET,
IMPORT_HMAC_PREVIOUS_KEYS, IMPORT_PRIVACY_HMAC_CURRENT_VERSION,
IMPORT_PRIVACY_HMAC_CURRENT_SECRET, IMPORT_PRIVACY_HMAC_PREVIOUS_KEYS,
IMPORT_FENCE_HMAC_CURRENT_VERSION, IMPORT_FENCE_HMAC_CURRENT_SECRET,
IMPORT_FENCE_HMAC_PREVIOUS_KEYS, PG_DSN, CH_MIGRATE_PASSWORD,
CH_CONSOLE_PASSWORD, CH_READ_DSN, CH_WRITE_DSN, CH_BACKUP_PASSWORD,
NATS_URL, REDIS_ADDR, RESTORE_REDIS_ADDR, RESTORE_REDIS_PASSWORD,
LICENSE_PUBKEY, ALERT_WEBHOOK_ENC_KEY, BACKUP_MANIFEST_SIGNING_KEY,
BACKUP_MANIFEST_PUBLIC_KEYS, PG_DSN_BACKUP, PG_DSN_RESTORE_ADMIN,
PG_DSN_RESTORE, GRAFANA_ADMIN_PASSWORD. Необязательные: SMTP_PASS (нужен
при заданном mail.user), EMAIL_OUTBOX_ENC_KEY (нужен при
mail.outbox.dedicatedKey), CH_CONSOLE_DSN (без него SQL-консоль отключена) и
пара BOOTSTRAP_EMAIL с BOOTSTRAP_PASSWORD — они нужны только до создания
первого владельца, после чего оба ключа из Secret удаляют.
Релизный профиль ждёт отдельный полный DSN или URL на каждый сервис:
PG_DSN_QUERY_API, PG_DSN_INGEST_API, PG_DSN_CH_WRITER, PG_DSN_WORKER,
PG_DSN_MIGRATOR, NATS_URL_<СЕРВИС>, REDIS_ADDR_<СЕРВИС>, а также
PG_DSN_MONITOR и REDIS_ADDR_MONITOR для экспортёров. Так компрометация
сервиса приёма событий, смотрящего в интернет, не даёт доступ к роли мигратора
или воркера.
Ingress, TLS и доступ к внутренним интерфейсам
Чарт не создаёт Service и Ingress для приложения, поэтому опубликовать его
нужно самостоятельно:
query-apiслушает портqueryApi.port, по умолчанию 8080;ingest-apiслушаетingestApi.port, по умолчанию 8081;- маршрутизация повторяет контракт обратного прокси:
/v1/*— вingest-api,/api/*и остальное — вquery-api; - TLS завершается на вашем ingress, а
public.appBaseUrlобязан бытьhttps; - задайте
queryApi.trustedProxyCidrsиingestApi.trustedProxyCidrsравными подсетям вашего ingress. Пустое значение означает, что заголовки с адресом клиента игнорируются и используется адрес соединения.
Внутренний стек наблюдаемости публиковать не нужно и незачем: все его Service
— ClusterIP, Ingress для них нет, а при пустом
observability.networkPolicy.uiIngress сетевые политики запрещают доступ к
интерфейсам по умолчанию. Grafana открывают проброшенным портом:
kubectl port-forward svc/grafana 3000:3000
Метрики приложения собираются не через Service, а по аннотациям подов —
prometheus.io/scrape, порт 9090, путь /metrics, — поэтому сбор
работает и без публикации.
Проверки готовности и как посмотреть результат
ingest-api и query-api получают readinessProbe — httpGet на
/healthz, initialDelaySeconds: 5, periodSeconds: 10. Проба
готовности здесь единственная: liveness-пробы в чарте нет, отдельных маршрутов
/readyz или /livez у сервисов не существует.
Поды запускаются ужесточённо: runAsNonRoot с UID и GID 10001, файловая система
только для чтения, все capabilities сброшены, повышение привилегий запрещено,
профиль seccomp RuntimeDefault, токен сервисного аккаунта не монтируется.
Посмотреть, что получилось:
helm status actionpulse
helm get values actionpulse
kubectl get deploy,pod,pvc -l app=actionpulse
kubectl logs deploy/actionpulse-query-api
Затем проверьте установку так же, как в любом другом варианте развёртывания:
ответ на /healthz через ваш ingress, состояние лицензии через
GET /api/v1/license, тестовое событие и ограниченный отчёт по нему. Подробнее —
в проверках состояния.
Масштабирование
Число реплик задаётся значениями ingestApi.replicas (по умолчанию 2),
queryApi.replicas (2), chWriter.replicas (1) и worker.replicas (1).
Ресурсы общие для всех четырёх сервисов: запрос 250m CPU и 256 МиБ, предел 1 CPU
и 512 МиБ.
ingest-api и ch-writer в документации продукта описаны как сервисы без
состояния, масштабируемые горизонтально. Для worker такого утверждения нет —
оставляйте одну реплику, если поставщик не подтвердил иное для вашего релиза.
Помните про том импорта: query-api и worker монтируют его одновременно,
поэтому при любом числе реплик query-api claim обязан поддерживать
ReadWriteMany. Размер по умолчанию — 10 ГиБ.
Отключить стек целиком можно через observability.enabled: false, но тогда
метрики, алерты, сбор логов и трассировку вы обеспечиваете сами: bootstrap-задача
Elasticsearch, которая создаёт роль отправителя логов, его учётную запись, пароль
kibana_system и политику хранения логов, — часть этого же стека, и без неё
отправка логов будет отвергаться по аутентификации.