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

Диагностика установки

Симптом, вероятная причина, точная проверка и безопасное исправление для десяти отказов on-prem установки ActionPulse — от контейнера, который не поднимается, до недействительного сертификата.

Кому
Операторам установки
Проверено
На этой странице

Эта страница для оператора, у которого установка на своих серверах уже запущена или запускается прямо сейчас — и что-то не работает. Каждый случай разобран одинаково: симптом, вероятная причина, конкретная проверка и исправление, которое не портит данные. Если отказ на стороне интеграции — событие не отправляется, приём отвечает 401, 403 или 422 — начните с общего дерева диагностики. Если непонятно, чей это отказ вообще, — пять минут на разделение.

Все команды выполняются из корня распакованного комплекта — каталога, где лежат docker-compose.onprem.yml, .env и preflight.sh. Команд make … в комплекте нет: они работают только из клона репозитория продукта, и ни одна инструкция оператора на них не опирается. В примерах ${PP_DOMAIN} — домен из вашего .env; подставьте своё значение или экспортируйте переменную в оболочку. Общее состояние всегда показывает docker compose -f docker-compose.onprem.yml ps -a: долгоживущие сервисы обязаны быть running, одноразовые задачи — exited (0).

Контейнеры не поднимаются

Сервис не запускается: контейнер сразу выходит или перезапускается по кругу

Вероятная причина. Незаполненный или некорректный .env: обязательные переменные заданы жёстко, и без них конфигурация вообще не разворачивается. Дальше по частоте — ссылка на образ не в виде tag@sha256, отсутствующий или слишком открытый файл секрета (ACL Redis, конфигурация NATS, сертификаты и ключи Elasticsearch и Kibana) и ожидание зависимости, которая не стала здоровой.

Как проверить. Закрытая проверка окружения, затем разворачивание конфигурации, затем журнал конкретного сервиса:

./preflight.sh .env
docker compose -f docker-compose.onprem.yml --env-file .env config --quiet
docker compose -f docker-compose.onprem.yml logs --tail 200 ingest-api

preflight.sh печатает одну строку и называет виновника: групповые права у .env (use chmod 0600), must contain at least 24 characters, must be an exact tag@sha256 reference, must contain exactly one … entry, must use an authenticated URL without whitespace. Успешный проход заканчивается строкой preflight: environment, exact image refs and external secret files passed.

Безопасное исправление. Исправьте .env (обычный файл, режим 0600), досоздайте недостающие файлы секретов и поднимите только затронутый сервис: docker compose -f docker-compose.onprem.yml --env-file .env up -d ingest-api. Данные ни один из этих шагов не касается.

Если не помогло. Сверьте перечень обязательных переменных и файлов с конфигурацией и порядком запуска в развёртывании на Docker Compose. Если сервис ждёт зависимость, поднимите хранилища отдельно и дождитесь их готовности.

Сервис поднялся, но не отвечает на проверку работоспособности

Вероятная причина. Либо приложение действительно не готово — query-api отдаёт 503, когда не может опросить PostgreSQL, ingest-api — когда потерял соединение с NATS, — либо снаружи не проходит край: не запущен Caddy, домен не разрешается, закрыты порты 80 и 443.

Как проверить. Сравните ответ изнутри контейнера и снаружи:

docker compose -f docker-compose.onprem.yml exec -T query-api wget -q -O- http://localhost:8080/healthz
docker compose -f docker-compose.onprem.yml exec -T ingest-api wget -q -O- http://localhost:8081/healthz
curl -sS -o /dev/null -w '%{http_code}\n' "https://${PP_DOMAIN}/healthz"

Успех — {"status":"ok"}. Отказы приходят в обычном конверте: {"error":{"code":"internal","message":"postgres unavailable","details":{}}} у query-api и nats disconnected у ingest-api.

Безопасное исправление. Изнутри ok, снаружи нет — дело в крае: смотрите logs --tail 200 caddy, проверьте, что домен указывает на этот хост, и перезапустите только прокси: docker compose -f docker-compose.onprem.yml --env-file .env restart caddy. Сертификаты лежат в отдельном томе и перезапуск переживают. Изнутри тоже отказ — идите в раздел про названное хранилище.

Если не помогло. Других маршрутов готовности нет: /readyz и /livez не существует. Полный перечень штатных проверок — в проверках работоспособности.

База данных, аналитическое хранилище и очередь

Не подключается база данных

Вероятная причина. Контейнер PostgreSQL не стал здоровым; либо пароль в .env изменили после того, как том с данными уже создан — пароль применяется только при первичной инициализации кластера, а проверка работоспособности его не сверяет, поэтому контейнер остаётся здоровым, а сервисы падают на аутентификации; либо в подключении сервиса указана роль, которой в базе ещё нет, потому что roles.sql не применён.

