В прод: 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-коуча.
Оплата пока не подключена: премиум и курсы открываются промокодом.