195 lines
9.7 KiB
Markdown
195 lines
9.7 KiB
Markdown
# Журнал миграции в Timeweb
|
||
|
||
Хронология фактического переезда. План и архитектурные решения —
|
||
в [timeweb.md](timeweb.md). Здесь только то, что реально сделано,
|
||
с датами.
|
||
|
||
Новые записи — сверху.
|
||
|
||
---
|
||
|
||
## Шаг 5 — переезд default application_dir на /srv (2026-05-22, выполнено)
|
||
|
||
`/mnt` по FHS — место для точек монтирования внешних дисков; на
|
||
системном диске Timeweb (фаза 1) это семантически неверно. Поменяли
|
||
дефолт на `/srv/applications` (FHS: «data for services provided by
|
||
this system»), для текущего YC-сервера сделали override в инвентаре.
|
||
|
||
Изменения:
|
||
|
||
- `vars/vars.yml` — `application_dir: "/srv/applications"`
|
||
(комментарий обновлён).
|
||
- `production.yml` — у хоста `server` добавлен override
|
||
`application_dir: "/mnt/applications"`.
|
||
- `playbook-system.yml` — добавлен `vars/vars.yml` в `vars_files`,
|
||
захардкоженный `/mnt/applications` в задачах
|
||
`Create directory for mount` и `Mount external storages` заменён
|
||
на `{{ application_dir }}`.
|
||
- `playbook-remove-user-and-app.yml` — то же самое (`vars/vars.yml`
|
||
в `vars_files` + `{{ (application_dir, user_name) | path_join }}`).
|
||
- `tasks.py` — новый helper `_application_dir()` читает значение
|
||
сначала из inventory (override), затем из `vars/vars.yml`. `login_as_app`
|
||
больше не содержит `/mnt/applications`.
|
||
|
||
Что остаётся хардкодом — только `/mnt/applications` в `production.yml`
|
||
как override, и это правильно.
|
||
|
||
На Timeweb-инвентаре (когда появится) можно либо не задавать
|
||
`application_dir` вовсе (применится дефолт `/srv/applications`), либо
|
||
задать явно — для читаемости.
|
||
|
||
Проверить прогоном `inv pl -- system` на текущем сервере (Yandex
|
||
Cloud) — ничего не должно поменяться, потому что inventory override
|
||
возвращает `/mnt/applications` и mount всё ещё включён. Diff ожидается
|
||
пустой.
|
||
|
||
### Восстановление restic-снапшотов после смены путей
|
||
|
||
Старые снапшоты записаны с путями `/mnt/applications/<app>`. На
|
||
Timeweb данные должны лежать в `/srv/applications/<app>`. У restic
|
||
нет встроенного «remap path» при restore, поэтому делается в два
|
||
шага: восстановить во временный каталог, затем `rsync` на новое
|
||
место с сохранением uid/gid (приложения уже созданы playbook'ом с
|
||
теми же uid/gid, см. шаг про подготовку target).
|
||
|
||
Пример — восстановить gitea на Timeweb-машине:
|
||
|
||
```bash
|
||
sudo /usr/local/sbin/restic-shell.sh
|
||
|
||
# Распакуем нужную поддиректорию во временный каталог
|
||
restic restore latest \
|
||
--target /tmp/restic-restore \
|
||
--include /mnt/applications/gitea
|
||
|
||
# Перенесём данные на новый путь, сохранив владельца/группу/ACL/xattr
|
||
sudo rsync -aAX --info=progress2 \
|
||
/tmp/restic-restore/mnt/applications/gitea/ \
|
||
/srv/applications/gitea/
|
||
|
||
sudo rm -rf /tmp/restic-restore
|
||
```
|
||
|
||
Несколько приложений за один проход:
|
||
|
||
```bash
|
||
restic restore latest \
|
||
--target /tmp/restic-restore \
|
||
--include /mnt/applications/gitea \
|
||
--include /mnt/applications/outline \
|
||
--include /mnt/applications/miniflux
|
||
|
||
for app in gitea outline miniflux; do
|
||
sudo rsync -aAX --info=progress2 \
|
||
"/tmp/restic-restore/mnt/applications/$app/" \
|
||
"/srv/applications/$app/"
|
||
done
|
||
sudo rm -rf /tmp/restic-restore
|
||
```
|
||
|
||
Альтернатива через `restic mount` (если не хочется промежуточной
|
||
копии — данные мапятся как FUSE-FS):
|
||
|
||
```bash
|
||
sudo mkdir -p /mnt/restic-snapshots
|
||
restic mount /mnt/restic-snapshots &
|
||
sudo rsync -aAX \
|
||
/mnt/restic-snapshots/snapshots/latest/mnt/applications/gitea/ \
|
||
/srv/applications/gitea/
|
||
sudo fusermount -u /mnt/restic-snapshots
|
||
```
|
||
|
||
После переезда новые снапшоты будут записываться уже с путями
|
||
`/srv/applications/<app>` — никаких трюков для текущих бэкапов не
|
||
нужно.
|
||
|
||
---
|
||
|
||
## Шаг 4 — условное монтирование внешнего диска (2026-05-22, выполнено)
|
||
|
||
Задача `Mount external storages` в `playbook-system.yml` теперь
|
||
выполняется только при включённом флаге `mount_external_storage`
|
||
(default `false`). Сам UUID диска оставлен захардкоженным в
|
||
плейбуке — параметризовать не стали, потому что для Timeweb (фаза 1)
|
||
монтирование вообще не нужно, а для фазы 2 пока неизвестно, какой
|
||
UUID получится у второго диска.
|
||
|
||
Изменения:
|
||
|
||
- `playbook-system.yml` — у задачи mount добавлен
|
||
`when: mount_external_storage | default(false) | bool`.
|
||
- `production.yml` (инвентарь YC) — у хоста `server` добавлен
|
||
`mount_external_storage: true`, чтобы текущее поведение
|
||
сохранилось.
|
||
|
||
В будущем `timeweb.yml` просто не будет задавать эту переменную —
|
||
mount пропустится, `/mnt/applications` останется обычной директорией
|
||
на системном диске.
|
||
|
||
На фазе 2 (подключение медленного диска в Timeweb) UUID в
|
||
`playbook-system.yml` придётся поменять и включить флаг — это
|
||
осознанный шаг, не автоматизировано.
|
||
|
||
Проверено прогоном `inv pl -- system` на текущем сервере (Yandex
|
||
Cloud) — задача mount по-прежнему выполняется, `/mnt/applications`
|
||
смонтирован, изменений нет.
|
||
|
||
---
|
||
|
||
## Шаг 3 — переключение auth на cr.yandex (2026-05-22, выполнено)
|
||
|
||
Заменена аутентификация в Yandex Container Registry с YC-metadata
|
||
service на OAuth-token из vault.
|
||
|
||
Изменения:
|
||
|
||
- `files/yandex-docker-registry-auth.sh` — **удалён**.
|
||
- `playbook-homepage.yml` — задача `ansible.builtin.script:
|
||
yandex-docker-registry-auth.sh` заменена на
|
||
`community.docker.docker_login` с `username: oauth`, `password:
|
||
"{{ yc_oauth_token }}"`.
|
||
- `playbook-transcriber.yml` — то же самое.
|
||
|
||
Локальные push-плейбуки (`playbook-homepage-registry.yml`,
|
||
`playbook-transcriber-registry.yml`) не трогал — там нет auth-задачи
|
||
в принципе, локальный docker аутентифицируется вручную
|
||
(`yc container registry configure-docker` или `docker login`).
|
||
Если позже захочется унифицировать — можно добавить тот же
|
||
`docker_login` с `delegate_to: 127.0.0.1`.
|
||
|
||
Проверено прогоном `inv pl -- homepage` и `inv pl -- transcriber` на
|
||
текущем сервере (Yandex Cloud) — ошибок нет, контейнеры работают.
|
||
Значит и на Timeweb заработает (единственная разница — исходящий IP,
|
||
а OAuth-токен в YC принимается извне).
|
||
|
||
---
|
||
|
||
## Шаг 2 — OAuth-token для cr.yandex (2026-05-22, выполнено)
|
||
|
||
В `vars/secrets.yml` добавлена (или обновлена) переменная
|
||
`yc_oauth_token` со свежим OAuth-токеном Яндекса. Токен будет
|
||
использоваться для логина в `cr.yandex` с новой машины Timeweb
|
||
(вместо текущего скрипта `files/yandex-docker-registry-auth.sh`,
|
||
который завязан на YC metadata service `169.254.169.254` и
|
||
работает только внутри YC).
|
||
|
||
Сам код переключения на `community.docker.docker_login` пока не
|
||
вносится — это следующая итерация. Сейчас токен просто положен в
|
||
vault, чтобы не делать этого в день cutover'а под прессом.
|
||
|
||
---
|
||
|
||
## Шаг 1 — снижение TTL DNS (2026-05-22, выполнено)
|
||
|
||
В админке Yandex 360 для зоны `vakhrushev.me` уменьшен TTL
|
||
A-записей с **21 600 с (6 ч)** до **1 200 с (20 мин)**. Это даёт
|
||
запас по времени на распространение изменений после смены IP в
|
||
день cutover'а — старые кэширующие резолверы перестанут отдавать
|
||
старый адрес максимум через 20 минут (вместо 6 часов).
|
||
|
||
Делается **заранее**, потому что само снижение TTL тоже
|
||
распространяется по кэшам по правилам старого TTL — то есть после
|
||
правки нужно подождать ≥ 6 часов, чтобы новое значение TTL само
|
||
успело прижиться. Раньше cutover'а нужно сделать с большим
|
||
запасом — день в день не сработает.
|