К содержанию
ActionPulse

Развёртывание в Kubernetes через Helm

Чарт ActionPulse для Kubernetes — состав, обязательные значения без секретов, установка и обновление одной командой, ingress и TLS на стороне оператора, масштабирование.

Кому
Инженерам с кластером Kubernetes
Проверено
На этой странице

Чарт лежит в поставляемом комплекте по пути 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 после первого входа, а не через значения чарта.

Что подготовить до установки

  1. Внешние PostgreSQL, ClickHouse, Redis и NATS. Роли с минимальными правами создаются файлом postgres/roles.sql из комплекта, пользователи Redis и учётные записи NATS — по шаблонам redis-users.acl.example и nats-server.conf.example оттуда же.
  2. Secret с учётными данными — по умолчанию actionpulse-secrets.
  3. Secret для наблюдаемости — по умолчанию actionpulse-observability-secrets — и Secret с TLS для Elasticsearch — по умолчанию actionpulse-elasticsearch-tls с ключами ca.crt, tls.crt, tls.key. Три учётные записи Elasticsearch должны уже существовать с минимальными правами.
  4. Класс хранения с режимом ReadWriteMany для тома импорта: query-api и worker монтируют его одновременно. Либо передайте свой готовый claim через importUploads.existingClaim.
  5. Если включаете резервное копирование — бакет S3, ключи доступа в том же Secret и named collection actionpulse_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 получают readinessProbehttpGet на /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 и политику хранения логов, — часть этого же стека, и без неё отправка логов будет отвергаться по аутентификации.