feat(ops): лёгкий Postgres на Beget под forgejo и glitchtip (вариант А) #3080

Merged
lekss361 merged 1 commit from fix/3061-infra-postgres-split into main 2026-08-24 16:31:13 +00:00
Owner

Зачем

Базы forgejo (265 МБ) и glitchtip (729 МБ) сейчас лежат внутри gendesign-postgres-1 — того самого кластера, который 30.08 уезжает целиком вместе с 15 ГБ Site Finder. В окно переезда легли бы веб Forgejo, git по HTTP и Actions, то есть CI, из которого мы деплоим, ровно тогда, когда он нужнее всего. Отдельно неприятно, что Forgejo заявлен компенсацией риска «всё в одном аккаунте Selectel»: падая вместе с переездом, эту роль он не выполняет.

Владелец выбрал вариант А — отдельный лёгкий кластер на Beget. PostGIS тут не нужен: обе схемы обычные реляционные, поэтому postgres:16-alpine, а не postgis/postgis:16-3.4 ради гигабайта данных.

Мерж и деплой ничего не меняют

  • Сервис infra-postgres стоит под профилем infra, которого нет ни в одном окружении на Beget → docker compose его просто не видит.
  • GLITCHTIP_DB_HOST имеет дефолт postgres → строка подключения GlitchTip рендерится байт-в-байт как сегодня.
  • Переключение = правка одной переменной на VM после переноса данных. Откат = правка обратно.

Первая редакция этого свойства не имела: сервис стоял в профиле glitchtip, который на Beget уже активен, и мерж поднял бы кластер с пустым INFRA_PG_PASSWORD в вечный restart-loop при зелёном деплое. Поймано ревью.

Что в диффе

Файл Что
docker-compose.prod.yml сервис infra-postgres (профиль infra, том infra_postgres_data, портов наружу нет), GLITCHTIP_DB_HOST с дефолтом, снят depends_on: postgres у glitchtip-web/worker, runbook в шапке
ops/db-bootstrap/infra-postgres/01-roles-and-databases.sh bootstrap ролей и баз forgejo / glitchtip
ops/backup-forgejo.sh автоопределение контейнера с живой базой forgejo
ops/split-infra-postgres.sh перенос данных, dry-run по умолчанию
.forgejo/workflows/deploy-infra.yml подхват конфиг-дрейфа infra-postgres, но только если контейнер уже существует
ops/gendesign-backup-forgejo.default.example PG_CONTAINER в конфиге бэкапа

Две защиты, которые стоит отревьюить отдельно

Healthcheck проверяет не приём соединений, а наличие обеих баз. Иначе «отравленный том» — когда bootstrap упал уже после инициализации PGDATA и потому больше никогда не запустится — выглядел бы здоровым, а обнаружился бы в ночь переезда как role "forgejo" does not exist.

backup-forgejo.sh при двух кандидатах останавливается с exit 1, а не выбирает первого. Это ровно окно миграции, когда база лежит в обоих кластерах: молча выбрать один — с вероятностью 1/2 бэкапить труп, а порог MIN_DB_DUMP_BYTES протухший дамп не поймает, он нормального размера.

ops/split-infra-postgres.sh

Dry-run по умолчанию, --apply для реального прогона. Останавливает писателей, снимает число строк, восстанавливает целевой ролью (суперюзер забрал бы таблицы себе и выдал permission denied уже после переключения), сверяет построчные карты из pg_class.

Ревью нашло два сценария бесшумной потери, оба закрыты:

  1. Писатель с несовпавшим именем больше не пропускается молча — имя контейнера Forgejo это догадка, его compose лежит вне репозитория. Пропуск теперь только явным --allow-missing-writer.
  2. --force-restore отказывается работать, если переключение уже сделано (читает app.ini и конфиг GlitchTip) — иначе затёр бы боевой новый кластер старым дампом, и сверка подтвердила бы успех.

Дампы под umask 077 + chmod 600: в дампе forgejo хеши паролей и access-токены.

Test plan

Проверено на живом Docker 29.5.3, не по YAML:

  • матрица профилей через docker compose config: сегодняшний рендер Beget байт-в-байт идентичен main
  • healthcheck исполнен внутри postgres:16-alpine — обе базы → 0, после DROP DATABASE одной → 1
  • bootstrap реально завёл роли и базы; пароль со спецсимволами логинится; в docker logs его нет
  • bash -n на обоих скриптах
  • ручной прогон ops/split-infra-postgres.sh без --apply на Beget — до окна переезда
  • прогон с --apply, проверка веба Forgejo и приёма событий GlitchTip