Как проверить. В журнале сервиса ищите префикс pgpool: и сообщение об отказе аутентификации, затем сверьте состояние самой базы и список ролей — последняя команда не печатает ни одного пароля:

docker compose -f docker-compose.onprem.yml logs --tail 100 postgres
docker compose -f docker-compose.onprem.yml exec -T postgres pg_isready -U pp -d actionpulse
docker compose -f docker-compose.onprem.yml exec -T postgres \
  sh -ec 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U pp -d actionpulse -Atc "SELECT rolname FROM pg_roles ORDER BY 1"'

Безопасное исправление. Если пароль разошёлся с кластером — верните в .env значение, с которым кластер был инициализирован, и перезапустите сервисы. Если оно утрачено, смените пароль роли в самой базе и синхронно обновите .env и все подключения. Если не хватает ролей — примените postgres/roles.sql от имени суперпользователя: пароли передаются переменными psql, поэтому сам файл никогда не содержит учётных данных.

Если не помогло. На чистой базе roles.sql применяется дважды — до первой миграции и ещё раз после неё; DDL выполняет только pp_migrator, и он никогда не обслуживает трафик.

Не подключается аналитическое хранилище

Вероятная причина. Пароли в подключениях к ClickHouse разошлись с паролями пользователей; пароль внутри подключения не экранирован по правилам URL; адрес сервиса не попадает в сеть, разрешённую пользователю ClickHouse; либо контейнер не стал здоровым и до аутентификации дело не доходит.

Как проверить. Повторите проверку, которой пользуется compose, и посмотрите, какие пользователи существуют. В журналах приложения ищите AUTHENTICATION_FAILED — разошлись пароль или адрес, — и ACCESS_DENIED (код 497) — не хватает права у роли.

docker compose -f docker-compose.onprem.yml logs --tail 200 clickhouse
docker compose -f docker-compose.onprem.yml exec -T clickhouse \
  sh -ec 'clickhouse-client --user default --password "$CH_DEFAULT_PASSWORD" --query "SELECT 1"'
docker compose -f docker-compose.onprem.yml exec -T clickhouse \
  sh -ec 'clickhouse-client --user default --password "$CH_DEFAULT_PASSWORD" --query "SELECT name FROM system.users ORDER BY name"'

Безопасное исправление. Приведите подключения в соответствие с паролями, экранируйте специальные символы в паролях внутри подключений, проверьте, что подсеть служебной сети совпадает с той, что задана пользователям ClickHouse, и перезапустите ClickHouse: пароли пользователей читаются из окружения при старте контейнера. Данные при этом не затрагиваются.

Если не помогло. Совместимый пользователь default разрешён только с петлевого адреса, поэтому «починить» доступ, подставив его в рабочее подключение, не получится: роль каждого подключения фиксирована, и закрытая проверка отвергает подмену. Если в списке нет пользователя SQL-консоли, значит не применены миграции ClickHouse — см. ниже.

Не подключается очередь

Вероятная причина. Конфигурация NATS не содержит того, чего от неё ждёт установка: JetStream с каталогом хранения /data и служебный listener 8222, по которому идёт проверка работоспособности. Либо подключение задано без учётных данных или с чужим паролем. Либо одноразовая задача создания потоков не завершилась успешно — тогда ingest-api, ch-writer и worker намеренно не стартуют: в on-prem они не создают потоки сами, а только проверяют их наличие.

Как проверить. Точная строка отказа в журнале приложения: ingest-api: required pre-provisioned stream EVENTS. У ch-writer и worker формулировка та же, но со своими именами потоков.

docker compose -f docker-compose.onprem.yml logs --tail 100 nats
docker compose -f docker-compose.onprem.yml logs query-api-nats-bootstrap
docker compose -f docker-compose.onprem.yml logs --tail 100 ingest-api
./preflight.sh --validate-acl-contracts secrets/redis-users.acl secrets/nats-server.conf

Безопасное исправление. Одноразовая задача идемпотентна — она создаёт или обновляет ровно три потока, EVENTS, REPLAY и IMPORT_FENCES, — поэтому её можно запустить повторно:

docker compose -f docker-compose.onprem.yml --env-file .env up query-api-nats-bootstrap
docker compose -f docker-compose.onprem.yml --env-file .env up -d ingest-api ch-writer worker

Если не проходит проверка работоспособности самой очереди, добавьте в её конфигурацию отсутствующий блок JetStream или служебный listener и перезапустите только nats.

Если не помогло. Проверьте, что учётная запись одноразового создания потоков не подставлена ни в один долгоживущий сервис: закрытая проверка запрещает это специально, а перепутанные подключения дают отказ на операциях, которых у роли нет.

