Выполняем healthcheck инструментами Podman - Академия Selectel

Выполняем healthcheck инструментами Podman

Тирекс
Тирекс Самый зубастый автор
30 марта 2026

Зачем нужен healthcheck, какие бывают типичные ошибки при запуске контейнера с ним, что такое systemd и Quadlet и зачем нужны.

Изображение записи

Начиная работать с Podman, я задал себе вопрос — функционирует ли healthcheck в Podman так же, как в Docker? Да и нет. Давай разбираться.

Статью написал Роман Шубин, CTO и автор Telegram-канала Bash Days.

Различия между Podman и Docker

Ключевая разница между Podman и Docker кроется не в самих «хелфчеках», а в архитектуре. Docker использует централизированный демон, который управляет контейнерами и следит за их состоянием.

Работает это так. В файле Dockerfile/Compose задается проверка:


      HEALTHCHECK CMD curl --fail http://localhost:8080 || exit 1

Демон Docker сам запускает проверки, хранит статус healthy/unhealthy, может перезапускать контейнер в связке с restart policy. А в Podman все происходит чуть менее магически. Да, в нем есть такие же healthcheck, как и в Docker, но нет демона, который будет все это крутить. Все проверки происходят на уровне systemd или podman healthcheck run.

Podman не будет автоматически проверять контейнер каждые N секунд, если вы это явно не настроете. Это выглядит уже более гибко и по-современному.

И получаем в итоге такую картину:

  • Docker → «убери руки, я сам все проверю и прослежу».
  • Podman → «я дам тебе инструменты, а ты решай, как их использовать».

Хорошо, с этим разобрались, стало намного яснее. Едем дальше и переходим к практическим действиям.

Создаем и запускаем контейнер с healthcheck и разбором ошибок

Расчехляем лабораторию с развернутым Podman. Создаем файл app.py:


      from flask import Flask, jsonify
import os

app = Flask(__name__)

@app.route("/")
def index():
    return "Hello from Podman!"

@app.route("/health")
def health():
    if os.getenv("FAIL", "false") == "true":
        return jsonify({"status": "unhealthy"}), 500
    return jsonify({"status": "ok"}), 200

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

Готовим Dockerfile:


      FROM python:3.11-slim

WORKDIR /app
COPY app.py .
RUN pip install flask
EXPOSE 8080
HEALTHCHECK --interval=10s --timeout=3s --retries=3 CMD curl --fail http://localhost:8080/health || exit 1
CMD ["python", "app.py"]

Собираем и запускаем:


      podman build -t healthcheck-demo

Сборка образа прошла успешно, но мы получаем сообщение:


      HEALTHCHECK is not supported for OCI image format and will be ignored Must use docker format

Всплыло очень важное отличие Podman от Docker. По умолчанию Podman собирает образы в формате OCI, а в нем healthcheck не поддерживается.

Да, тут важно знать, что есть два формата образов:

  • OCI (Open Container Initiative) — стандарт, который использует Podman.
  • Docker format — старый формат Docker.

Поэтому указываем дополнительный ключ при сборке:


      podman build --format docker -t healthcheck-demo
Скриншот терминала с выводом команды podman images, показывающий успешно созданный образ localhost/healthcheck-demo с тегом latest.

Все супер: собралось, ошибок и предупреждений не вылезло. Но это старый формат образа. И нам такое не совсем подходит, у нас OCI и Podman. Давайте ковырять.

Удаляем строчку с HEALTHCHECK из Dockerfile и пересобираем образ без ключа --format. Запускаем контейнер:


      podman run -d -p 8080:8080 --name health-demo healthcheck-demo

Смотрим:

Скриншот терминала: вывод 'podman ps' для контейнера health-demo. В колонке STATUS указано время работы (Up 7 seconds), но информация о здоровье (health) еще отсутствует.

Хм, видим, колонка STATUS есть, но healthcheck не видно. Останавливаем контейнер:


      podman stop d384c0c67528

