Учебное руководство
Главы руководства
На этой странице

Эксплуатация, резервные копии и восстановление

Оглавление · Далее: проверки

Примеры production-команд ниже выполняются на VPS в Bash пользователем gheilt-deploy или уполномоченным администратором. Нельзя заменить их локальным docker compose и ожидать, что команда затронет сервер.

Удобная оболочка для текущего релиза

release_dir=$(readlink -f /opt/gheilt/current)
test -n "$release_dir" && test -f "$release_dir/release.env"
compose=(docker compose --project-name gheilt \
  --env-file /opt/gheilt/.env --env-file "$release_dir/release.env" \
  -f "$release_dir/compose.yaml" -f "$release_dir/compose.production.yaml")
dc() { "${compose[@]}" "$@"; }

Дальнейшие команды dc предполагают эту оболочку. Если current ещё нет, используйте конкретный release directory из неудачного первого запуска.

Статус и логи

dc ps
dc logs --tail=100 web functions
dc logs --tail=100 strapi hatchet postgres
docker stats --no-stream
free -m
df -h

Логи могут содержать payload CMS. Не отправляйте их целиком в публичные issue. docker stats помогает увидеть рост памяти, но не заменяет длительные метрики: Prometheus/Grafana/alerting в проекте не настроены.

curl --fail https://gheilt.mxsource.xyz/api/health
curl --fail https://gheilt.mxsource.xyz/api/trpc/news.list

Эти проверки ничего не меняют в данных. Webhook smoke сбрасывает редакционный кеш Next.js; задача Hatchet при этом не создаётся. Health Hatchet может оставаться зелёным при неисправной обработке задач: нужно наблюдать реальный результат в воркере.

Применение новых настроек

Для изменения только кода используйте GitHub Actions. После изменения environment нужно пересоздать контейнеры: restart не перечитывает environment.

dc up -d --no-build --pull never --wait web functions

Команда использует уже загруженные образы. Наличие отсутствующего образа — повод восстановить доступ к registry через штатный pipeline, а не собирать всё на VPS. Перезапуск очереди/баз должен учитывать активные задачи и доступность приложений.

Что резервировать

ОбъектЗачем
/opt/gheilt/.envDB passwords, signing/encryption keys, token
База strapiЗаписи CMS и администраторы
База hatchetОпределения, очередь, состояние и история задач
gheilt_s3_metadataКаталог Garage: buckets, keys, объектные ссылки
gheilt_s3_dataБайты S3 объектов
gheilt_strapi_uploadsMedia-файлы, на которые ссылается CMS
gheilt_hatchet_configКлючи/конфигурация, связанные с токенами
PG globalsРоли; файл содержит чувствительные password hashes
Тома CaddyTLS-ключи/конфигурация; можно перевыпустить, но сохранение полезно
Release metadataКакая версия образов работала вместе с копией

Репозиторий не содержит автоматического backup scheduler, offsite-хранилища или retention policy; одноразовый migration backup проверяет PostgreSQL/Garage restore. Копия на том же VPS не спасает от потери VPS. Храните шифрованную копию отдельно и проверяйте восстановление.

Пример согласованной копии в окно обслуживания

Это процедура с временной недоступностью CMS/очереди. Сначала отрепетируйте её локально. Не выполняйте команды остановки автоматически ради чтения документации. Далее предполагаются Bash, определённый dc и уже работающий production:

set -euo pipefail
umask 077
exec 9>/opt/gheilt/deploy.lock
flock -n 9
backup_dir="/opt/gheilt/backups/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$backup_dir"
# EXIT пытается вернуть сервисы и при ошибке копирования.
trap 'dc up -d --no-build --pull never --wait --wait-timeout 240' EXIT
dc stop --timeout 60 caddy web strapi functions hatchet s3
cp /opt/gheilt/.env "$backup_dir/environment.env"
cp "$release_dir/release.env" "$backup_dir/release.env"
printf '%s\n' "$release_dir" > "$backup_dir/release-path.txt"
dc exec -T postgres pg_dump -U gheilt -d strapi -Fc > "$backup_dir/strapi.dump"
dc exec -T postgres pg_dump -U gheilt -d hatchet -Fc > "$backup_dir/hatchet.dump"
dc exec -T postgres pg_dumpall -U gheilt --globals-only > "$backup_dir/globals.sql"
for volume in s3_metadata s3_data strapi_uploads hatchet_config caddy_data caddy_config; do
  docker run --rm -v "gheilt_$volume:/source:ro" -v "$backup_dir:/backup" \
    alpine:3.23 tar -czf "/backup/$volume.tgz" -C /source .
done

Проверьте exit status и наличие архивов. pg_dump берёт согласованный snapshot каждой базы, но два отдельных dump не являются одним межбазовым snapshot. Остановка producers/worker уменьшает расхождения; таймаут остановки не гарантирует завершение произвольно долгой задачи. Caddy и Garage остановлены на время копирования: metadata SQLite и object files копируются согласованно. Изменяемые файлы работающего Garage архивировать нельзя.

EXIT trap поднимает сервисы. После завершения процесса/скрипта освобождается lock. Если выполняли пример интерактивно, выйдите из этого shell, чтобы освободить fd 9. Затем проверьте доступность приложений и доставьте зашифрованную копию вне хоста. Нельзя копировать активную папку PostgreSQL обычным tar вместо pg_dump.

Учебное восстановление базы без изменения production

На локальном Docker-стеке создайте новую базу с отдельным именем:

docker compose exec -T postgres createdb -U gheilt -O strapi learning_restore
docker compose exec -T postgres pg_restore -U gheilt --no-owner --role=strapi \
  --exit-on-error --single-transaction -d learning_restore < /path/to/strapi.dump
docker compose exec -T postgres psql -U gheilt -d learning_restore -c '\dt'

/path/to/strapi.dump — доступная локальная учебная копия, не приватная production- база. Если база уже существует, выберите другое имя. Не применяйте --clean к рабочей базе для обхода конфликта. Проверка \dt доказывает только наличие таблиц; нужна проверка данных и запуск отдельного экземпляра CMS на восстановленной базе.

Восстановление всего стека на новом изолированном хосте

  1. Подготовьте хост и Docker; остановите поступление новых изменений на время переключения. Не меняйте DNS, пока восстановление не проверено.
  2. Безопасно восстановите .env в /opt/gheilt/.env с правами 600 и release-файлы нужной версии. Получите соответствующие образы через авторизованный registry.
  3. Поднимите только PostgreSQL. Его init script создаст нужные роли/пустые базы с паролями из восстановленного .env. Не запускайте Strapi/Hatchet до restore.
  4. Восстановите custom-format dumps strapi и hatchet через pg_restore --no-owner с нужной ролью и --exit-on-error --single-transaction в пустые базы.
  5. Восстановите uploads и hatchet_config в отдельные свежие тома с теми же именами. Пример ниже распаковывает только uploads на новом хосте.
  6. Запустите тот же release, проверьте CMS, файлы, токены, worker connection и задачу.
  7. После проверки переключите трафик и создайте новую резервную копию.
# На новом хосте, когда текущий release и dc уже подготовлены:
dc up -d --no-build --pull never --wait postgres
dc exec -T postgres pg_restore -U gheilt --no-owner --role=strapi \
  --exit-on-error --single-transaction -d strapi < "$backup_dir/strapi.dump"
dc exec -T postgres pg_restore -U gheilt --no-owner --role=hatchet \
  --exit-on-error --single-transaction -d hatchet < "$backup_dir/hatchet.dump"
docker volume create gheilt_strapi_uploads
docker run --rm -v gheilt_strapi_uploads:/restore -v "$backup_dir:/backup:ro" \
  alpine:3.23 tar -xzf /backup/strapi_uploads.tgz -C /restore