Миграции

Миграции не проходят

Вероятная причина. Одноразовая задача миграции завершилась с ошибкой, а query-api ждёт именно её успешного завершения и поэтому не стартует. Причины: подключение не под ролью миграций, не применён или применён не в том порядке roles.sql, ClickHouse ещё не готов, либо релиз требует отдельных предварительных шагов.

Как проверить. Различайте два префикса в журнале задачи: pgpool: goose up: — упала миграция PostgreSQL; chmigrate: <файл>: — упала миграция ClickHouse, и в сообщении названа конкретная. Журнал применённых миграций ClickHouse хранится в PostgreSQL, в таблице ch_schema_migrations.

docker compose -f docker-compose.onprem.yml logs query-api-migrate
docker compose -f docker-compose.onprem.yml exec -T postgres \
  sh -ec 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U pp -d actionpulse -Atc "SELECT max(version_id) FROM goose_db_version WHERE is_applied"'
docker compose -f docker-compose.onprem.yml exec -T postgres \
  sh -ec 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U pp -d actionpulse -Atc "SELECT max(version), count(*) FROM ch_schema_migrations"'

Безопасное исправление. Миграции идемпотентны и сериализованы advisory-блокировкой, поэтому повторный запуск после устранения причины безопасен:

docker compose -f docker-compose.onprem.yml --env-file .env up query-api-migrate

Если причина в правах — примените postgres/roles.sql и повторите. Порядок на чистой базе: roles.sql, миграции, roles.sql ещё раз. При обновлении: сначала миграции, потом повторное применение roles.sql, и только затем выкладка ingest-api со своим подключением.

Если не помогло. Не обходите отказ ручным поэтапным запуском сервисов: у обновления есть обязательный порядок и обязательные предварительные шаги — см. обновление. Заодно убедитесь, что на существующем томе не запущена версия хранилища из будущего: для NATS и ClickHouse это запрещено без проверенной цепочки промежуточных шагов.

События не доходят до отчётов

Приём отвечает 202, но событий в отчётах нет

Вероятная причина. Приём подтверждает запись в очередь, а в аналитическое хранилище её переносит отдельный сервис ch-writer. Значит, либо он не работает, либо не успевает, либо ClickHouse отвергает вставки, либо сообщения ушли в dead-letter. Отдельный случай: ch-writer намеренно не стартует, если в хранилище остались устаревшие записи об отклонённых исторических импортах — тогда он прямо пишет об этом в журнал.

Как проверить. Журнал сервиса и три метрики. Метрики слушают только внутри контейнера и наружу не публикуются:

docker compose -f docker-compose.onprem.yml logs --tail 200 ch-writer
docker compose -f docker-compose.onprem.yml exec -T ch-writer \
  sh -ec 'wget -q -O- http://localhost:9090/metrics' \
  | grep -E 'pp_writer_lag|pp_writer_insert_errors_total|pp_writer_dlq_total'

pp_writer_lag — сколько сообщений ждёт обработки; pp_writer_insert_errors_total растёт на каждой неудачной попытке вставки; pp_writer_dlq_total — когда сообщение ушло в dead-letter. Те же сигналы приходят алертами ActionPulseEventsConsumerLagHigh, ActionPulseClickHouseInsertErrors и ActionPulseDLQGrowth — см. мониторинг.

Безопасное исправление. Сначала устраните причину на стороне ClickHouse — место на диске, ошибки вставки, доступность, — затем дайте отставанию слиться само. Масштабировать обработчики до устранения причины не нужно: это только добавит нагрузки хранилищу, которое и так не справляется.

Если не помогло. Рост pp_writer_dlq_total означает, что не записанные сообщения попали в dead-letter внутри потока событий, где они хранятся не дольше 48 часов. Разбор dead-letter выполняет поставщик: обратитесь в поддержку, приложив время, метрики и журнал ch-writer. Не удаляйте сообщения и не подтверждайте их массово — это молча теряет данные.

Кабинет и запросы API

Кабинет открывается, но запросы падают

Вероятная причина. По коду ответа:

Код Причина
401 истёк токен доступа — он живёт 15 минут, обновляется через /api/v1/auth/refresh
403 с license_invalid лицензия отсутствует, недействительна или истекла
404 на /api/v1/billing/… биллинга в on-prem нет вовсе, это ожидаемо
5xx на отчётах недоступно аналитическое хранилище или база
отказ по origin APP_BASE_URL и CORS_ORIGINS не совпадают с адресом, по которому открыт кабинет

Важно: проверка работоспособности query-api опрашивает только PostgreSQL. Недоступный ClickHouse её не окрашивает — /healthz продолжит отвечать ok, пока отчёты падают.

