9.7 KiB
Журнал миграции в Timeweb
Хронология фактического переезда. План и архитектурные решения — в 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добавлен overrideapplication_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-машине:
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
Несколько приложений за один проход:
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):
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'а нужно сделать с большим запасом — день в день не сработает.