Раздел 21 · Haskell

В прод: Docker, миграции, метрики

lead~25 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прод: Docker, миграции, метрики

Сервис написан и протестирован. Последний шаг это вывести его в мир так, чтобы деплой был предсказуемым. Многоступенчатый Docker отделит тяжёлую сборку от лёгкого образа, healthcheck и автоматические миграции сделают старт безопасным, а пара слов про метрики покажет, куда смотреть под нагрузкой. И факультативом сравним наш ReaderT-стек с альтернативой на effectful.

Старт: миграции до трафика

Сначала main. Порядок шагов важен: сначала читаем конфиг, поднимаем окружение, прогоняем миграции, и только потом начинаем слушать порт. Так схема готова раньше, чем придёт первый запрос.

main :: IO ()
main = do
  cfg <- loadConfig
  env <- buildEnv cfg
  runSqlPool runMigrations (envPool env)   -- миграции до приёма трафика
  logInfo (envLogger env) ("our-meeple слушает порт " <> tshow (configPort cfg))
  app <- mkApp env
  run (configPort cfg) app

Миграции при старте удобны для небольшого сервиса: один бинарь сам приводит базу в порядок. Для больших систем миграции выносят в отдельный шаг деплоя (отдельная команда до выкатки кода), но идея та же: схема обновлена раньше, чем новый код её использует.

Многоступенчатый Docker

GHC и cabal весят сотни мегабайт, и тащить их в рантайм-образ незачем. Многоступенчатая сборка решает это: компилятор живёт в стадии build, а в финальный образ копируется только бинарь.

# --- стадия сборки ---
FROM haskell:9.6.7 AS build
WORKDIR /build
RUN apt-get update && apt-get install -y --no-install-recommends libpq-dev
# Сначала только описание зависимостей: пока оно не менялось,
# слой со скачанными пакетами берётся из кеша Docker.
COPY our-meeple.cabal cabal.project ./
RUN cabal update && cabal build --only-dependencies exe:our-meeple
COPY . .
RUN cabal install exe:our-meeple --installdir=/out --install-method=copy

# --- стадия запуска ---
FROM debian:bookworm-slim AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
        libpq5 ca-certificates wget
COPY --from=build /out/our-meeple /usr/local/bin/our-meeple
ENV MEEPLE_PORT=8080
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD wget -qO- "http://localhost:${MEEPLE_PORT}/health" || exit 1
CMD ["our-meeple"]

Два приёма экономят время и место. Первый: сначала копируем только *.cabal и cabal.project и собираем зависимости, и лишь потом весь код. Пока зависимости не менялись, Docker берёт этот слой из кеша, и пересборка после правки кода занимает секунды, а не минуты. Второй: рантайм-образ это debian-slim с одной системной библиотекой libpq5, без GHC.

Healthcheck и проба готовности

HEALTHCHECK в Dockerfile периодически дёргает /health, тот самый эндпоинт из урока 43. Пока он не отвечает 200, оркестратор считает контейнер не готовым и не пускает на него трафик. Это и есть проба готовности: новый контейнер начинает получать запросы, только когда сервис реально поднялся и база смигрировала.

Метрики: куда смотреть

Логи отвечают на вопрос “что случилось с конкретным запросом”, метрики на вопрос “что происходит в целом”. Минимум, что меряем у такого сервиса:

  • частота и латентность запросов по эндпоинтам (находит медленные ручки);
  • доля ошибок 4xx и 5xx (отличает проблему клиента от проблемы сервера);
  • занятость пула соединений (если пул всегда полон, запросы ждут базу);
  • бизнес-метрики домена (активные аренды, просрочки).

В экосистеме это обычно prometheus через middleware WAI: оно само меряет каждый запрос и отдаёт метрики на отдельном эндпоинте. Подключать его здесь не будем, важно понять, что мерить, а не какой пакет взять.

премиум-урок

Продолжение в премиуме: 1 200 ₽ в месяц или 10 000 ₽ в год. Вместе с ним серверные проверки задач и школьные токены AI-коуча.

Оплата пока не подключена: премиум и курсы открываются промокодом.