Как узнать, нормально ли работает ваше приложение

Ваш Dockerfile создан, ваш образ создан, и вы готовы к развертыванию в продукте. После быстрого docker run ваш контейнер готов, и вы с нетерпением ждете, пока ваше приложение начнет обслуживать запросы.

Вы ждете несколько секунд… docker ps сообщает, что ваш контейнер запущен, но ничего не обслуживается. Что случилось?

Полуправда о контейнере вверх

Начнем с создания простейшего контейнера Docker, используя следующий файл Docker:

FROM nginx:1.17.7

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

docker build -t docker-health .
docker run --rm --name docker-health -p 8080:80 docker-health

Контейнер NGINX теперь работает и прослушивает локальный порт 8080. Продолжайте и протестируйте его, выполнив curl localhost:8080, или просто откройте http://localhost:8080 в своем браузере:

Сообщается, что контейнер Up работает несколько секунд:

Но что проверяет Docker, чтобы сообщить, что ваш контейнер запущен? Давайте рассмотрим процессы, выполняющиеся внутри контейнера:

Процесс, запускаемый ENTRYPOINT или CMD файла Docker, работает как
PID 1. PID - это сокращение от идентификационного номера процесса, которое автоматически присваивается каждому процессу при его создании. Каждый процесс имеет уникальный PID в любой Unix-подобной системе. Процесс, работающий как PID 1 внутри контейнера, обрабатывается особым образом, поскольку он игнорирует любой сигнал, такой как SIGINT или SIGTERM, и не завершается, если он не закодирован для этого.

Пока PID 1 запущен и работает, движок Docker будет сообщать, что контейнер тоже запущен и работает. Даже если вы приостановите контейнер с помощью docker pause, контейнер все равно будет отображаться как Up, но с флагом (Paused):

Механизм Docker на самом деле не знает и не заботится о том, что делает контейнерное приложение. Но мы делаем.

Представим, что наш контейнер на основе NGINX был создан для обслуживания (среди прочего) некоторого статического system-status.txt файла с информацией о состоянии системы. Этот файл может быть недоступен сразу после первоначального запуска контейнера, поскольку мы можем создать его как-нибудь иначе. Давайте попробуем получить этот файл вскоре после создания контейнера:

Файл не существует, и мы получили ошибку 404. Означает ли это, что контейнер в порядке или нет? В конце концов, мы получили ответ от NGINX.

Хотя здесь используется простой пример со статическим файлом, ваша проверка вполне может быть связана, например, с доступностью API вашего приложения. Учитывая, что вашему контейнерному приложению может потребоваться несколько секунд для загрузки и предоставления доступа к API, ваш контейнер будет отмечен как Up, хотя пока ничего не обслуживается.

Точно так же, если приложение сталкивается с проблемой, делающей его конечные точки API недоступными, PID может все еще работать, но ни один клиент не сможет с ним взаимодействовать. Фактически, вы получаете контейнер зомби, который docker ps сообщает, что он работает нормально.

Разве не было бы лучше, если бы Docker предоставил способ выполнения пользовательских проверок, отличный от проверки процесса, которую он уже выполняет? Проверка, которую мы, разработчики приложений, могли бы определить индивидуальным образом, подходящим по своей природе для приложения, работающего внутри контейнера?

Давайте посмотрим, как это сделать, и определим индивидуальную проверку работоспособности.

Проверки состояния контейнера

Механизм Docker, начиная с версии 1.12, позволяет определять пользовательские команды как проверки работоспособности. Хотя эта функция доступна с середины 2016 года, довольно удивительно, что многие образы Docker до сих пор ее не используют.

Пользовательская проверка работоспособности указывается в файле Docker с помощью директивы HEALTHCHECK. Команда проверки выполняется внутри контейнера, поэтому убедитесь, что она доступна. Что касается типа проверки, которую он выполняет, это может быть все, о чем вы можете подумать, если он возвращает соответствующий код статуса выхода. Согласно официальной документации Docker, эти коды выхода могут быть:

«0: успешно - контейнер исправен и готов к использованию
1: нездоров - контейнер работает неправильно
2: зарезервирован - не используйте этот код выхода»

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

Давайте попробуем переписать Dockerfile, использованный в приведенном выше примере, на этот раз реализуя проверки работоспособности:

FROM nginx:1.17.7
RUN apt-get update && apt-get install -y wget
HEALTHCHECK CMD wget -q --method=HEAD localhost/system-status.txt

Во-первых, файл Dockerfile был дополнен командой RUN для установки Wget клиента, который будет использоваться при определении проверки работоспособности.

Во-вторых, был определен HEALTHCHECK. Проверка работоспособности включает вызов с использованием wget для получения определенного URL-адреса настраиваемого system-status.txt файла. Если файл найден, код выхода команды будет 0, в противном случае - 8 (что указывает на Сервер выдал ответ с ошибкой согласно официальной странице кодов выхода Wget).

Тестирование проверки работоспособности

Используя расширенный файл Dockerfile, приведенный выше, давайте создадим новый образ и перезапустим контейнер:

Выдав docker ps сейчас, мы можем увидеть, как движок Docker сообщает о состоянии работоспособности контейнера в дополнение к его UP статусу:

В конфигурации по умолчанию для проверок работоспособности команда проверки работоспособности не запускается немедленно, поэтому вы видите начальный статус (health: starting). Это означает, что ваш контейнер запущен, но проверки работоспособности еще не выполнялись. Через 30 секунд выполняется команда проверки работоспособности, и, поскольку system-status.txt не существует, контейнер теперь отображается как unhealthy:

Попробуем вылечить контейнер, создав образец system-status.txt:

docker exec docker-health sh -c \
'echo OK > /usr/share/nginx/html/system-status.txt'

В следующие 30 секунд контейнер должен отображаться как healthy:

Параметры проверки работоспособности

Команду HEALTHCHECK также можно использовать с четырьмя различными параметрами:

--interval = DURATION (по умолчанию: 30 с)
--timeout = DURATION (по умолчанию: 30 с)
--start-period = DURATION (по умолчанию: 0 с)
--retries = N ( по умолчанию: 3)

Параметр interval указывает количество секунд для первоначального ожидания перед выполнением проверки работоспособности, а затем частоту, с которой будут выполняться последующие проверки работоспособности.

Параметр timeout указывает количество секунд, в течение которых Docker ожидает, пока ваша команда проверки работоспособности вернет код выхода, прежде чем объявить его неудачным (а ваш контейнер - unhealthy).

Параметр start-period указывает количество секунд, необходимое вашему контейнеру для начальной загрузки. В течение этого периода проверки работоспособности с кодом выхода больше нуля не помечают контейнер как unhealthy; однако код состояния 0 пометит контейнер как healthy.

Параметр retries указывает количество последовательных сбоев проверки работоспособности, необходимых для объявления контейнера как unhealthy.

Поведение Docker Swarm

Полезная функция проверки работоспособности при запуске контейнеров в Docker Swarm заключается в том, что пока контейнер находится в состоянии unhealthy или (health: starting), маршрутизация отключена, и никакие запросы не достигают контейнера вообще.

Заключение

Запуск вашего контейнера не обязательно означает, что ваше приложение работает или ведет себя так, как задумано. Проверки работоспособности Docker могут быть очень легко реализованы и могут помочь вам быстро выявить неустойчивое поведение, прежде чем оно станет реальной проблемой.

В следующий раз, когда вы создадите Dockerfile, подумайте о добавлении проверки работоспособности.