И запускаем в таком формате:


      podman run -d \
  --health-cmd="curl --fail http://localhost:8080/health || exit 1" \
  --health-interval=10s \
  --health-timeout=3s \
  --health-retries=3 \
  --name demo \
  healthcheck-demo
Скриншот терминала: контейнер demo получил статус '(unhealthy)' сразу после запуска из-за некорректно настроенной проверки.

Отлично, в STATUS появился unhealthy. Это уже что-то! Давайте отдебажим, почему контейнер не поднял. Запускаем:


      podman inspect demo

И видим:

Скриншот вывода 'podman inspect' с логами ошибок: виден код выхода (ExitCode: 1) и сообщение об отсутствии утилиты curl в контейнере.

Вот и причина — в собранном образе отсутствует утилита curl. Исправим это, добавив в Dockerfile строчку:


      RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

Пересобираем OCI образ и запускаем:


      podman run -d \
  --replace \
  -p 8080:8080 \
  --health-cmd="curl --fail http://localhost:8080/health || exit 1" \
  --health-interval=10s \
  --health-timeout=3s \
  --health-retries=3 \
  --name demo \
  healthcheck-demo

Я добавил в команду ключ --replace, теперь Podman сам удалит старый контейнер и создаст новый. Если ключ не указать, то получим такую ошибку:


      You have to remove that container to be able to reuse that name: that name is already in use, or use –replace to instruct Podman to do so.

Запустилось, смотрим:

Скриншот команды 'podman ps': после исправления образа контейнер demo успешно прошел проверку и получил статус '(healthy)'.

Класс, контейнер жив, STATUS = healthy. Смотрим podman inspect demo:

Скриншот детальных логов здоровья в 'podman inspect': показаны успешные проверки (ExitCode: 0) и стандартный вывод curl.
Скриншот параметров Healthcheck: наглядно показаны настройки Test (команда curl), Timeout (3s) и Retries (3).

Ожидаемый результат получен, curl успешно отрабатывает. На хостовой машине выполняем еще одну проверку:

Скриншот терминала: выполнение команды 'curl localhost:8080/health' и получение корректного JSON-ответа {'status': 'ok'}.

Тоже все в порядке, мы видим {“status”:”ok”}.

Ключи запуска

Теперь давайте разберем ключи запуска:

  • health-interval=10s — каждые 10 секунд выполняется health-cmd.
  • health-timeout=3s — если curl не успел выполниться за три секунды → ошибка.
  • health-retries=3 — три неудачных проверки подряд → контейнер становится unhealthy (именно подряд, а не суммарно).

Dev

Для dev-окружений обычно выставляют:

  • --health-interval=5s,
  • --health-retries=1.

Сразу и быстро всплывают проблемы, не нужно ничего ждать.

Prod

Для production-окружений:

  • --health-interval=30s,
  • --health-retries=3-5.

Получаем устойчивость к кратким лагам.

Вообще эти параметры докручиваются индивидуально под потребности проекта, у каждого будет своя «золотая середина». В большинстве случаев на небольших пет-проектах healthcheck вообще не используют, а зря.

Systemd

Итого получается, что Podman healthcheck — это не часть образа, а часть конфигурации контейнера. Чтобы Podman вел себя как «production-ready», обычно используют systemd.

Генерируем unit:


      podman generate systemd --name demo --files --new

И получаем предупреждение: DEPRECATED command: It is recommended to use Quadlets for running containers and pods under systemd.

Ладно, как скажете, будем использовать Quadlets.

Quadlet

Quadlet — это способ описать контейнер сразу как systemd unit. Удаляем результат прошлой команды:


      rm container-demo.service && systemctl --user daemon-reload

Создаем Quadlets-файл:


      mkdir -p ~/.config/containers/systemd

Создаем файл: /etc/containers/systemd/demo.container с содержимым:


      [Unit]