По той же схеме восстановите hatchet_config и, если нужно, Caddy volumes. Здесь не показан автоматический скрипт полного восстановления: процедура требует проверки выбранной копии, версии PostgreSQL и совместимости релиза. globals.sql полезен для дополнительных ролей, но не исполняется вслепую поверх ролей, уже созданных init script. Восстановление очереди может вернуть старые задачи и привести к повтору внешних побочных эффектов; нужна идемпотентность обработчиков.

Ручной откат образов

Для отката только web/functions/Caddy на VPS в Bash выберите существующий release. CMS и S3 сохраняются; их откат выполняется отдельно после проверки данных:

rollback_dir=/opt/gheilt/releases/REPLACE_WITH_COMMIT_SHA
old_compose=(docker compose --project-name gheilt \
  --env-file /opt/gheilt/.env --env-file "$rollback_dir/release.env" \
  -f "$rollback_dir/compose.yaml" -f "$rollback_dir/compose.production.yaml")
exec 9>/opt/gheilt/deploy.lock
flock -n 9
"${old_compose[@]}" config --quiet
mapfile -t images < <("${old_compose[@]}" config --images)
docker image inspect "${images[@]}" >/dev/null
"${old_compose[@]}" up -d --no-deps --no-build --pull never --wait --wait-timeout 240 web functions caddy
curl --fail https://gheilt.mxsource.xyz/api/health
ln -sfn "$rollback_dir" /opt/gheilt/current.next
mv -Tf /opt/gheilt/current.next /opt/gheilt/current

Сначала замените SHA, проверьте совместимость и наличие образов. Это не откат базы. Для постоянного исправления измените Git/branch: следующий push main снова применит содержимое main. Не воспринимайте удачный health как полную проверку старого релиза.

Обновление зависимостей и освобождение диска

Обновляйте небольшими группами: workspace через pnpm, CMS через её npm lockfile, инфраструктуру через image tags. При major-обновлении PG нужен план миграции данных; замена 17-alpine на новый major поверх старого volume не является таким планом.

Сначала df -h, docker system df и список релизов. Не запускайте глобальный prune с volumes: можно потерять данные и образы для rollback. Retention/cleanup в проекте пока не автоматизированы.

Дополнение для Garage

Прежний пример backup покрывает PostgreSQL и local uploads, но после добавления S3 его недостаточно: отдельно нужны объекты gheilt_s3_data и metadata gheilt_s3_metadata, а также ключи из .env. Для согласованной cold-копии остановите Strapi и Garage; на это время media недоступны. Скопируйте оба тома, затем поднимите Garage, дождитесь health и поднимите CMS. Проверьте восстановление на отдельном экземпляре. Не снимайте работающую SQLite metadata обычным tar и не считайте копию на том же VPS защитой от потери VPS.

Проверяемая предмиграционная копия

release.sh вызывает scripts/deploy/backup-content.sh один раз перед первой новой CMS под deploy flock. Он сохраняет SQL, оба Garage volumes, old uploads, env и release refs в каталог 0700, восстанавливает SQL в отдельную scratch DB и Garage в отдельную сеть с отдельными volumes. Cleanup удаляет только созданные scratch объекты и возвращает прежние приложения. Маркер /opt/gheilt/content-migration.backup указывает на копию. Это проверка миграции, не планировщик ежедневных backups и не offsite backup.

Ручной повтор выполняйте в согласованное окно обслуживания с deploy lock. Новую CMS/data не откатывайте запуском старого Strapi image: используйте проверенную копию на изолированном стенде и отдельный план восстановления. Rollback web использует --no-deps и сохраняет CMS/S3. Не удаляйте volumes исходного проекта ради restore drill.

current хранит релиз web, cms-current — фактически работающий релиз CMS. Во время подготовки и частичного rollback они могут различаться; backup сохраняет обе ссылки и возвращает каждую компоненту по её собственной версии. Garage restore drill также читает контрольный объект и сравнивает байты, а не только проверяет bucket info.