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

XI. DevOps

От образа до готового сервиса

Оглавление · Linux и SSH · Compose проекта · Деплой

Задача: различать артефакт, запущенный процесс, готовность операции и проверенный релиз. Эта глава объясняет модель; реальные команды публикации — в главе о деплое.

Образ, контейнер, данные

Образ задаёт файловую систему и настройки запуска. Контейнер — экземпляр запуска с собственным writable layer, процессами и сетевым окружением. Несколько контейнеров из одного образа могут иметь разные переменные и разные данные. Linux-контейнер использует ядро хоста; на macOS Docker обычно запускает их в Linux VM. Запуск контейнеров.

Исходник схемы
flowchart LR
  Commit["Исходники и lockfile"] --> CI["CI: проверки и сборка"]
  CI --> Image["Образ в GHCR: digest"]
  Image --> Container["Контейнер на VPS"]
  Config["Конфигурация и секреты"] --> Container
  Container --> Volume["Постоянные данные: volumes"]
  Container --> Probe["Проверка нужной операции"]

Digest идентифицирует образ по содержимому, tag — имя, которое можно переназначить. Digest приложения не фиксирует состояние PostgreSQL или секреты. В Atmanki web/Strapi получают digest сборки; инфраструктурные образы задаются тегами веток. Поэтому воспроизводимость релиза оцениваем по всему составу, а не одному SHA.

Writable layer исчезает при удалении контейнера. Named volume живёт отдельно; bind mount показывает путь хоста внутри контейнера. Пересоздание приложения не должно удалять данные CMS. Volume не является резервной копией: ошибка записи или потеря диска может затронуть его так же, как обычный каталог. Хранение контейнера. Не используйте down -v как обычный способ «перезапустить сайт».

Внутри контейнера localhost обозначает сам контейнер. В общей Compose-сети приложение обращается к сервисам по именам: strapi:1337, postgres:5432, s3:3900. Публикация порта на хосте нужна для доступа снаружи, а не для этого внутреннего пути. Например, локальный Strapi привязан к 127.0.0.1 хоста; внешний вход в production организует Caddy. Подробности — в инфраструктуре.

У наблюдений разная сила

Обозначим L(t) — процесс жив, H(t) — выбранный probe успешен, R(op,t) — операция op доступна в момент t. Это предикаты наблюдения, не постоянные свойства образа. L(t) не влечёт R(op,t). H(t) подтверждает только контракт probe; между проверкой и следующим запросом состояние может измениться.

НаблюдениеЧто подтверждаетЧего недостаточно для вывода
Контейнер runningОсновной процесс не завершилсяПриложение принимает запросы
Порт слушаетсяЕсть сокет для подключенийЗапрос завершится успешно
Локальный HTTP probeКонкретный endpoint ответилДоступ снаружи через DNS/TLS/Caddy
Чтение CMSПроверенный путь к данным работаетВсе записи и медиа перенесены правильно
Проверка пользовательского сценарияЭтот сценарий выполнен на этом релизеВсе возможные сценарии и будущая доступность

Liveness спрашивает, способен ли процесс продолжать работу. Readiness спрашивает, можно ли сейчас направлять на него нужную нагрузку. Startup probe отделяет медленный старт от сбоя уже работающего приложения. Это понятия проектирования; наш Compose не вводит три независимых механизма автоматически.

Healthcheck Docker выставляет starting, healthy или unhealthy по результатам команды с интервалом, timeout и retries. Exit 0 означает успех, exit 1 — неуспех. Это измерение во времени: слишком короткий timeout даёт ложные сбои, слишком длинный задерживает обнаружение. Docker HEALTHCHECK.

Не проверяйте удалённую БД в liveness без понимания последствий: перезапуск приложения не чинит недоступную БД и может увеличить нагрузку. Для readiness зависимость может быть существенна; для страниц, уже сгенерированных Next, её отсутствие может иметь другие последствия. Контракт выбирают для операции.

Зависимости запуска — граф

В Atmanki зависимости service_healthy образуют ориентированный граф:

Исходник схемы
flowchart LR
  Postgres["PostgreSQL: pg_isready"] --> Strapi["Strapi: /_health"]
  Garage["Garage: status"] --> Strapi
  Strapi --> Web["Next: /api/health"]
  Web --> Caddy["Caddy: внешний вход"]
  Strapi --> Caddy
  Garage --> Caddy

Стрелка означает ожидание зависимости при запуске, не направление всех HTTP-запросов. Compose ждёт успешного healthcheck для condition: service_healthy. Обычный порядок создания без этого условия не доказывает готовность. Порядок запуска Compose.