Как проверить. Статус лицензии и журнал сервиса:

curl -fsS "https://${PP_DOMAIN}/api/v1/license" -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
docker compose -f docker-compose.onprem.yml logs --tail 200 query-api

Ответ содержит поля valid, read_only и reason. Значения reason, которые объясняют отказ записи: active — всё в порядке; expired; grace period (read-only) — срок вышел, семь дней работает только чтение; expired beyond grace period — и этот запас исчерпан. Тела отказов буквальные: license invalid or missing: <причина>, license expired; running read-only until renewed, license status is temporarily unavailable.

Безопасное исправление. Для 401 достаточно обновить токен. Для license_invalid — загрузите действующий ключ от имени владельца: замена подхватывается всеми сервисами примерно за секунду, перезапуск не нужен, см. лицензию. Для 5xx — идите в разделы про хранилища выше. Для отказа по origin — приведите публичные адреса в .env к тому виду, по которому кабинет открывается, и перезапустите query-api; адрес приложения обязан остаться на https, иначе слетит защищённый флаг у cookie аутентификации.

Если не помогло. Соберите код ответа, значение заголовка X-Request-ID из ответа и журнал query-api за то же окно: по идентификатору запроса поддержка найдёт запись, не запрашивая у вас тело запроса.

Диск и сертификат

Закончилось место на диске

Вероятная причина. Растут тома данных: аналитическое хранилище (события), база, очередь, журналы Elasticsearch, хранилище метрик Prometheus, рабочий каталог резервного копирования.

Как проверить.

df -h
docker system df -v
docker compose -f docker-compose.onprem.yml exec -T clickhouse df -h /var/lib/clickhouse

Штатные сигналы: ActionPulseDiskSpaceLow — ниже 15 процентов свободного в течение 15 минут; ActionPulseDiskSpaceCritical — ниже 5 процентов в течение пяти минут; ActionPulseDiskTelemetryMissing — метрики файловых систем пропали, и дисковые алерты ослепли. Убедитесь, что алерт смотрит на реальную файловую систему с данными, а не на overlay.

Безопасное исправление. Добавьте ёмкость и примените штатные сроки хранения: для метрик Prometheus, для журналов в Elasticsearch (политику удаления создаёт одноразовая настройка, по умолчанию 30 дней; переменной для этого срока в шаблоне .env нет — задайте её вручную) и для резервных копий. Несущественные исторические импорты и записи сессий останавливайте их собственными органами управления, сохранив надёжность приёма.

Если не помогло. Без исправного экспортёра метрик хоста дисковые сигналы слепы вовсе — починить надо сначала их, иначе следующий раз вы узнаете о переполнении от пользователей.

Сертификат недействителен

Вероятная причина. Домен установки указан как localhost — тогда край выдаёт внутренний самоподписанный сертификат, и предупреждение браузера ожидаемо. Для настоящего домена автоматический выпуск требует, чтобы запись DNS указывала на этот хост, а порты 80 и 443 были доступны извне: без этого выпуск не завершается.

Как проверить. Вывод openssl показывает издателя и срок — внутренний самоподписанный сертификат отличается от настоящего сразу:

docker compose -f docker-compose.onprem.yml logs --tail 200 caddy
openssl s_client -connect "${PP_DOMAIN}:443" -servername "${PP_DOMAIN}" </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -subject -dates

Безопасное исправление. Убедитесь, что домен в .env — настоящее имя, DNS указывает на этот хост, а 80 и 443 открыты снаружи, и перезапустите только край: docker compose -f docker-compose.onprem.yml --env-file .env restart caddy. Выданные сертификаты лежат в отдельном томе и перезапуск переживают — повторный выпуск не потребуется.

Если не помогло. Собственный сертификат в поставляемой конфигурации края не предусмотрен: в ней нет ни директивы для своего сертификата, ни адреса ACME-аккаунта. Если по политике организации нужен свой сертификат или внутренний удостоверяющий центр, согласуйте изменение конфигурации края с поставщиком — не подменяйте её наугад на работающей установке. Учтите и то, что для любого имени, кроме localhost, включается принудительный HTTPS сроком на год: после первого успешного визита браузер уже не пойдёт на http.

Что дальше

Набор для обращения в поддержку — cat VERSION, ps -a, вывод ./preflight.sh .env, журналы затронутых сервисов и метрики pp_*; полный перечень с пояснениями, что из чего берётся, — в пяти минутах на разделение. Оттуда же начинайте, если непонятно, чей это отказ. Если отказ появился сразу после смены версии, идите в обновление: у него есть обязательный порядок, который нельзя обойти. Что проверять на здоровой установке — проверки работоспособности.