От образа до готового сервиса
Оглавление · 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.