После старта зависимость может упасть. Граф не превращает Compose в постоянный координатор готовности. Вложенный depends_on.restart: true относится к явным операциям Compose с зависимостью; это другой механизм, чем restart: unless-stopped у самого сервиса. Последний управляет повторным запуском после завершения процесса и не перезапускает контейнер только потому, что healthcheck стал unhealthy. Restart policies Docker.

В этом checkout Caddy не имеет отдельного healthcheck. Поэтому успешный up --wait не является проверкой HTTPS-входа: release.sh делает её дополнительно.

Что именно проверяет Atmanki

Источники — compose.yaml, release.sh и маршруты приложения.

  • pg_isready проверяет приём соединений PostgreSQL. Это не чтение таблиц CMS под её пользователем и не проверка результата миграций.
  • Garage status проверяет состояние узла; это не загрузка и чтение каждого объекта.
  • Strapi /_health отвечает за probe CMS; наличие опубликованного учебного контента подтверждается отдельной процедурой импорта.
  • /api/health возвращает status: ok. Он не обращается к CMS и не валидирует новости или фотографии.
  • /api/content-health без кеша читает site, news, events, pages, faq и partners через CMS-адаптер. При ошибке отвечает 503; при успехе — ready и количества записей. Он не устанавливает ожидаемые количества, не скачивает медиа и не сравнивает данные с источником.

Последний endpoint полезнее для проверки пути к CMS, но ready с нулём новостей сам по себе не доказывает корректный перенос. Для этого нужны критерии содержимого из главы CMS. Внешний HTTPS probe проверяет путь через домен и proxy с точки запуска команды, а не доступность из каждого региона.

Остановка и повторный запуск

По умолчанию Docker stop отправляет SIGTERM основному процессу, ждёт ограниченное время и при необходимости отправляет SIGKILL. Начальный сигнал и timeout можно настроить. Docker stop.

Обработчик сигнала должен получить его: shell-обёртка, PID 1 и передача сигналов дочернему процессу влияют на результат. Корневой Dockerfile запускает Node через exec-форму CMD; Strapi Dockerfile запускает npm run start. Сам факт exec-формы не доказывает завершение всех активных операций. Здесь не добавляем собственный supervisor или обработчик вместо framework; проверка остановки — отдельный опыт.

Повторный запуск — переход к новому процессу с новым состоянием памяти. Запрос, прерванный остановкой, мог уже изменить БД. Автоматический retry допустим по контракту операции, а не потому, что контейнер снова healthy.

Релиз — несколько независимых состояний

Исходник схемы
flowchart TD
  Built["CI выпустил образы"] --> Pulled["VPS получил образы"]
  Pulled --> CMS["CMS запущена и подготовлена"]
  CMS --> Gate{"CONTENT_READY?"}
  Gate -->|нет| Prepared["prepared: старый frontend остаётся"]
  Gate -->|да| Start["Запустить приложения; ждать probes"]
  Start --> Verify["HTTPS health и content-health"]
  Verify -->|успех| Current["Переключить current"]
  Verify -->|сбой| Rollback["Попытка вернуть прежние web/Caddy"]

Это упрощённая схема текущего release.sh; ошибки более ранних команд также прерывают скрипт. flock сериализует запуск релизов на VPS. CONTENT_READY=false позволяет успешно подготовить CMS, сохранив старый frontend: exit 0 не всегда означает переключение сайта. Ветка возврата прежних web/Caddy не откатывает БД, CMS-изображение и изменения данных; при первом релизе прежнего приложения может не быть. См. границы отката.

Разделяйте в отчёте: код закоммичен; CI прошёл; образы опубликованы; релиз переключён; пользовательский сценарий проверен. Каждое утверждение требует своего свидетельства. Чтение скрипта показывает намерение, но не свежий результат его выполнения на VPS.

Упражнение: контракт готовности

Ничего не останавливая на production, заполните таблицу для четырёх случаев: PostgreSQL ещё стартует; CMS отвечает, но токен неверный; Next отвечает, но Caddy настроен на другой домен; CMS отдаёт записи, но объект фотографии удалён. Для каждого укажите, какие probes могут пройти, какой пользовательский сценарий сломается и какая проверка обнаружит это. Используйте точный код endpoints.

В изолированной учебной среде затем можно сравнить предположение с наблюдением. Не переносите такой эксперимент на VPS по умолчанию. Следующая глава — конкретные образы, сети и тома Atmanki.