Решение проблем
Практический разбор типовых ситуаций «что-то работает не так»: симптом → причина → проверка и решение. Раздел про эксплуатацию уже запущенной инсталляции; первое спотыкание при подъёме demo-сетапа — в Quick Start, а install-time проблемы онбординга (порты, CA, первый агент) — в таблицах Установка из пакетов → Troubleshooting и Онбординг душ → Troubleshooting.
Перед разбором конкретной ситуации полезно помнить два инварианта, объясняющих большинство «странностей»:
- Авторитетное состояние — в PostgreSQL, presence и координация — в Redis. Статус агента в реестре (
connected/disconnected) — это снимок, который Keeper сводит с фактом фоном; снимок отстаёт от факта на секунды. Минимальный обязательный контур кластера — PostgreSQL + Redis; Vault обязателен для PKI/секретов. - Идентичность доказывается, а не сообщается. Подключение агента к Keeper-у держится на общем PKI-корне (mTLS), а доступ оператора — на короткоживущем токене. Большинство отказов «не доверяет» и «доступ запрещён» — про эти два механизма.
Сводная таблица
Заголовок раздела «Сводная таблица»| Симптом | Вероятная причина | Куда смотреть |
|---|---|---|
Агент не переходит в connected | сервис не запущен / mTLS не сходится / порт недостижим / SID ≠ FQDN / снимок отстаёт | § Агент не connected |
| Агент не доверяет серверу Keeper-а | серверный cert Keeper-а и SoulSeed выпущены из разных PKI-корней | § TLS/mTLS не сходится |
Инкарнация в error_locked | прогон упал, состояние заблокировано для защиты | § Инкарнация в error_locked |
| Bootstrap-токен истёк или потерян | TTL вышел / токен показывается один раз | § Bootstrap-токен истёк или потерян |
| Прогон падает на рендере шаблона | необъявленная переменная / неверный маркер интерполяции / strict-mode | § Ошибки рендера и шаблонов |
| Операции падают на резолве секрета | Vault недостижим или путь/поле в KV неверны | § Vault недостижим и PKI |
Доступ запрещён (401/403) | токен истёк / у оператора нет permission | § Доступ запрещён |
Агент не переходит в connected
Заголовок раздела «Агент не переходит в connected»Симптом. Хост зарегистрирован, soul init прошёл, но в реестре (GET /v1/souls) он не в статусе connected — остаётся pending или уходит в disconnected.
Идите по списку проверок сверху вниз — он отсортирован от самого частого к редкому.
1. Запущен ли рабочий демон
Заголовок раздела «1. Запущен ли рабочий демон»soul init только получает идентичность (SoulSeed) и завершается — он не держит соединение. Постоянный стрим к Keeper-у держит отдельная команда soul run (в проде — systemd-сервис).
- Проверьте, что сервис агента активен (
systemctl status soul) и в логах нет ошибок старта. - Если
initпрошёл, а демон не запущен — хост так и останется неconnected.
2. Подождать пару секунд — статус это снимок
Заголовок раздела «2. Подождать пару секунд — статус это снимок»Статус в реестре (souls.status) — ленивый снимок для Operator API, а не источник истины о presence. Авторитет «агент онлайн» — живой стрим (lease в Redis); снимок Keeper сводит с фактом фоном и догоняет его с лагом в несколько секунд.
- Сразу после
soul runопроситеGET /v1/souls/{sid}ещё раз через несколько секунд — переходpending → connectedне мгновенный. - Это by-design, не зависание. Если через минуту статуса всё ещё нет — переходите к следующим проверкам.
3. mTLS: общий PKI-корень
Заголовок раздела «3. mTLS: общий PKI-корень»Рабочий стрим (soul run) идёт по mTLS на EventStream-порт. Стрим установится только если серверный сертификат Keeper-а и SoulSeed агента выпущены из одного PKI-корня — иначе агент не доверяет серверу (и наоборот). Подробный разбор — § TLS/mTLS не сходится.
4. Сетевая достижимость EventStream-порта
Заголовок раздела «4. Сетевая достижимость EventStream-порта»Агент сам устанавливает исходящее соединение к Keeper-у (входящих портов на управляемом хосте нет). Стрим не поднимется, если порт EventStream недостижим:
- С хоста агента должен быть доступен
endpoints[].hostнаendpoints[].event_stream_port(mTLS-фазаsoul run) — отдельный порт от bootstrap. - Проверьте firewall/NAT между хостом и Keeper-ом (или его балансировщиком) на этом порту.
host/event_stream_portвsoul.ymlдолжны указывать на реальный listener Keeper-а — раскладка блокаkeeper.endpointsописана в Конфигурация → soul.yml.
5. SID = FQDN совпадает
Заголовок раздела «5. SID = FQDN совпадает»SID агента — это FQDN хоста, и он зашит в SoulSeed-сертификат. Если SID, под которым хост зарегистрирован, не совпадает с FQDN из сертификата — Keeper не сопоставит стрим с записью реестра.
- SID в
soul.ymlи SID при регистрации (POST /v1/souls) должны совпадать с реальным FQDN. - PKI-роль выпускает SoulSeed только на имена из своего allowed-домена — используйте FQDN из этого домена, а не короткое hostname.
TLS/mTLS не сходится
Заголовок раздела «TLS/mTLS не сходится»Симптом. Агент не подключается, в логах — ошибка верификации сертификата («агент не доверяет серверу», certificate validation failed, unknown authority).
Причина. Для mTLS обе стороны должны доверять одному удостоверяющему центру. Если серверный сертификат Keeper-а подписан другим корнем, чем SoulSeed-сертификаты хостов, — взаимное доверие ломается и стрим не устанавливается.
Проверка и решение.
- Серверный сертификат Keeper-а и сертификаты агентов должны происходить из одного PKI-корня (PKI Vault). Нельзя взять серверный cert Keeper-а «откуда попало» (например, публичный ACME-сертификат для веб-домена) — он должен быть из того же корня, что и SoulSeed хостов.
- На хосте агента доверенный CA задаётся явно (поле CA в блоке
keeper.tlsвsoul.yml) и должен указывать на корень того же PKI. - FQDN Keeper-а из
endpoints[].hostдолжен входить в SAN серверного сертификата — иначе верификация имени не пройдёт даже при верном корне.
Полная модель транспорта и PKI-корня — Безопасность → Транспорт; как этот корень выпускается и где живёт приватный ключ CA — Безопасность → Секреты.
Инкарнация в error_locked
Заголовок раздела «Инкарнация в error_locked»Симптом. GET /v1/incarnations/{name} показывает status: error_locked. Новый прогон на этой инкарнации не запускается.
Что это значит. Прогон сценария завершился ошибкой, и изменения не закоммичены в состояние инкарнации. Состояние блокируется намеренно: это защита от того, чтобы продолжить работу с частично применённым или неизвестным состоянием. БД хранит прежнее (консистентное) состояние, а инкарнация ждёт вмешательства оператора.
Как посмотреть причину.
- Откройте отчёт о прогоне и историю —
GET /v1/incarnations/{name}/history(snapshots состояния по каждому изменению). Там видно, на каком шаге и почему прогон упал. - Логи Keeper-а и агента по этому прогону связаны общими идентификаторами (id прогона, trace-id) — этого достаточно, чтобы дойти до конкретного упавшего шага.
Как разблокировать.
- Устраните причину провала — это может быть ошибка в сценарии/Destiny, недоступный хост, неверный input, упавший внешний шаг.
- Запустите прогон повторно после фикса. Снятие блокировки и повторный запуск — операции Operator API над инкарнацией.
- Состояние перейдёт в
readyтолько при успешном завершении прогона; до этого момента прежнее состояние остаётся нетронутым.
Bootstrap-токен истёк или потерян
Заголовок раздела «Bootstrap-токен истёк или потерян»Симптом. soul init падает с bootstrap token invalid / expired / used, либо токен потерян и его негде взять.
Причина. Bootstrap-токен:
- одноразовый — сжигается при первом успешном онбординге;
- короткоживущий — TTL по умолчанию 24 часа;
- показывается один раз — Keeper хранит только его хеш, plain-токен после выписки восстановить нельзя.
Решение — перевыпустить токен. Для уже зарегистрированного хоста, который ещё не прошёл онбординг:
curl -s -X POST https://keeper.example.com/v1/souls/host-01.example.com/issue-token \ -H "Authorization: Bearer $TOKEN"- Новый plain-токен возвращается в ответе один раз — сразу сохраните его для доставки на хост.
- Действует инвариант «максимум один активный токен на хост»: если предыдущий токен ещё активен, запрос без флага вернёт
409(защита от плодящихся валидных токенов). Чтобы заменить активный токен новым, используйте флагforce(CLI/обёртка) илиforce: true(MCP) — старый помечается использованным, выпускается новый. - Доставьте новый токен на хост любым удобным способом и повторите
soul init.
Если хост уже успешно онбордился (SoulSeed на диске есть), а токен «потерян» — он больше не нужен: онбординг пройден, идентичность лежит на хосте. Перевыпуск нужен только для повторного онбординга.
Полный поток онбординга и доставки токена — Онбординг душ. Для агента в push-режиме (по SSH) bootstrap-токен не используется вовсе.
Ошибки рендера и шаблонов
Заголовок раздела «Ошибки рендера и шаблонов»Симптом. Прогон падает на фазе рендера — до применения к хосту. Ошибка указывает на необъявленную переменную, неизвестную функцию или ошибку синтаксиса выражения.
Причина. В Soul Stack два движка шаблонизации с строгой границей по файлам, и оба работают в strict-mode (никакого молчаливого «пустой строки вместо неизвестного» — отсутствующее имя это ошибка, а не пустое значение):
- CEL — все выражения в YAML. Top-level expression-ключи (
when:,where:,changed_when:,failed_when:,until:) — вся строка целиком есть CEL-выражение, без обёртки. Интерполяция внутри строк (params:,vars:, литералы) — через маркер${ … }. - Go text/template — рендер файлов-шаблонов
templates/<path>.tmpl. Запускается единственным шагомcore.file.rendered, тоже в strict-mode.
Типичные ошибки и решение.
| Что в ошибке | Причина | Решение |
|---|---|---|
undeclared reference / неизвестное имя | переменной нет в контексте (опечатка, не объявлена в input:/vars:, не тот аксессор) | объявите вход в input: Destiny или значение в vars:; сверьте имя; факты хоста доступны как soulprint.self.<path> (голая soulprint.<path> — ошибка) |
${...} попало в файл как есть | маркер интерполяции ${ … } работает в YAML-выражениях, а не внутри .tmpl-файлов | в .tmpl используйте синтаксис Go text/template ({{ .var }}), а не ${ … } |
| строка не распозналась как выражение | перепутаны контексты: в expression-ключе обёрнули в ${ }, или в строковом контексте забыли маркер | в when:/where:/… пишите голый CEL без обёртки; в строках — только через ${ … } |
| неизвестная функция в шаблоне файла | функция вне разрешённого набора (исключены читающие FS/сеть/окружение, выполняющие команды, генерирующие крипто) | используйте функцию из разрешённого набора либо вынесите вычисление в CEL-фазу |
Ловите ошибки офлайн — soul-lint до применения. Линтер soul-lint проверяет артефакты (Destiny/Scenario/шаблоны) офлайн, без доступа к Keeper-у и хостам — это правильное место поймать ошибку рендера до прогона. Запускайте его локально и в CI как обязательный шаг. Подкоманды, exit-коды и пример CI-шага — Компоненты → soul-lint. Сам синтаксис Destiny и рендер файлов — DSL → Destiny.
Vault недостижим и PKI
Заголовок раздела «Vault недостижим и PKI»Симптом. Старт нового Keeper-инстанса падает, либо прогоны начинают валиться на фазе рендера с ошибкой чтения секрета; онбординг новых агентов не проходит.
Причина. Vault обслуживает две критичные функции: секреты (DSN PostgreSQL, ключ подписи операторских токенов, значения для vault(...) в выражениях и vault:-ref в конфиге) и PKI (выпуск SoulSeed-сертификатов, общий корень доверия для mTLS).
Минимальный обязательный контур кластера — PostgreSQL + Redis; Vault обязателен именно для PKI и секретов. Без Vault страдают операции, которым нужны секрет или новый сертификат.
Что продолжает работать при недоступном Vault:
- уже установленные стримы агентов (соединение поднято, зарезолвленные секреты закэшированы);
- фоновая чистка и координация Keeper-а;
- операции Operator API, не требующие секретов из Vault.
Что падает:
- старт нового Keeper-инстанса — он резолвит DSN PostgreSQL из Vault на старте (fail-fast);
- любой прогон с секретом — шаг, тянущий
vault(...)в CEL илиvault:-ref в шаблоне, не отрендерится; - онбординг нового агента — PKI недоступен, SoulSeed не выпустить.
Проверка и решение.
- Проверьте доступность Vault и его статус (unsealed). Симптом «cannot read postgres dsn» на старте Keeper-а — почти всегда про Vault: либо он недоступен, либо нужный KV-путь/поле отсутствуют.
- Сверьте
vault:-ref в конфиге Keeper-а: путь и имя поля в KV должны существовать. - После восстановления Vault существующие сессии переживают сбой — Keeper переустановит токен; при быстром возврате Vault операторы могут не заметить перебоя.
- Как Vault интегрирован, как резолвятся секреты и где живёт корень PKI — Безопасность → Секреты.
Доступ запрещён
Заголовок раздела «Доступ запрещён»Симптом. Вызов Operator API возвращает 401 или 403.
Причина и решение. Это два разных случая:
401— токен не принят. Операторский токен (JWT) короткоживущий, и немедленного отзыва по содержимому токена нет: истёкший токен просто перестаёт приниматься (это и есть основная защита — отозванный оператор перестаёт действовать с истечением TTL). Решение — получить свежий токен. Перевыпуск операторского токена — операция Operator API; разбор реестра операторов и ролей — Управление операторами.403— токена приняли, но прав нет. У оператора нет нужного permission для операции (модель default-deny). Проверьте роль оператора и её permissions. Грамматика прав и scope — Операторы → Роли и доступ.
См. также
Заголовок раздела «См. также»- Эксплуатация → Обзор — мониторинг, масштабирование, обновления, бэкап; краткая таблица triage в конце.
- Установка из пакетов → Troubleshooting — install-time проблемы (порты, CA, первый запуск).
- Онбординг душ — полный поток регистрации и подключения агентов.
- Безопасность → Транспорт — mTLS, общий PKI-корень, направление соединения.
- Конфигурация → soul.yml и keeper.yml — параметры, подключение, hot-reload.
- Компоненты → soul-lint — офлайн-проверка артефактов до применения.