Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
7d711425fd
|
|||
|
e03a4c417d
|
@@ -8,6 +8,60 @@
|
||||
|
||||
---
|
||||
|
||||
## Шаг 7 — `run-app` тег + унификация registry (2026-05-22, выполнено)
|
||||
|
||||
По итогам аудита подготовительных задач выявлены и закрыты две
|
||||
несостыковки:
|
||||
|
||||
### 7a. Пропущенный `run-app` тег в remembos
|
||||
|
||||
В `playbook-remembos.yml:73` была задача
|
||||
`Restart docker compose services if config changed but not
|
||||
docker-compose.yml` (условный рестарт через `state: restarted`,
|
||||
триггер — изменение `config.toml` без изменения `docker-compose.yml`),
|
||||
у неё не было тега `run-app`. На cutover'е при
|
||||
`--skip-tags run-app` основной запуск пропустился бы (правильно), а
|
||||
эта условная задача всё равно сработала бы (потому что её `when:`
|
||||
истинно при первом деплое — конфиг создаётся), попыталась бы
|
||||
рестартануть несуществующий compose-стек и упала. Тег добавлен.
|
||||
|
||||
### 7b. Унификация `registry_url` в docker_login
|
||||
|
||||
`playbook-homepage.yml` и `playbook-transcriber.yml` использовали
|
||||
хардкод `registry_url: "cr.yandex"`, а `playbook-remembos.yml` —
|
||||
`'{{ yc_container_registry }}'` из vault. Привёл к одному виду:
|
||||
теперь во всех трёх — `"{{ yc_container_registry }}"` из vault.
|
||||
|
||||
`docker_registry_prefix` в `vars/homepage.yml` и `vars/transcriber.yml`
|
||||
не трогал — там полный image-prefix вида `cr.yandex/<org-id>`,
|
||||
это отдельная концепция (есть отдельный vault-var
|
||||
`yc_container_registry_repository`, используемый в
|
||||
`files/remembos/docker-compose.template.yml`). Если позже захочется
|
||||
унифицировать целиком — это отдельная итерация.
|
||||
|
||||
### Аудит бэкапов: gap'ы по `caddyproxy`, `remembos`, `transcriber`
|
||||
|
||||
Эти три приложения имеют состояние в `data_dir`, но не имеют ни
|
||||
`backup.template.sh`, ни ansible-генерируемого `backup-targets`.
|
||||
Для миграции это закрывается через **rsync** на cutover'е — данные
|
||||
переносятся напрямую, без зависимости от restic-снапшотов:
|
||||
|
||||
- `caddyproxy/data/` — TLS-сертификаты Let's Encrypt (важно, чтобы
|
||||
не упереться в rate-limit LE при перевыпуске ~17 сертов).
|
||||
- `remembos/data/` — user data (memos-токен, telegram tokens).
|
||||
- `transcriber/data/` — пользовательские транскрипции.
|
||||
|
||||
Это означает: на этапе rsync (шаг 4 cutover'а в плане) **нельзя**
|
||||
полагаться только на restic-restore — для этих трёх апов rsync —
|
||||
единственный канал. Для остальных приложений (которые имеют
|
||||
`backup.sh` или `backup-targets`) можно при необходимости использовать
|
||||
restic как фолбэк, но rsync всё равно остаётся основным методом.
|
||||
|
||||
Долгосрочно — добавить им backup-механизм отдельной итерацией после
|
||||
миграции. Сейчас это сверх сферы.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 6 — `vars/vars.yml` загружается во всех плейбуках (2026-05-22, выполнено)
|
||||
|
||||
Сегодняшний коммит `8378f0e` («Migration: expose some public vars»)
|
||||
|
||||
+50
-29
@@ -284,28 +284,33 @@ sudo /usr/local/sbin/backup-all.py 2>&1 | tee /tmp/final-backup.log
|
||||
|
||||
### Шаг 2. Остановить все приложения на источнике
|
||||
|
||||
Аккуратно остановить контейнеры каждого приложения (через
|
||||
`docker compose down` от соответствующего пользователя или одним
|
||||
проходом):
|
||||
Останавливаем docker-демон целиком — это атомарно гасит все
|
||||
контейнеры за один вызов, не зависит от текущего списка приложений
|
||||
и шлёт корректный SIGTERM (с грейс-периодом ~15 сек) каждому, что
|
||||
функционально эквивалентно `docker compose down` по всем стекам.
|
||||
|
||||
```bash
|
||||
inv ssh
|
||||
for user in caddyproxy authelia netdata miniflux rssbridge wakapi \
|
||||
dozzle transcriber wanderer memos gitea outline homepage gramps \
|
||||
calibre remembos apprise tuwunel goaccess; do
|
||||
sudo -iu "$user" bash -c "cd /mnt/applications/$user && docker compose down"
|
||||
done
|
||||
sudo systemctl stop docker.service docker.socket
|
||||
sudo systemctl disable docker.service docker.socket # страховка от автостарта при ребуте
|
||||
sudo systemctl stop cron # чтобы ночной backup-cron не побежал
|
||||
```
|
||||
|
||||
(Можно завести вспомогательный плейбук `playbook-shutdown-all.yml`,
|
||||
если такое будет часто.)
|
||||
Финальный бэкап (шаг 1) **обязательно** должен пройти до этого
|
||||
момента — `backup-all.py` запускает скрипты приложений, которые
|
||||
делают `docker compose exec ... pg_dump ...`; без работающего
|
||||
daemon это сломается.
|
||||
|
||||
Проверить `docker ps`, что пусто. Снять флаги cron на бэкап (чтобы
|
||||
финальный backup не побежал во время миграции):
|
||||
`disable` — страховка: если по какой-то причине старая машина
|
||||
перезагрузится во время rsync (или мы вернёмся на источник для
|
||||
проверки/отката), docker не поднимется автоматически и сервисы
|
||||
не начнут писать в данные, которые мы уже считаем «фиксированной
|
||||
копией». В случае отката — `enable` + `start` обратно.
|
||||
|
||||
```bash
|
||||
sudo systemctl stop cron
|
||||
```
|
||||
Проверить, что `docker ps` сейчас отвечает «daemon not running»
|
||||
(или вернёт пустой список — зависит от того, как `inv ssh` пройдёт
|
||||
до/после стопа). Если нужно убедиться, что контейнеры реально
|
||||
ушли — `ps auxf | grep -E "containerd|docker" | grep -v grep`.
|
||||
|
||||
### Шаг 3. Раскатать инфраструктуру на target БЕЗ запуска приложений
|
||||
|
||||
@@ -323,7 +328,9 @@ uv run ansible-playbook -i timeweb.yml --diff \
|
||||
Цель — после этого на target есть:
|
||||
|
||||
- Корректные uid/gid для всех приложений.
|
||||
- Каталоги `/mnt/applications/<app>/{data,config,backups}`.
|
||||
- Каталоги `/srv/applications/<app>/{data,config,backups}` (на
|
||||
Timeweb дефолт изменён с `/mnt/applications`; см.
|
||||
[журнал шаг 5](timeweb-migration-log.md)).
|
||||
- Шаблоны `docker-compose.yml` и application-конфиги — отрендерены
|
||||
и лежат на месте.
|
||||
- Docker и сети созданы.
|
||||
@@ -331,34 +338,48 @@ uv run ansible-playbook -i timeweb.yml --diff \
|
||||
|
||||
### Шаг 4. Перенос данных
|
||||
|
||||
Два варианта.
|
||||
Пути меняются: на YC данные лежат в `/mnt/applications/<app>`, на
|
||||
Timeweb — в `/srv/applications/<app>`. Rsync делает remap сам
|
||||
(потому что мы указываем источник и приёмник явно). Для трёх
|
||||
приложений без backup-механизма (`caddyproxy`, `remembos`,
|
||||
`transcriber`) rsync — **единственный** канал переноса, restic
|
||||
для них не альтернатива.
|
||||
|
||||
**Вариант A — rsync напрямую (быстрее).** С target-машины тянем
|
||||
данные со старой:
|
||||
**Вариант A — rsync напрямую (основной путь).** С target-машины
|
||||
тянем данные со старой:
|
||||
|
||||
```bash
|
||||
sudo rsync -aAX --info=progress2 --delete \
|
||||
--exclude='lost+found' \
|
||||
major@158.160.46.255:/mnt/applications/ \
|
||||
/mnt/applications/
|
||||
/srv/applications/
|
||||
```
|
||||
|
||||
`-aAX` сохраняет ACL/xattrs и uid/gid (численные значения).
|
||||
Численные uid/gid на target совпадают с источником, потому что
|
||||
плейбуки на обеих машинах создают пользователей с одинаковыми
|
||||
явно заданными `app_owner_uid`/`gid`.
|
||||
|
||||
Каждое приложение можно тянуть отдельно — удобнее наблюдать
|
||||
прогресс и можно частично пересинхронизировать в случае ошибок.
|
||||
|
||||
**Вариант B — restore из restic.** Если по сети источник недоступен
|
||||
(например, IP уже закрыли) или хочется проверить, что бэкапы вообще
|
||||
рабочие — восстанавливаемся из YC S3:
|
||||
прогресс и можно частично пересинхронизировать в случае ошибок:
|
||||
|
||||
```bash
|
||||
sudo /usr/local/sbin/restic-shell.sh
|
||||
restic restore latest --target /mnt/applications --path /mnt/applications
|
||||
sudo rsync -aAX --info=progress2 --delete \
|
||||
major@158.160.46.255:/mnt/applications/gitea/ \
|
||||
/srv/applications/gitea/
|
||||
```
|
||||
|
||||
Рекомендую **A с фолбэком на B**: rsync быстрее и точнее (с
|
||||
точностью до секунды), restic держим как страховку.
|
||||
**Вариант B — restore из restic (страховка).** Если по сети
|
||||
источник недоступен или хочется проверить, что бэкапы вообще
|
||||
рабочие. Подробный пример (с учётом смены `/mnt` → `/srv`) — в
|
||||
[журнале миграции, шаг 5](timeweb-migration-log.md).
|
||||
|
||||
Для `caddyproxy`, `remembos`, `transcriber` использовать B
|
||||
**нельзя** — у них нет архивации, в restic-снапшоте данных просто
|
||||
нет. Только A.
|
||||
|
||||
Рекомендую **A как основной метод**, B держим как страховку
|
||||
для приложений, у которых есть восстановимый снапшот.
|
||||
|
||||
### Шаг 5. Запуск приложений на target
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
- name: "Login to Yandex Container Registry"
|
||||
community.docker.docker_login:
|
||||
registry_url: "cr.yandex"
|
||||
registry_url: "{{ yc_container_registry }}"
|
||||
username: "oauth"
|
||||
password: "{{ yc_oauth_token }}"
|
||||
|
||||
|
||||
@@ -77,3 +77,5 @@
|
||||
when:
|
||||
- config_file_result.changed
|
||||
- not docker_compose_file_result.changed
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
- name: "Login to Yandex Container Registry"
|
||||
community.docker.docker_login:
|
||||
registry_url: "cr.yandex"
|
||||
registry_url: "{{ yc_container_registry }}"
|
||||
username: "oauth"
|
||||
password: "{{ yc_oauth_token }}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user