Description=Demo container with healthcheck

[Container]
Image=localhost/healthcheck-demo:latest

PublishPort=8080:8080

HealthCmd=curl --fail http://localhost:8080/health || exit 1
HealthInterval=10s
HealthTimeout=3s
HealthRetries=3

[Install]
WantedBy=multi-user.target

Перезагружаем systemd:


      systemctl daemon-reexec
systemctl daemon-reload
Запускаем контейнер через systemd:
systemctl start demo.service
systemctl status demo.service
Скриншот вывода 'systemctl status demo.service': сервис Flask-приложения активен (running) под управлением Quadlet.

Отлично! На красное внимание не обращайте, это «варнинги». Сообщается, что мы запустили контейнер в developer-режиме, классическое предупреждение от Flask.

Смотрим, что говорит Podman:

Итоговый скриншот 'podman ps', подтверждающий корректный запуск и проброс портов для контейнера под управлением systemd.

Ага, мы только что подняли контейнер с помощью systemd, а healthcheck у нас явно прописан в unit-файле.

Quadlet-сервисы являются сгенерированными unit’ами, поэтому их нельзя напрямую включить через systemctl enable.

Как это работает

  1. systemd стартует,
  2. quadlet-generator читает /etc/containers/systemd/,
  3. генерирует demo.service,
  4. смотрит [Install] → WantedBy=multi-user.target,
  5. автоматически включает unit.

Перезагружаем машину:

Скриншот терминала: команда 'uptime' показывает 1 минуту работы системы, а 'podman ps' подтверждает, что контейнер systemd-demo уже в статусе '(healthy)'.

Видим, что unit автоматически стартанул наш контейнер. Даже без команды systemctl enable demo.service.

Эта тема довольно неочевидная, но очень интересная. О ней мало подробностей, потому что обычно все довольствуются подходом «Работает, не трогаем. То, что под капотом — это уже не наша забота». Но мне всегда было интересно, как оно устроено внутри, поэтому и вы теперь это знаете.

Насчет compose-файлов сильно погружаться не буду, но скажу так: главное отличие от юнитов в следующем:

  • docker-compose up → отдельный процесс,
  • systemd → управляет контейнером как сервисом.

Почему systemd — это круто? Потому что там есть автозапуск на более низком уровне, логи через journalctl, restart policy, зависимости After / Requires, единый контроль сервисов. В общем, Quadlet — это декларативный способ описания контейнеров для systemd, который частично заменяет Docker-compose в systemd-ориентированных окружениях.

Вернемся к healthcheck

Ключевая идея — healthcheck выполняется внутри контейнера, а не снаружи.

Скриншот фрагмента JSON-конфигурации из 'podman inspect', описывающего команду проверки и интервалы (Interval: 1s).

Команда запускается как отдельный процесс, внутри того же неймспейса с тем же filesystem. Healthcheck проверяет внутреннее состояние контейнера, а не доступность сервиса извне.

У healthcheck есть три состояния (STATUS):

  • starting,
  • healthy,
  • unhealthy.

Тут все очевидно: стартуем, живем, упало.

Важно! Healthcheck ≠ restart, Podman не перезапускает контейнер сам, этим он отличается от того же Kubernetes. Чтобы добавить перезапуск, можно подшаманить unit-файл и добавить Restart=always.

Вывод

А для чего вообще нужен healthcheck? Если кратко — чтобы понять, реально ли приложение работает или просто имеется статус «контейнер запущен». Только не тешьте себя напрасными надеждами, что healthcheck добавляет какую-то «магическую» самовосстановляемость (или самовосстанавливаемость?). Нет, это просто честная и прозрачная проверка состояния контейнера. Инструмент подскажет, если что-то пойдет не так, но исправлять ситуацию за вас не станет.

В связке с systemd или Quadlet так вообще красота получается. Можно построить полноценную, управляемую и предсказуемую систему. Без лишней магии, но с полным контролем.