textmachine/platform/deploy/README.md

132 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Развёртывание платформы
Одна VM, systemd, бинари артефактами CI (`PLATFORM_DIRECTION.md` §3). Не Kubernetes: дети-`tmctl`
живут часами и держат эксклюзивный лок на файлах книги на локальном диске — оркестратор, способный
переселить под посреди прогона, этой нагрузке враждебен.
## Файлы
- `tmplatformd.service` — юнит контрольной панели. Тело юнита проверено `systemd-analyze verify`
(systemd 259) — exit 0, без замечаний. ⚠ Проверять надо с ПОДСТАВЛЕННЫМ существующим `ExecStart=`:
дословно юнит даёт exit 1, потому что `verify` проверяет и наличие бинаря, а `/usr/local/bin/tmplatformd`
на стенде нет. ⚠ **юнит целиком под systemd не запускался** — нет sudo и нет бинаря на стенде.
Отдельные свойства песочницы при этом проверены ЖИВЫМ прогоном в пользовательском systemd 259
(`systemd-run --user --wait`), а не вычитаны из доки:
`ProtectSystem=strict` + `ReadWritePaths=` на несуществующем пути → **`226/NAMESPACE`, юнит не
стартует**; тот же путь созданным → `0/SUCCESS`; префикс `-` на несуществующем → `0/SUCCESS`
(строка игнорируется — потому мы её и не префиксуем);
`ProtectHome=yes``/home` пуст, чтение `/home/<user>` даёт `Permission denied`;
`ProtectHome=tmpfs` + `BindPaths=<каталог>` → каталог виден. Ресурсные потолки
(`MemoryMax=80%`, `OOMPolicy=continue`) живьём не проверялись — вывод из
`systemd.resource-control(5)`/`systemd.service(5)`.
## Откат релиза: не ниже версии 5
`goose down` до версии 4 и ниже НЕ РАБОТАЕТ на живой базе: down-путь `00005` восстанавливает
`users_email_key` и `email NOT NULL`, а обе формы нарушают строки, которые пишет боевой код
(неподтверждённая личность даёт `email = NULL`; один адрес законно принадлежит двум аккаунтам).
Откат транзакционный, поэтому падение ничего не портит — но планировать откат ниже 5 нельзя,
план отката — накатить вперёд. Разбор: `docs/STACK_DECISIONS.md` §8.
## Что юнит закрывает содержательно
-**ПЕРЕ-ДИСПОЗИЦИЯ (P4, D39.106): прогон НЕ ребёнок этого юнита и не живёт в его cgroup.**
Каждый прогон — транзиентный юнит в СОБСТВЕННОМ systemd-менеджере пользователя `tmplatform`,
в срезе `tm-runs.slice`, потому что перевод обязан пережить деплой платформы. Следствия для
установки: обязателен `loginctl enable-linger tmplatform`; юниту нужен доступ к своей же
пользовательской шине (`ProtectHome=tmpfs` + `BindPaths=/run/user/%U` +
`DBUS_SESSION_BUS_ADDRESS`); ресурсные потолки НИЖЕ ограничивают только контрол-плейн.
- **PD-13 (осиротевшие процессы движка)** закрыт теперь ТАМ, где прогон: собственным cgroup прогона
(`TM_PLATFORM_RUN_MEMORY_MAX`/`_TASKS_MAX`), и это ИЗМЕРЕНО — в дефолтном `app.slice`
пользовательского менеджера лимиты принимаются и не применяются (`STACK_DECISIONS` §16).
Дев-путь супервизора с группой процессов остаётся дев-путём.
- **`TimeoutStopSec=90`** больше, чем дренаж платформы (15 с) плюс grace движка (30 с). Меньше —
и systemd прибьёт `tmctl` посреди остановки, оставив лок проекта.
- **Секреты через `LoadCredential=`,** а не через окружение: переменная окружения видна в
`/proc/<pid>/environ` и наследуется каждым ребёнком-`tmctl`. Конфиг читает `*_FILE` первым.
## Установка (набросок, исполняется владельцем)
```sh
useradd --system --home-dir /srv/textmachine tmplatform
# ⚠ Каталог создаём ЯВНО: --system не создаёт домашний каталог, а `ReadWritePaths=/srv/textmachine`
# при `ProtectSystem=strict` на несуществующем пути валит сборку mount-namespace — юнит не стартует
# вовсе (PD-91, systemd.exec(5)). Это первая команда, которую пропускают, читая набросок сверху вниз.
install -d -m0750 -o tmplatform -g tmplatform /srv/textmachine
install -D -m0755 tmplatformd /usr/local/bin/tmplatformd
install -D -m0755 tmplatformctl /usr/local/bin/tmplatformctl
# ⚠ БЕЗ ЭТОГО НИ ОДИН ПРОГОН НЕ СТАРТУЕТ. Прогоны — транзиентные юниты в пользовательском
# менеджере `tmplatform`, а он существует вне сессии входа только при включённом linger.
# Проверка: `systemctl --user -M tmplatform@ is-system-running` отвечает, а не «Failed to connect».
loginctl enable-linger tmplatform
# Движок кладётся по ВЕРСИОНИРОВАННОМУ пути: попытка прогона пиннится к тому, с которого началась
# (строка 139), поэтому выкат новой версии не подменяет исполняемое у идущего прогона.
install -D -m0755 tmctl /opt/textmachine/engine/<версия>/tmctl
install -d -m0700 -o root -g root /etc/tmplatform
printf '%s' 'postgres://...' > /etc/tmplatform/dsn && chmod 0400 /etc/tmplatform/dsn
printf '%s' '<oauth client secret>' > /etc/tmplatform/oidc_client_secret && chmod 0400 /etc/tmplatform/oidc_client_secret
```
`/etc/tmplatform/env` — несекретное окружение:
```
TM_PLATFORM_ADDR=127.0.0.1:8080
TM_PLATFORM_TRUSTED_ORIGINS=https://app.example.org
TM_PLATFORM_OIDC_ISSUER=https://accounts.google.com
TM_PLATFORM_OIDC_CLIENT_ID=...
TM_PLATFORM_OIDC_REDIRECT_URL=https://app.example.org/auth/callback
TM_PLATFORM_AFTER_LOGIN=/library
TM_PLATFORM_SIGNUP_GRANT_USD=5
TM_PLATFORM_BOOKS_DIR=/srv/textmachine/books
TM_PLATFORM_METRICS_ADDR=127.0.0.1:9464
```
**`TM_PLATFORM_BOOKS_DIR` — каталог, который сервис ПИШЕТ и, на отклонённом интейке, УДАЛЯЕТ.**
Всё под ним создано загрузкой книг; книга, заведённая дев-инструментом с чужим `--workdir`, туда не
попадает и не удаляется никогда (гард `books.owns`). Каталог должен существовать и принадлежать
`tmplatform`, и он обязан быть в `ReadWritePaths=` юнита — при `ProtectSystem=strict` отсутствующий
путь валит сборку mount-namespace целиком (PD-91, та же грабля, что и с `/srv/textmachine`):
```sh
install -d -m0750 -o tmplatform -g tmplatform /srv/textmachine/books
```
**`/metrics` не аутентифицирован** — его защищает только адрес привязки. Дефолт `127.0.0.1`, то
есть снаружи он недостижим; поднять его на внешний адрес значит открыть операционную форму деплоя
(глубина очереди, число прогонов, возраст холдов) всем, кто до него дотянется. Скрейпер живёт на том
же хосте либо ходит через тот же edge, что и API. Разбор — `docs/STACK_DECISIONS.md` §24, строка
риска — PD-179.
**Загрузка книги идёт минуты, и это касается edge-прокси.** Маршрут `POST /v0/books` принимает до
`TM_PLATFORM_MAX_UPLOAD_BYTES` (64 МиБ) и держит соединение до `TM_PLATFORM_UPLOAD_DEADLINE`
(10 минут). Прокси перед сервисом обязан разрешать столько же: у него свои `client_max_body_size` и
свои таймауты чтения, и молчаливо режет их он, а не мы.
**`TM_PLATFORM_STATE_DIR` меняется только когда живых прогонов нет.** Exit-маркер пишется по пути,
вычисленному при СПАВНЕ, а читается по пути из текущей конфигурации: после смены каталога конец
идущего прогона становится невидим, и реконсилятор перезапускает его как потерянный (PD-155). Путь
обязан быть абсолютным — относительный демон отвергает на старте, потому что маркер пишет юнит из
каталога книги, а читает демон из своего.
Миграции выкатываются ОДИН раз, не каждой репликой: `TM_PLATFORM_MIGRATE=1 tmplatformd` разово
либо отдельный шаг деплоя. `goose` держит advisory-лок, так что параллельный запуск не гонка,
но и не норма.
## Где на сервере лежат книги
`/srv/textmachine` — корень библиотеки НА СЕРВЕРЕ. Решение владельца «книги живут в `~/books`»
относится к машине разработки: под этим юнитом домашние каталоги недоступны вовсе
(`ProtectHome=yes` подставляет пустой `/home` и детям-`tmctl` тоже), поэтому библиотека под
домашним каталогом на сервере просто не откроется — не «сработает медленнее», а не найдётся.
Оператору, которому это нужно, юнит называет ровно две строки замены (`ProtectHome=tmpfs` +
`BindPaths=`); других изменений не требуется.
⚠ Выбирающего путь кода ещё нет: `Supervisor.Workdir` задаёт вызывающий, а вызывающий — воркер,
которого нет (строка 103 единого бэклога). Когда он появится, корень становится настройкой, и её
дефолт — этот каталог.
## Чего здесь ещё нет
TLS и домен (перед юнитом предполагается edge-прокси), ограничитель соединений на edge,
ротация логов, бэкап Postgres. Всё это — работа с первым реальным деплоем, не раньше.