keeper.yml
Конфиг одного инстанса Keeper-а. Несколько инстансов с разным kid стоят за общими PostgreSQL и Redis и образуют кластер — конфиг у каждого свой, различается как минимум идентификатором.
Файл — типизированный YAML: на старте он проходит валидацию (структура, типы, инварианты). Неизвестный ключ, неверный тип или нарушенный инвариант отвергаются с диагностикой; конфиг не применяется частично.
Конвенции типов
Заголовок раздела «Конвенции типов»| Запись | Смысл |
|---|---|
string | строка UTF-8. |
int | целое. |
bool | true / false. |
duration | Go-duration (1s / 500ms / 1h30m) плюс суффикс <N>d для дней (30d). Композитная форма вида 1d2h не поддерживается. |
enum{a,b,c} | строка из перечисленного множества. |
string(host:port) | host:port; host — IP или DNS-имя, port — 1..65535. |
vault-ref | строка vault:<path> (например vault:secret/keeper/postgres); резолвится Vault-клиентом Keeper-а на старте. |
path | абсолютный путь в ФС хоста Keeper-а. |
В таблицах ниже default: — означает обязательное поле. Опциональные блоки можно опускать целиком — применяются значения по умолчанию.
kid — идентификатор инстанса
Заголовок раздела «kid — идентификатор инстанса»kid: keeper-eu-west-01| Поле | Тип | Default | Назначение |
|---|---|---|---|
kid | string (kebab-case, уникален в кластере) | — | Стабильный человекочитаемый идентификатор инстанса. Используется в lease на агентов, в аудит-событиях, в метках метрик. Обязателен. |
listen — сетевые слушатели
Заголовок раздела «listen — сетевые слушатели»Keeper поднимает несколько независимых listener-ов. gRPC разделён на два sub-listener-а: bootstrap (онбординг агента, server-only TLS — у агента до онбординга ещё нет клиентского сертификата) и event_stream (долгоживущий bidi-стрим, mTLS).
listen: grpc: bootstrap: addr: "0.0.0.0:9442" tls: cert: /etc/keeper/tls/server.crt key: /etc/keeper/tls/server.key event_stream: addr: "0.0.0.0:8443" # прод-дефолт 8443; dev часто 9443 max_apply_size_mb: 8 tls: cert: /etc/keeper/tls/server.crt key: /etc/keeper/tls/server.key ca: /etc/keeper/tls/ca.crt openapi: { addr: "0.0.0.0:8080" } mcp: { addr: "0.0.0.0:8081" } # опционально; уберите блок — MCP выключен metrics: { addr: "0.0.0.0:9090" }| Поле | Тип | Default | Назначение |
|---|---|---|---|
listen.grpc.bootstrap.addr | string(host:port) | — | bind-адрес listener-а онбординга (server-only TLS). Обязателен. Типично :9442 (одинаков в dev и проде). |
listen.grpc.bootstrap.tls.cert / .key | path | — | серверный сертификат и ключ для онбординга. CA сюда не кладётся (TLS односторонний). |
listen.grpc.event_stream.addr | string(host:port) | — | bind-адрес долгоживущего стрима к агентам (mTLS). Обязателен; должен отличаться от bootstrap-адреса. Прод-дефолт :8443, в dev обычно :9443. |
listen.grpc.event_stream.tls.cert / .key | path | — | серверный сертификат и ключ. Допустимо совпадение с bootstrap. |
listen.grpc.event_stream.tls.ca | path | — | CA, которой валидируются клиентские сертификаты агентов на mTLS-handshake. |
listen.grpc.event_stream.max_apply_size_mb | int (МиБ, ≥1) | 8 | Потолок размера одного исходящего сообщения к агенту (прежде всего пачка отрендеренных задач). При попытке отправить больше — fail-fast с понятной ошибкой, а не молчаливый отказ на стороне агента. Должен быть ≤ recv-лимиту агента (keeper.max_apply_size_mb в soul.yml); дефолты обеих сторон совпадают (8 МиБ). |
listen.openapi.addr | string(host:port) | — | bind-адрес Operator API (первичный интерфейс оператора, OpenAPI). Обязательный listener; отключение не предусмотрено. Типично :8080. |
listen.mcp.addr | string(host:port) | — | bind-адрес MCP-сервера (интерфейс наравне с OpenAPI). Опциональный listener: если addr не задан / блок убран — MCP не поднимается, MCP выключен. Типично :8081. |
listen.metrics.addr | string(host:port) | — | bind-адрес выделенного Prometheus-/metrics. Отдельный порт (обычно :9090), без auth-цепочки Operator API. Обязательный listener; отключение не предусмотрено. Опц. защита — блок metrics. |
postgres
Заголовок раздела «postgres»postgres: dsn_ref: vault:secret/keeper/postgres pool: { min: 5, max: 50 }PostgreSQL — единственное холодное хранилище состояния кластера: реестры агентов и операторов, каталог сервисов, журналы.
| Поле | Тип | Default | Назначение |
|---|---|---|---|
postgres.dsn_ref | vault-ref | — | Vault-ссылка на полный DSN. Plaintext-DSN в файл не пишется. Поле в Vault KV — dsn. |
postgres.pool.min | int (≥1) | 2 | Минимальный размер пула на инстанс. |
postgres.pool.max | int (≥min) | 20 | Максимальный размер пула. Суммарный поток к PG = max × число инстансов — учитывайте при настройке max_connections PostgreSQL. |
Redis — горячий слой и шина координации между инстансами: presence/heartbeat, lease на идентификаторы агентов, выбор лидера фоновых задач. Клиент поддерживает три топологии нативно — mode: standalone | sentinel | cluster; пустой/опущенный mode = standalone (forward-compat). Выбор режима под HA — prerequisites.md → Redis.
# Standalone (default): один узел.redis: mode: standalone # можно опустить — это default addr: "redis.example.com:6379" password_ref: vault:secret/keeper/redis
# Sentinel: HA с автоматическим failover (рекомендуемый прод-путь on-premise).redis: mode: sentinel master_name: mymaster sentinels: - "sentinel-1.example.com:26379" - "sentinel-2.example.com:26379" - "sentinel-3.example.com:26379" password_ref: vault:secret/keeper/redis # пароль Redis-узлов sentinel_password_ref: vault:secret/keeper/redis#sentinel # опц., пароль sentinel-узлов
# Cluster: шардирование по слотам (горизонтальное масштабирование).redis: mode: cluster nodes: - "redis-1.example.com:6379" - "redis-2.example.com:6379" - "redis-3.example.com:6379" password_ref: vault:secret/keeper/redis| Поле | Тип | Default | Назначение |
|---|---|---|---|
redis.mode | enum{standalone,sentinel,cluster} | standalone (пусто/опущено) | Топология Redis. standalone — один узел; sentinel — Redis Sentinel HA (master-discovery в клиенте); cluster — Redis Cluster (slot-routing в клиенте). |
redis.addr | string(host:port) | — | Адрес узла. Обязателен при mode: standalone; при sentinel/cluster игнорируется. |
redis.master_name | string | — (optional) | Имя monitored master group. Обязателен при mode: sentinel. |
redis.sentinels | list<string(host:port)> | — (optional) | Адреса sentinel-узлов. Обязателен (непустой) при mode: sentinel. |
redis.nodes | list<string(host:port)> | — (optional) | Адреса узлов кластера для bootstrap-discovery (клиент сам подтянет полную топологию). Обязателен (непустой) при mode: cluster. |
redis.password_ref | vault-ref или string | — | Пароль Redis. vault:<mount>/<path>[#field] резолвится из Vault (default-поле password, override через #field); plaintext работает как есть; пустое — без пароля. |
redis.sentinel_password_ref | vault-ref или string | — (optional) | Отдельный пароль самих sentinel-узлов. Та же форма, что password_ref. Имеет смысл только при mode: sentinel. |
Vault — обязательная зависимость Keeper-а: блок проверяется на старте, без доступного Vault Keeper не стартует. Несёт PKI для выпуска mTLS-идентичности агентов, KV для секретов (DSN, пароли) и ключ подписи операторских токенов.
Keeper читает KV-секреты прозрачно как из KV v1, так и из KV v2 — версия mount-а определяется автоматически, оператору всё равно, какая из них поднята. Указывать версию в конфиге не нужно (см. опциональный override kv_version ниже).
# Dev: статический токенvault: addr: "http://127.0.0.1:8200" token: "dev-root-token" auth: { method: token } # default; блок auth можно опустить pki_mount: "pki"
# Прод: AppRolevault: addr: "https://vault.example.com:8200" auth: method: approle role_id: keeper-prod # не секрет — допустим inline secret_id_file: /etc/keeper/vault-secret-id # mode 0400/0600 pki_mount: "pki/soulstack"vault.auth.method выбирает способ аутентификации Keeper-а в Vault:
token(default) — статический токен изvault.token. Удобно для локальной разработки. Блокauthцеликом можно опустить — это эквивалентmethod: token.approle— прод-путь: Keeper делаетapprole/loginсrole_id+secret_idи получает renewable-токен, который продлевается в фоне.
AppRole-credentials не читаются из самого Vault (иначе — циклическая зависимость: именно ими Keeper и логинится). Источник локальный: role_id — не секрет, задаётся inline; secret_id — секрет, берётся из mode-ограниченного файла (secret_id_file) либо переменной окружения (secret_id_env).
| Поле | Тип | Default | Назначение |
|---|---|---|---|
vault.addr | string (URL) | — | Адрес Vault. |
vault.token | string | — | Статический токен для method: token. При method: approle задавать нельзя. |
vault.kv_mount | string | secret | Mount point KV (без указания версии). Работает и для KV v1, и для KV v2 — версия определяется автоматически. |
vault.kv_version | enum{"1","2"} | — (auto) | Опциональный override версии KV mount-а. По умолчанию — автоопределение, указывать не нужно. Задаётся только для захардненного Vault, где автоопределение закрыто ACL (политика не даёт читать служебный mount-эндпоинт). Значение вне множества отвергается. |
vault.auth.method | enum{token,approle} | token | Метод аутентификации. Пустое = token. |
vault.auth.role_id | string | — | role_id AppRole (не секрет). Обязателен при method: approle. |
vault.auth.secret_id_file | path | — | Путь к файлу с secret_id. Взаимоисключающ с secret_id_env; ровно один обязателен при approle. |
vault.auth.secret_id_env | string | — | Имя переменной окружения с secret_id. |
vault.pki_mount | string | — | Mount PKI engine, через который Keeper выпускает идентичность агентов при онбординге. |
vault.pki_role | string | (optional) | Имя PKI-роли. Vault подписывает CSR через <pki_mount>/sign/<pki_role>. |
Один конфиг для KV v1 и v2
Заголовок раздела «Один конфиг для KV v1 и v2»Версию KV в конфиге указывать не нужно — один и тот же vault-блок работает и на KV v1, и на KV v2. Keeper определяет версию mount-а автоматически (probe через sys/internal/ui/mounts), поэтому в обычной конфигурации достаточно одного kv_mount:
vault: addr: "https://vault.internal:8200" auth: method: approle role_id: keeper-prod secret_id_file: /etc/keeper/vault-secret-id kv_mount: "secret" # путь mount-а; v1 или v2 — определяется автоматически pki_mount: "pki/soulstack"Симметрия распространяется и на ссылки на секреты. vault:-ref пишется mount-relative и без сегмента data/ — путь одинаков для обеих версий, сегмент data/ (нужный только KV v2) клиент подставляет сам:
postgres: dsn_ref: vault:secret/keeper/postgres # берёт поле dsnredis: password_ref: vault:secret/keeper/redis#password # конкретное поле через #auth: jwt: signing_key_ref: vault:secret/keeper/jwt-signing-keyТо же правило — для явного чтения секрета в сценарии через core.vault.kv-read: path указывается без data/ и работает на обеих версиях одинаково.
- name: Read DB credentials from Vault on: keeper module: core.vault.kv-read register: db_creds params: path: secret/redis/admin # без data/; работает и на v1, и на v2 fields: [username, password] # опционально; без — вернётся весь payloadЕдинственное место, где версия появляется в конфиге, — опциональный override kv_version. Он нужен только для захардненного Vault, где политика закрывает служебный probe-endpoint sys/internal/ui/mounts и автоопределение не проходит:
vault: # ... остальной блок ... kv_mount: "secret" kv_version: "1" # явно фиксируем версию, раз авто-определение недоступноВ норме строки kv_version в конфиге нет: поле опционально, default — auto (см. таблицу полей vault выше).
JWT-аутентификация операторов для Operator API и MCP. Блок отвечает только за подпись и формат токенов; реестр операторов и каталог ролей живут в PostgreSQL и управляются через API.
auth: jwt: signing_key_ref: vault:secret/keeper/jwt-signing-key issuer: keeper-eu-west-01 ttl_default: 24h ttl_bootstrap: 720h # 30 дней| Поле | Тип | Default | Назначение |
|---|---|---|---|
auth.jwt.signing_key_ref | vault-ref | vault:secret/keeper/jwt-signing-key | Vault KV-путь до ключа подписи операторских токенов. |
auth.jwt.issuer | string | <kid> | Значение claim iss в выпускаемых токенах. По умолчанию — kid инстанса; можно задать единое имя на кластер. |
auth.jwt.ttl_default | duration | 24h | TTL обычных операторских токенов. Короткий TTL — естественная защита (отозванный оператор перестаёт действовать с истечением токена). |
auth.jwt.ttl_bootstrap | duration | 720h (30 дней) | TTL первого bootstrap-токена, выпускаемого keeper init. |
У агента блока auth нет — он аутентифицируется к Keeper-у по mTLS (см. soul.yml). JWT — только для операторов.
metrics
Заголовок раздела «metrics»Опциональный блок защиты /metrics (сам bind-адрес — listen.metrics.addr). При отсутствии блока эндпоинт обслуживается без аутентификации.
metrics: auth: basic: enabled: true username: scrape password_ref: vault:secret/keeper/metrics-password| Поле | Тип | Default | Назначение |
|---|---|---|---|
metrics.auth.basic.enabled | bool | false | Включить HTTP Basic-auth на /metrics. |
metrics.auth.basic.username | string | — | Имя пользователя. Обязательно при enabled: true. |
metrics.auth.basic.password_ref | vault-ref | — | Vault-ссылка на пароль (поле password в KV). Plaintext запрещён. Обязательно при enabled: true. |
Пароль сравнивается constant-time и не попадает в логи. У агента симметричной защиты через Vault нет (у soul нет Vault-клиента) — там пароль задаётся файлом, см. soul.yml → metrics.
otel: enabled: true exporter: otlp endpoint: "otel-collector.example.com:4317"OpenTelemetry — push: при включении Keeper сам отправляет трейсы в указанный OTLP-коллектор. Входящего listener-порта для OTel у Keeper нет.
| Поле | Тип | Default | Назначение |
|---|---|---|---|
otel.enabled | bool | false | Включить экспорт. |
otel.exporter | enum{otlp} | otlp | Формат экспорта. |
otel.endpoint | string(host:port) | — | Адрес OTLP-коллектора (gRPC). Обязателен при enabled: true. |
otel.export_metrics | bool | false | Опц. push метрик по OTLP в дополнение к Prometheus-scrape. На текущем этапе экспортируются только трейсы; по умолчанию метрики идут через Prometheus-/metrics. |
logging
Заголовок раздела «logging»logging: level: info format: json file: /var/log/keeper/keeper.log # пусто → stderr без ротации rotation: max_size_mb: 100 max_age_days: 7 max_files: 10 compress: trueПоведение зависит от logging.file:
- не задан → вывод в
stderrбез ротации (удобно под systemd/journald и в контейнере); - задан → запись в файл со встроенной ротацией (без зависимости от внешнего
logrotate), архивы рядом по шаблону<file>-<timestamp>.<ext>.
| Поле | Тип | Default | Назначение |
|---|---|---|---|
logging.level | enum{debug,info,warn,error} | info | Уровень логирования. |
logging.format | enum{json,text} | json | json для машинной обработки, text для человека. |
logging.file | path | — (stderr) | Путь к лог-файлу. Пусто — stderr без ротации. |
logging.rotation.max_size_mb | int (МБ) | 100 | Порог ротации одного файла. |
logging.rotation.max_age_days | int (≥0) | 7 | Сколько дней хранить архив. |
logging.rotation.max_files | int | 10 | Сколько архивов держать. |
logging.rotation.compress | bool | true | Сжимать ли архивы. |
Поля logging.rotation.* применяются только когда задан logging.file.
plugins
Заголовок раздела «plugins»Каталог плагинов с host = Keeper: CloudDriver-плагины (для cloud-провижининга) и SshProvider-плагины (для push по SSH). Keeper резолвит их из git-репозиториев в локальный кеш при старте.
plugins: cache_root: /var/lib/soul-stack-keeper/plugins work_root: /var/lib/soul-stack-keeper/plugin-src fetch_timeout: 120s max_artifact_size_mb: 256 max_clone_size_mb: 1024 cloud_drivers: - { name: aws, source: "git@github.com:example/soul-cloud-aws.git", ref: v2.0.0 } ssh_providers: - { name: vault-ssh, source: "git@github.com:example/soul-ssh-vault.git", ref: v1.0.0 }| Поле | Тип | Default | Назначение |
|---|---|---|---|
plugins.cache_root | path (абс.) | /var/lib/soul-stack-keeper/plugins | Кеш собранных артефактов плагинов. |
plugins.work_root | path (абс.) | /var/lib/soul-stack-keeper/plugin-src | Корень рабочих git-клонов резолвера. Должен быть вне cache_root. |
plugins.fetch_timeout | duration | 120s | Потолок одной цепочки git-операций резолва плагина. |
plugins.max_artifact_size_mb | int (МиБ, ≥1) | 256 | Потолок размера одного извлекаемого исполняемого файла плагина (защита диска от враждебного репозитория). Превышение — fail-closed. |
plugins.max_clone_size_mb | int (МиБ, ≥1) | 1024 | Потолок размера рабочего дерева клона. Превышение — fail-closed. |
plugins.cloud_drivers[].name / .source / .ref | string / git-URL / git-ref | — | Имя провайдера, git-репозиторий плагина и его ref (tag или branch). |
plugins.ssh_providers[].name / .source / .ref | string / git-URL / git-ref | — | То же для SSH-провайдеров push. |
Версия артефакта — это git ref (tag или branch), а не поле в манифесте; semver-range не используется.
plugin_runtime
Заголовок раздела «plugin_runtime»Lifecycle host-процесса плагинов на Keeper-стороне: таймауты handshake-а и остановки, whitelist capabilities, политика конфликтов ресурсов.
plugin_runtime: socket_dir: /var/run/soul-stack-keeper/plugins startup_timeout: 10s shutdown_grace: 10s allowed_capabilities: - run_as_root - network_outbound - network_inbound - vault_access - fs_write_root - exec_subprocess conflict_policy: warn enable_tls: false| Поле | Тип | Default | Назначение |
|---|---|---|---|
plugin_runtime.socket_dir | path | /var/run/soul-stack-keeper/plugins/ | Каталог Unix-domain socket-ов плагинов (mode 0700, владелец — служебный пользователь Keeper-а). |
plugin_runtime.startup_timeout | duration | 10s | Время от запуска плагин-процесса до handshake. Превышение — SIGTERM, затем SIGKILL. |
plugin_runtime.shutdown_grace | duration | 10s | Окно от SIGTERM до SIGKILL при остановке плагина. |
plugin_runtime.allowed_capabilities | list<enum> | все 6 | Whitelist возможностей плагина. Линтер отвергает Destiny до запуска, если плагин запрашивает capability вне списка. По умолчанию разрешены все шесть; сужайте по политике безопасности. |
plugin_runtime.conflict_policy | enum{warn,fail} | warn | Что делать, если два плагина в одном прогоне претендуют на один ресурс: warn — записать аудит и продолжить, fail — пометить шаг неуспешным. |
plugin_runtime.enable_tls | bool | false | mTLS на plugin-сокете. В текущем релизе — только false; безопасность обеспечивается правами 0700 на сокет. |
audit: enabled: true otel_export: true retention_days: 365| Поле | Тип | Default | Назначение |
|---|---|---|---|
audit.enabled | bool | true | Глобальный выключатель аудита. При false write-path не пишет в PostgreSQL. В проде держите true (compliance). |
audit.otel_export | bool | true | Дублировать аудит-событие в OTel span как атрибут (источник правды — PostgreSQL). |
audit.retention_days | int (≥1) | 365 | Срок хранения записей аудита (дней). Чистка выполняется фоновой задачей. |
У агента блока audit нет: события Soul-стороны (отчёты о прогонах) идут через Keeper и пишутся им.
Фоновая задача очистки PostgreSQL от просроченных записей (один лидер на кластер, выбирается через Redis-lease).
reaper: enabled: true interval: 1h dry_run: false batch_size: 500 lock_ttl: 5m rules: expire_pending_seeds: { enabled: true, max_age: 24h, action: delete } mark_disconnected: { enabled: true, stale_after: 90s, action: set_status, target_status: disconnected } purge_audit_old: { enabled: true, max_age: 365d, action: delete } # … остальные правила| Поле | Тип | Default | Назначение |
|---|---|---|---|
reaper.enabled | bool | true | Включить очистку. |
reaper.interval | duration | 1h | Интервал прохода. |
reaper.dry_run | bool | false | Сухой прогон без мутаций (для проверки). |
reaper.batch_size | int | 500 | Размер батча одного прохода. |
reaper.lock_ttl | duration | 5m | TTL Redis-lease на лидерство. |
reaper.rules | map<string, object> | — | Предопределённые правила чистки (просроченные токены онбординга, отметка отвалившихся агентов, удаление старого аудита и т. п.). Поля правила зависят от его action. |
cadence_scheduler
Заголовок раздела «cadence_scheduler»Опциональный блок планировщика регулярных запусков (один лидер на кластер). При отсутствии — поднимается с дефолтами.
cadence_scheduler: enabled: true # опущено → ON по умолчанию; false → выключить poll_floor: 30s poll_ceiling: 60s poll_idle: 120s lock_ttl: 5m| Поле | Тип | Default | Назначение |
|---|---|---|---|
cadence_scheduler.enabled | bool (tri-state) | nil → ON | Включение планировщика. Опущено/null → ON (по умолчанию включён); false → выключить; true → включить. Читается на старте. |
cadence_scheduler.poll_floor | duration | 30s | Нижняя граница шага опроса. Абсолютный минимум — 30s. |
cadence_scheduler.poll_ceiling | duration | 60s | Верхняя граница шага опроса. Должна быть ≥ poll_floor. |
cadence_scheduler.poll_idle | duration | 120s | Шаг опроса при пустом реестре расписаний. Должен быть ≥ poll_ceiling. |
cadence_scheduler.lock_ttl | duration | 5m | TTL Redis-lease на лидерство планировщика. |
Опциональный per-оператор rate-limiter тяжёлых write-эндпоинтов. При отсутствии блока — включён с дефолтами.
tempo: enabled: true voyage_create: # лимит создания батчевых прогонов rate: 10 # токенов в секунду burst: 20 # допустимый всплеск voyage_preview: # лимит предпросмотра (мягче — без записи) rate: 30 burst: 60| Поле | Тип | Default | Назначение |
|---|---|---|---|
tempo.enabled | bool (tri-state) | true | Включить лимитер. Опущено/null → включён. |
tempo.voyage_create.rate | float | 10 | Скорость пополнения токенов на создание прогона (запросов в секунду). |
tempo.voyage_create.burst | int | 20 | Допустимый всплеск создания. |
tempo.voyage_preview.rate | float | 30 | Скорость пополнения на предпросмотр (мягче — операция без записи). |
tempo.voyage_preview.burst | int | 60 | Допустимый всплеск предпросмотра. |
web_ui_enabled
Заголовок раздела «web_ui_enabled»Top-level тоггл встроенного операторского web-UI на маршруте /ui. Реальный UI включён в исполняемый файл keeper и отдаётся им из коробки — отдельного процесса, порта или backend-а не требует; статика садится на уже существующий Operator-API-listener (listen.openapi.addr, обычно :8080). Подробнее о доступе и работе с UI — Web-интерфейс.
# web_ui_enabled: true # дефолт (опущено / null → ON); false — opt-out| Поле | Тип | Default | Назначение |
|---|---|---|---|
web_ui_enabled | *bool (tri-state) | true | Монтировать ли встроенный UI на /ui. Опущено / null → true (default-ON, single-binary UI «из коробки»); явный false → opt-out: статика /ui не монтируется, /v1/* и /docs не затрагиваются. В отличие от tempo / cadence_scheduler от инфраструктуры не зависит — UI вшит в исполняемый файл, внешнего backend-а не нужно. |
HA-инвариант: воркер-пул при нескольких инстансах
Заголовок раздела «HA-инвариант: воркер-пул при нескольких инстансах»В кластере из нескольких живых инстансов исполнение прогонов должно идти через общий воркер-пул, а не привязываться к памяти одного инстанса. Этим управляет ключ acolytes:
acolytes: 4 # число воркеров пула исполнения на инстанс; 0 — пул выключен| Поле | Тип | Default | Назначение |
|---|---|---|---|
acolytes | int (≥0) | 0 | Число воркеров пула исполнения прогонов на инстанс. 0 — пул не поднимается, исполнение идёт «классическим» путём в памяти инстанса-владельца. |
Сайзинг: сколько ставить acolytes
Заголовок раздела «Сайзинг: сколько ставить acolytes»Что это. acolytes: N — число фоновых воркеров-«аколитов» в одном инстансе Keeper, которые разбирают общую очередь прогонов (apply-заданий) из PostgreSQL и исполняют их. Значение считается per-инстанс; суммарная параллельность кластера = acolytes × число живых инстансов.
С чем масштабируется. С числом и шириной одновременно идущих прогонов, не с числом душ: 10000 агентов — это не 10000 аколитов. Агенты просто висят на стримах и сами по себе аколита не занимают; аколит активен, только когда в очереди есть задания запущенного прогона. Один прогон на 50 хостов = 50 заданий в очереди, и их параллельно разгребают все живые аколиты всех инстансов кластера.
Сколько ставить — стартовые числа:
| Топология | acolytes (на инстанс) | Почему |
|---|---|---|
| Одиночный Keeper | 0 | Штатный дефолт: прогон исполняет сам принявший его инстанс. Для одного узла корректно и безопасно. |
| HA из 2–3 инстансов | 10 | Разумный старт: половина дефолтного postgres.pool.max (20) с запасом; даёт 20–30 параллельных заданий по кластеру. acolytes > 0 здесь обязателен (см. HA-инвариант). |
| Высокая нагрузка | 20+ | Только если под реальной нагрузкой очередь planned-заданий устойчиво растёт и apply задерживаются — широкие прогоны на сотни хостов или много одновременных запусков. |
Потолок и связь с postgres.pool.max. Держите acolytes ≤ postgres.pool.max − запас (~8–10). Каждый воркер берёт PostgreSQL-коннекты из общего пула, и за те же коннекты с ним конкурируют Operator API, Reaper и обработка Voyage. Если воркеров больше, чем коннектов в пуле, — лишние просто ждут свободный коннект (голодание, рост латентности), а параллелизма не прибавляется. Нужно больше воркеров — сначала поднимите postgres.pool.max, потом acolytes. Жёсткого верхнего предела у самого acolytes в конфиге нет (валидируется только ≥ 0); фактический потолок задаёт размер PG-пула.
hot_reload
Заголовок раздела «hot_reload»hot_reload: enable_signal: true enable_inotify: false audit_correlation_id: trueУправляет триггерами hot-reload. Блок опционален; при отсутствии — дефолты из таблицы. Все три поля требуют перезапуска при изменении (они контролируют сам механизм reload).
| Поле | Тип | Default | Назначение |
|---|---|---|---|
hot_reload.enable_signal | bool | true | Включить SIGHUP-триггер: процесс перечитывает keeper.yml с диска, валидирует и атомарно подменяет. |
hot_reload.enable_inotify | bool | false | Авто-reload по изменению файла (Linux-only). По умолчанию выключено. |
hot_reload.audit_correlation_id | bool | true | Генерировать correlation-id для аудит-событий reload. |
Hot-reload
Заголовок раздела «Hot-reload»Конфиг можно перечитывать без полного перезапуска процесса. Два пути:
- File-edit — оператор правит
keeper.ymlна хосте и шлёт процессуSIGHUP. Pipeline: parse → валидация → атомарная подмена → аудит. - API/MCP — мутация конфига через Operator API; дополнительно к подмене происходит write-back: изменённое значение записывается обратно в
keeper.yml(с сохранением комментариев и порядка ключей, атомарной заменой файла).
Любая ошибка валидации оставляет текущее состояние нетронутым — файл не модифицируется.
Что применяется без перезапуска: уровень логирования, параметры пула PostgreSQL, интервалы и правила фоновых задач, пороги rate-limiter-а, политики плагинов (whitelist capabilities, conflict-policy, таймауты новых запусков).
Что требует перезапуска: адреса listener-ов и TLS-сертификаты, DSN PostgreSQL и параметры Redis, адрес/auth Vault, ключ подписи токенов, пути лог-файлов и параметры ротации, OTel-экспортёр, поднятие/гашение целых подсистем (планировщик, воркер-пул).
Каждый инстанс кластера перечитывает свой конфиг независимо — централизованной cross-host синхронизации reload в текущем релизе нет.
Что перенесено в БД (в конфиге нет)
Заголовок раздела «Что перенесено в БД (в конфиге нет)»- RBAC (роли, привязки операторов, permissions) — в PostgreSQL, управление через API. Ключ
rbac:вkeeper.ymlотвергается. - Реестр сервисов и связанные well-known значения — в PostgreSQL, управление через API. Ключи
services:/default_destiny_source:вkeeper.ymlотвергаются.
Полный пример
Заголовок раздела «Полный пример»Минимальный валидный keeper.yml со всеми обязательными полями:
kid: keeper-eu-west-01
listen: grpc: bootstrap: addr: "0.0.0.0:9442" tls: cert: /etc/keeper/tls/server.crt key: /etc/keeper/tls/server.key event_stream: addr: "0.0.0.0:8443" tls: cert: /etc/keeper/tls/server.crt key: /etc/keeper/tls/server.key ca: /etc/keeper/tls/ca.crt openapi: { addr: "0.0.0.0:8080" } mcp: { addr: "0.0.0.0:8081" } metrics: { addr: "0.0.0.0:9090" }
postgres: dsn_ref: vault:secret/keeper/postgres pool: { min: 5, max: 50 }
redis: addr: "redis.example.com:6379" password_ref: vault:secret/keeper/redis
vault: addr: "https://vault.example.com:8200" auth: method: approle role_id: keeper-prod secret_id_file: /etc/keeper/vault-secret-id pki_mount: "pki/soulstack"
auth: jwt: signing_key_ref: vault:secret/keeper/jwt-signing-key issuer: keeper-eu-west-01 ttl_default: 24h ttl_bootstrap: 720h
otel: enabled: true exporter: otlp endpoint: "otel-collector.example.com:4317"
logging: level: info format: json rotation: { max_size_mb: 100, max_files: 10, compress: true }
audit: enabled: true otel_export: true retention_days: 365
reaper: enabled: true interval: 1h batch_size: 500 lock_ttl: 5m rules: expire_pending_seeds: { enabled: true, max_age: 24h, action: delete } mark_disconnected: { enabled: true, stale_after: 90s, action: set_status, target_status: disconnected } purge_audit_old: { enabled: true, max_age: 365d, action: delete }
# Для HA-кластера (несколько инстансов) задайте воркер-пул:# acolytes: 4См. также
Заголовок раздела «См. также»- soul.yml — конфиг агента.
- Конфигурация → Обзор — секреты через Vault, сквозные возможности, hot-reload в двух словах.
- Эксплуатация — масштабирование, мониторинг, обновления.
- Keeper — роль и порты компонента.