Docker health, здоровый взгляд.

Замечательная функция healthcheck имеется в инструментарии docker, на её основе kubernetes совершает множество действий над подами (контейнерами), например заменяет в реальном времени вышедший из строя контейнер. Я же хочу показать как можно пользоваться этими инструментами вне kubernetes. Для чего? Например что бы связывать контейнеры между собой в определенной последовательности или мониторинга состояния единичного сервиса.

Для начала рассмотрим пример с Dockerfile. Например у меня есть простой сервис который отдает что-то клиенту, скажем, вот такой скрипт на python:

#!/usr/bin/python

from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
class HttpProcessor(BaseHTTPRequestHandler):
	def do_GET (self):
		self.send_response(200)
		self.send_header('content-type','text/html')
		self.end_headers()
		self.wfile.write("<h1>Apps Server</h1><p>")

serv = HTTPServer(("0.0.0.0",8081),HttpProcessor)
serv.serve_forever()

Сервис будет доступен на порту 8081, и при обращении к нему будет отдавать обычный HTML в теле Apps Server. Назовем его server.py Хорошо, теперь напишем Dockerfile для него:

FROM python:2.7-alpine
RUN apk add --update curl && rm -rf /var/cache/apk/*
COPY ./server.py /opt/server.py
WORKDIR /opt
HEALTHCHECK CMD curl --fail http://localhost:8081/ || exit 1
CMD [ "python", "server.py" ]

Вкратце рассмотрим, мы берем за базовый образ python:2.7-alpine, далее добавляем в образ curl для получения результата от сервера, далее копируем файл скрипта в контейнер, назначаем рабочую директорию и добавляем проверку «здоровья» контейнера. По сути это обычный запрос к сервису и если ответ а нет, то статус «здоровья» изменится. Далее запускаем сервер. Собираем образ и запускаем его, смотрим результат:

#docker ps -a
CONTAINER ID   IMAGE                COMMAND              CREATED         STATUS                            PORTS     NAMES
a58c0a4d8bde   python-server-prod   "python server.py"   5 seconds ago   Up 4 seconds (health: starting)             python-apps

Для нас ключевое поле (health: starting), что говорит о том что контейнер запустился и ожидает ответа от сервиса, как только сервис вернет ответ, значение поля изменится на healthy. Если в процессе работы приложение перестанет возвращать ответ или завершиться с ошибкой, статус сменится на unhealthy. При этом сам контейнер может продолжать работать, но мы рассматриваем простой пример.

docker ps -a
CONTAINER ID   IMAGE                COMMAND              CREATED              STATUS                        PORTS     NAMES
a58c0a4d8bde   python-server-prod   "python server.py"   About a minute ago   Up About a minute (healthy)             python-apps

Так же, это применимо и к docker-compose, рассмотрим пример:

version: "3.7"

services:
  python-server:

    container_name: python-apps
    image: python-apps
    build: ./apps
    restart: always
    network_mode: host

    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8081/"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 10s

    logging:
      driver: "json-file"
      options:
        max-size: "100k"
        max-file: "5"

Запускаем и проверяем:

docker-compose ps
       Name              Command           State       Ports
------------------------------------------------------------
python-apps            python server.py   Up (healthy)

Если, нам необходимо не просто запустить какой-то другой контейнер после первого, но при этом не просто запустить а получить именно статус от самого сервиса, необходимо добавить зависимость:

depends_on:
  python-apps:
    condition: service_healthy

Например мы можем привязать к нашему docker-compose, nginx. Установить зависимость его от контейнера python-apps, то есть контейнер с nginx запуститься только после того как будет стартован python-apps, но тогда мы получим 502 ошибку, так как сам сервис ещё не будет запущен, но контейнер уже получил статус up. В этом случае, мы можем запускать nginx только после того как контейнер с нашим приложением ответил healthy. Это очень удобно например в связке приложения и базы данных, когда контейнер с базой запустился, но сама база ещё не прошла инициализацию, при запуске приложения оно будет выдавать ошибку соединения с базой, таким образом можно решить эту проблему.

А так же это не плохой инструмент мониторинга приложений внутри контейнера, достаточно сказать Zabbix-у или prometheus о статусе и мы можем получать оповещение когда контейнер все ещё продолжает работу, то есть его статус Up, но само приложение не возвращает нужных ответов.