Ручное переключение (после мержа, на VM)

  1. поднять кластер: профиль infra + INFRA_PG_PASSWORD
  2. ops/split-infra-postgres.sh — сначала без флагов, потом --apply
  3. Forgejo app.ini: HOST = infra-postgres:5432
  4. GlitchTip: GLITCHTIP_DB_HOST=infra-postgres
  5. конфиг бэкапа: PG_CONTAINER=gendesign-infra-postgres

Refs #3061, #3057, #3075, #2989, #2203

## Зачем Базы `forgejo` (265 МБ) и `glitchtip` (729 МБ) сейчас лежат внутри `gendesign-postgres-1` — того самого кластера, который 30.08 уезжает целиком вместе с 15 ГБ Site Finder. В окно переезда легли бы веб Forgejo, git по HTTP и Actions, то есть CI, из которого мы деплоим, ровно тогда, когда он нужнее всего. Отдельно неприятно, что Forgejo заявлен компенсацией риска «всё в одном аккаунте Selectel»: падая вместе с переездом, эту роль он не выполняет. Владелец выбрал **вариант А** — отдельный лёгкий кластер на Beget. PostGIS тут не нужен: обе схемы обычные реляционные, поэтому `postgres:16-alpine`, а не `postgis/postgis:16-3.4` ради гигабайта данных. ## Мерж и деплой ничего не меняют - Сервис `infra-postgres` стоит под профилем `infra`, которого нет ни в одном окружении на Beget → `docker compose` его просто не видит. - `GLITCHTIP_DB_HOST` имеет дефолт `postgres` → строка подключения GlitchTip рендерится байт-в-байт как сегодня. - Переключение = правка одной переменной на VM после переноса данных. Откат = правка обратно. Первая редакция этого свойства не имела: сервис стоял в профиле `glitchtip`, который на Beget **уже активен**, и мерж поднял бы кластер с пустым `INFRA_PG_PASSWORD` в вечный restart-loop при зелёном деплое. Поймано ревью. ## Что в диффе | Файл | Что | |---|---| | `docker-compose.prod.yml` | сервис `infra-postgres` (профиль `infra`, том `infra_postgres_data`, портов наружу нет), `GLITCHTIP_DB_HOST` с дефолтом, снят `depends_on: postgres` у glitchtip-web/worker, runbook в шапке | | `ops/db-bootstrap/infra-postgres/01-roles-and-databases.sh` | bootstrap ролей и баз `forgejo` / `glitchtip` | | `ops/backup-forgejo.sh` | автоопределение контейнера с живой базой `forgejo` | | `ops/split-infra-postgres.sh` | перенос данных, dry-run по умолчанию | | `.forgejo/workflows/deploy-infra.yml` | подхват конфиг-дрейфа `infra-postgres`, но только если контейнер уже существует | | `ops/gendesign-backup-forgejo.default.example` | `PG_CONTAINER` в конфиге бэкапа | ## Две защиты, которые стоит отревьюить отдельно **Healthcheck проверяет не приём соединений, а наличие обеих баз.** Иначе «отравленный том» — когда bootstrap упал уже после инициализации PGDATA и потому больше никогда не запустится — выглядел бы здоровым, а обнаружился бы в ночь переезда как `role "forgejo" does not exist`. **`backup-forgejo.sh` при двух кандидатах останавливается с exit 1, а не выбирает первого.** Это ровно окно миграции, когда база лежит в обоих кластерах: молча выбрать один — с вероятностью 1/2 бэкапить труп, а порог `MIN_DB_DUMP_BYTES` протухший дамп не поймает, он нормального размера. ## `ops/split-infra-postgres.sh` Dry-run по умолчанию, `--apply` для реального прогона. Останавливает писателей, снимает число строк, восстанавливает **целевой ролью** (суперюзер забрал бы таблицы себе и выдал `permission denied` уже после переключения), сверяет построчные карты из `pg_class`. Ревью нашло два сценария бесшумной потери, оба закрыты: 1. Писатель с несовпавшим именем больше не пропускается молча — имя контейнера Forgejo это догадка, его compose лежит вне репозитория. Пропуск теперь только явным `--allow-missing-writer`. 2. `--force-restore` отказывается работать, если переключение уже сделано (читает `app.ini` и конфиг GlitchTip) — иначе затёр бы боевой новый кластер старым дампом, и сверка подтвердила бы успех. Дампы под `umask 077` + `chmod 600`: в дампе forgejo хеши паролей и access-токены. ## Test plan Проверено на живом Docker 29.5.3, не по YAML: - [x] матрица профилей через `docker compose config`: сегодняшний рендер Beget байт-в-байт идентичен `main` - [x] healthcheck исполнен внутри `postgres:16-alpine` — обе базы → `0`, после `DROP DATABASE` одной → `1` - [x] bootstrap реально завёл роли и базы; пароль со спецсимволами логинится; в `docker logs` его нет - [x] `bash -n` на обоих скриптах - [ ] ручной прогон `ops/split-infra-postgres.sh` без `--apply` на Beget — до окна переезда - [ ] прогон с `--apply`, проверка веба Forgejo и приёма событий GlitchTip ## Ручное переключение (после мержа, на VM) 1. поднять кластер: профиль `infra` + `INFRA_PG_PASSWORD` 2. `ops/split-infra-postgres.sh` — сначала без флагов, потом `--apply` 3. Forgejo `app.ini`: `HOST = infra-postgres:5432` 4. GlitchTip: `GLITCHTIP_DB_HOST=infra-postgres` 5. конфиг бэкапа: `PG_CONTAINER=gendesign-infra-postgres` Refs #3061, #3057, #3075, #2989, #2203
lekss361 added 1 commit 2026-08-24 16:29:51 +00:00
feat(ops): лёгкий Postgres на Beget под forgejo и glitchtip
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
7d98a674c9
Переезд ломает сам Forgejo. Базы forgejo (265 МБ) и glitchtip (729 МБ)
физически лежат внутри gendesign-postgres-1, который уезжает целиком вместе
с 15 ГБ Site Finder. В момент переезда легли бы веб, git по HTTP и Actions —
то есть CI, из которого мы деплоим, ровно тогда, когда он нужнее всего.
Отдельно обидно, что Forgejo заявлен компенсацией риска «всё в одном
аккаунте Selectel»: падая вместе с переездом, он эту роль не выполняет.

