Аварийные процедуры¶
Runbook для инцидентов: что продолжает работать при отказе отдельного компонента, как откатить релиз, что делать при потере runner-хоста и при компрометации credentials. Статья для дежурного инженера; каждая процедура начинается с оценки, затем идут шаги и проверка.
Карта зависимостей¶
Что перестаёт работать при отказе компонента:
| Отказал | Что продолжает работать | Что встаёт |
|---|---|---|
iam-service или iam-db |
Уже выданные access token до истечения (до 300 с); проверка подписи по кэшу JWKS; статический ключ памяти | Обмен PAT и client credentials; через ~5 минут — все harness, исполнители, шлюз панели; доставка в память через service account ядра |
control-plane-api / control-plane-db |
Память и IAM сами по себе | Вся координация: задачи, claims, runs, approvals; доставка в память |
memory-service / memory-db |
Координация полностью: claims, runs, approvals, завершение задач | Сборка контекста (отдаётся деградированным), доставка журнала копит отставание или паркуется |
keycloak |
Harness и исполнители (ходят по PAT мимо Keycloak) | Вход людей в веб-консоль |
platform-api |
Harness и исполнители | Веб-консоль |
caddy |
Всё внутри хоста; доступ через SSH-туннель к 127.0.0.1 |
Любой доступ снаружи |
| Runner-хост | Платформа целиком | Автономное исполнение задач, назначенных этому исполнителю |
Отказ IAM¶
Оценка.
docker compose ps iam-service iam-db
curl -s http://127.0.0.1:18010/healthz
docker compose logs --since 15m iam-service | tail -50
Что происходит. Control Plane проверяет подпись access token по JWKS из
кэша (устаревший кэш допустим до CP_IAM_JWKS_STALE_AFTER_SECONDS, по
умолчанию 3600 с), поэтому токены, выданные до отказа, работают до своего
истечения. Новые токены не выдаются: harness и исполнители теряют доступ в
пределах срока жизни access token (300 с). Шлюз панели отвечает
503 gateway.identity_unavailable. context-adapter, работающий через
service account, получает отказы и копит отставание.
Шаги.
- Если лежит база:
docker compose up -d iam-db, проверить диск и логи PostgreSQL. - Если сервис падает при старте — смотреть первую ошибку в логах:
- ошибка миграции Alembic → откат релиза (ниже);
PermissionErrorна ключе подписи → владелецsecrets/iam-signing.pemдолжен быть uid 10001 (chown 10001:10001, режим600);- ошибка подключения к БД → пароль
IAM_POSTGRES_PASSWORDв.envне совпадает с ролью в базе.
docker compose up -d iam-service, дождатьсяhealthy.- Проверить
context_adapter_parked_tenants; при> 0—control-plane ops adapter redrive <tenant-id>.
Аварийный вход, пока IAM не поднят¶
Если восстановление IAM затягивается, а в Control Plane нужно войти (снять
claim, отменить задачу, отозвать binding), владелец хоста выпускает
аварийный ключ — короткоживущий admin-ключ Control Plane для человека
(CP-ADR-0065). API для этого нет: команда выполняется в контейнере
control-plane-api, границей доверия служит shell на хосте.
# principal оператора — cpOperatorPrincipalId в deploy/state/<env>.json
PRINCIPAL=$(python3 -c 'import json;print(json.load(open("deploy/state/taimen.json"))["cpOperatorPrincipalId"])')
docker compose exec -e BREAK_GLASS_OPERATOR="$(whoami)" control-plane-api \
python -m control_plane.break_glass issue --principal "$PRINCIPAL" --ttl 3600 \
--reason "IAM недоступен, <номер инцидента>"
Ключ cp_bg… печатается один раз. Им работают как обычным Bearer-токеном
(Authorization: Bearer cp_bg…), в том числе при
CP_LEGACY_API_KEYS_ENABLED=false: обычные legacy-ключи при этом
по-прежнему не принимаются.
| Ограничение | Значение |
|---|---|
| Кому | только активному principal вида human |
| Права | admin |
| Срок жизни | --ttl от 60 с до CP_BREAK_GLASS_MAX_TTL_SECONDS (4 ч); по умолчанию 1 ч |
| Аудит | событие api_key.break_glass_issued: principal, префикс, срок, причина, кто выпустил |
| Выключатель | CP_BREAK_GLASS_ENABLED=false — нет ни выпуска, ни приёма выпущенных |
Как только IAM поднят — отзовите все аварийные ключи:
Control Plane не готов¶
Оценка: curl -s http://127.0.0.1:18000/health/ready.
| Ответ | Причина | Действие |
|---|---|---|
503 database_unreachable |
База недоступна | docker compose ps control-plane-db, логи, диск; docker compose up -d control-plane-db |
503 migrations_pending |
Ревизия БД не равна head образа | Если API не стартует из-за ошибки миграции — логи control-plane-api, откат релиза; если ревизия БД новее образа — запущен старый образ поверх новой схемы: вернуть новый образ или сделать downgrade |
| Нет ответа | Контейнер в цикле рестартов | docker compose logs --tail 100 control-plane-api |
Пока API не готов, control-plane-worker и context-adapter не стартуют
(зависимость service_healthy) — это защита, а не отдельная проблема.
Откат релиза¶
Короткая версия; полная — в Обновлении и миграциях.
cd /opt/taimen/src
# 1. Если новый релиз применил миграции — downgrade НОВЫМ образом
docker compose stop control-plane-worker context-adapter control-plane-api
docker compose run --rm --no-deps control-plane-api alembic downgrade <ревизия прошлого релиза>
# 2. Код и образы прошлого релиза
git checkout <коммит прошлого релиза> && git submodule update --init --recursive
docker compose --profile core --profile edge build # или прежний IMAGE_TAG без сборки
docker compose --profile core --profile edge up -d
make smoke
Если downgrade невозможен — восстановление базы из бэкапа, снятого перед релизом (см. Резервное копирование).
Потеря runner-хоста¶
Оценка. Машина недоступна или скомпрометирована. На ней лежали: PAT исполнителя, токен подписки кодового агента, токен forge, рабочие копии с неопубликованными изменениями.
Что происходит с задачами. Claims исполнителя истекают по аренде
(CP_CLAIM_TTL_SECONDS, по умолчанию 300 с), после чего задачу может взять
другой исполнитель (takeover). Незавершённый run остаётся в статусе
running; когда исполнитель с тем же principal стартует снова, он находит
свой осиротевший run через /harness/context и закрывает его с
failure_reason=restart_recovery, возвращая задачу в очередь.
Шаги.
- Если потеря неконтролируемая (кража, взлом, доступ посторонних) —
считать все секреты хоста скомпрометированными:
- отозвать binding исполнителя в Control Plane (немедленно закрывает вход):
POST /api/v1/iam-bindings/<binding-id>:revoke; - отозвать PAT исполнителя в IAM (
…/platform-access-tokens/<id>:revoke); - отозвать токен подписки у поставщика и токен forge в forge.
- отозвать binding исполнителя в Control Plane (немедленно закрывает вход):
- Поднять новую машину по Установке runner.
- Выпустить исполнителю новый PAT (см. Секреты и ротация);
если binding отзывался — создать заново
POST /api/v1/principals/<principal-id>/iam-bindingsс прежними правами. - Запустить исполнителя. Проверить в логах, что осиротевшие runs закрыты
restart_recovery, а задачи вернулись в очередь. - Неопубликованная работа утеряна; опубликованные ветки
task/<id>лежат в forge и доступны для ревью.
Экстренно остановить исполнителя, не разбираясь:
Остановка безопасна в любой момент: run будет закрыт restart_recovery
при следующем старте, рабочая копия сохранится.
Компрометация credentials¶
PAT человека или агента¶
| Шаг | Команда | Эффект |
|---|---|---|
| 1. Закрыть вход в Control Plane | POST /api/v1/iam-bindings/<binding-id>:revoke |
Сразу: кэш binding сбрасывается в процессе API |
| 2. Отозвать PAT | POST …/tenants/<t>/platform-access-tokens/<id>:revoke?reason=leaked (bootstrap) или POST /api/v1/platform-access-tokens:revoke-self (владелец) |
Новые обмены невозможны |
| 3. Разобрать последствия | Audit IAM (platform_access_tokens.exchange по префиксу PAT), журнал событий Control Plane по principal |
Понять, что сделано чужим токеном |
| 4. Вернуть доступ | Новый PAT, повторный POST /api/v1/principals/<id>/iam-bindings |
Владелец работает дальше |
Уже выданные этим PAT access token живут до 300 с; шаг 1 закрывает их в
Control Plane раньше. Если PAT имел control-plane:admin, проверьте в
журнале созданные principals, bindings и изменения каталога за период утечки.
Bootstrap-токен IAM¶
IAM_BOOTSTRAP_TOKEN позволяет выпустить PAT любому principal — это
инцидент наивысшей важности.
- Сменить значение в
.envна новое случайное (openssl rand -hex 24),docker compose up -d iam-service(иpolicy-service,policy-worker, если подняты). - Выгрузить список PAT (
GET …/platform-access-tokens?includeRevoked=true) и audit IAM; отозвать всё, что выпущено не вами после вероятного момента утечки. - Проверить service accounts, созданные за тот же период, и отозвать
лишние (
…/service-accounts/<client-id>:revoke).
Ключ подписи IAM¶
Позволяет подделать access token любого principal. Немедленная ротация ключа и перезапуск сервисов, проверяющих токены, — см. Секреты и ротация.
Секрет service account¶
# ядро: bootstrap перевыпустит и отзовёт прежний
mv secrets/control-plane-iam.env /tmp/ && python3 deploy/bootstrap.py --env .env --name <env>
docker compose up -d control-plane-api control-plane-worker context-adapter
Для прочих service accounts — отзыв в IAM и перевыпуск; см. Секреты и ротация.
MEMORY_API_KEY, ключ LLM-провайдера, пароли БД¶
Сменить значение (для паролей БД — сначала ALTER ROLE в базе), обновить
.env, пересоздать потребителей. Процедуры — в
Секретах и ротации.
Периметр недоступен или истёк сертификат¶
Всё внутри хоста продолжает работать. Для эксплуатационных операций
используйте порты на 127.0.0.1 через SSH-туннель:
ssh -N -L 18000:127.0.0.1:18000 -L 18010:127.0.0.1:18010 <хост платформы>
curl -s http://127.0.0.1:18000/health/ready
Туннели в ssh-алиасе
Если в ~/.ssh/config для хоста прописаны LocalForward, а порты уже
заняты другой сессией, ssh падает целиком (bind … Address already in
use) — вместе с командой, ради которой вы подключались. Подключайтесь
с ssh -o ClearAllForwardings=yes <алиас>; для git поверх такого алиаса —
GIT_SSH_COMMAND='ssh -o ClearAllForwardings=yes'.
Причины и исправление сертификатов — в Периметре и TLS.
Закончился диск¶
PostgreSQL перестаёт принимать запись, сервисы отвечают 5xx.
df -h /var/lib/docker
docker system df
docker builder prune -f # кэш сборки
docker image prune -f # висячие образы
journalctl --vacuum-size=500M
Затем найдите растущий объект (см. Ресурсы и масштабирование) и заведите алерт на заполнение диска. Не удаляйте файлы внутри томов баз данных вручную.
Смена публичного адреса (issuer)¶
Не авария, но процедура с риском закрыть вход всем: issuer IAM равен
${TAIMEN_PUBLIC_URL}/iam, а Control Plane ищет binding по паре
(issuer, iam_principal_id).
- До переключения, пока старый адрес работает, создайте для каждого
principal binding с новым issuer — тем же вызовом
POST /api/v1/principals/<principal-id>/iam-bindings, указав"issuer": "https://new.example.com/iam"и прежние права. Старые bindings остаются и не мешают. - Настройте DNS и Caddyfile для нового имени, дождитесь сертификата.
- Поменяйте
TAIMEN_PUBLIC_URLиTAIMEN_PUBLIC_HOSTв.env. - Пересоберите
platform-web(публичные адреса зашиваются в бандл) и пересоздайте всё:docker compose … build platform-web && docker compose … up -d. - Обновите redirect URI клиента
platform-webв realm Keycloak (шаблон realm на существующий realm не применяется). - Обновите
CONTROL_PLANE_SERVERиCONTROL_PLANE_IAM_URLу исполнителей и операторов. Ключ записи вcredentials.jsonвключает адрес IAM — перенесите записи под новый адрес. - После проверки отзовите bindings со старым issuer.
Если шаг 1 пропущен и вход уже закрыт, bindings переносятся SQL в базе Control Plane:
UPDATE iam_principal_bindings
SET issuer = 'https://new.example.com/iam', updated_at = now()
WHERE issuer = 'https://old.example.com/iam';
После правки базы мимо API Control Plane может ещё до
CP_IAM_BINDING_STALE_AFTER_SECONDS (по умолчанию 120 с) отвечать по
закэшированному отказу; чтобы не ждать, перезапустите control-plane-api.
После инцидента¶
- Запишите хронологию, затронутые компоненты и принятые меры.
- Проверьте
make smoke,/health/ready,context_adapter_parked_tenants. - Если отзывались credentials — убедитесь, что все легитимные клиенты получили новые и работают.
- Добавьте алерт, который поймал бы инцидент раньше (см. Мониторинг и здоровье).