Владелец выбрал вариант А — отдельный лёгкий кластер на Beget. PostGIS тут
не нужен, обе схемы обычные реляционные: postgres:16-alpine против
postgis/postgis:16-3.4 ради одного гигабайта данных.

Мерж и деплой ничего не меняют. Сервис под профилем "infra", которого нет
ни в одном окружении на Beget, поэтому docker compose его не видит;
GLITCHTIP_DB_HOST имеет дефолт postgres, то есть строка подключения
GlitchTip рендерится байт-в-байт как сегодня. Переключение — правка одной
переменной на VM после переноса данных, откат — правка обратно.

Первая версия этого не держала: сервис стоял в профиле glitchtip, который
на Beget уже активен, и мерж поднял бы кластер с пустым INFRA_PG_PASSWORD
в вечный restart-loop при зелёном деплое. Поймано ревью.

Healthcheck проверяет не приём соединений, а наличие ОБЕИХ баз. Иначе
отравленный том — когда bootstrap-скрипт упал уже после инициализации
PGDATA и больше никогда не запустится — выглядел бы здоровым, а
обнаружился бы в ночь переезда как «role forgejo does not exist».

backup-forgejo.sh больше не хардкодит контейнер: ищет тот, где база forgejo
реально есть, и при двух кандидатах останавливается с exit 1. Это ровно
окно миграции, когда база лежит в обоих: молча выбрать один означает с
вероятностью 1/2 бэкапить труп, а порог MIN_DB_DUMP_BYTES протухший дамп
не поймает — он нормального размера.

ops/split-infra-postgres.sh — перенос: dry-run по умолчанию, снимок числа
строк после останова писателей, восстановление целевой ролью (суперюзер
забрал бы таблицы себе и выдал permission denied уже после переключения),
сверка построчных карт из pg_class. Ревью нашло два сценария бесшумной
потери, оба закрыты: писатель с несовпавшим именем больше не пропускается
молча (имя контейнера Forgejo — догадка, его compose вне репозитория), а
--force-restore отказывается работать, если переключение уже сделано.
Дампы под umask 077 и chmod 600: в дампе forgejo хеши паролей и токены.

Проверено на живом Docker, не по YAML: матрица профилей через compose
config, healthcheck исполнен в postgres:16-alpine (обе базы → 0, после DROP
одной → 1), bootstrap реально завёл роли и базы, пароль со спецсимволами
логинится, в docker logs его нет.

Refs #3061, #3057, #3075, #2989, #2203
lekss361 merged commit be2e07d9c3 into main 2026-08-24 16:31:13 +00:00
lekss361 deleted branch fix/3061-infra-postgres-split 2026-08-24 16:31:14 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3080
No description provided.