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

мы можем про­ве­рять, отзы­ва­ет­ся ли веб-сер­вер внут­ри кон­тей­не­ра на вхо­дя­щие запро­сы, вме­ня­е­мое ли коли­че­ство памя­ти он исполь­зу­ет, встре­ча­ют­ся ли в логах фра­зы вро­де «epic fail!!!»… Мно­го чего мож­но про­ве­рить, ведь про­вер­ка дела­ет­ся через запуск како­го-нибудь сто­рон­не­го скрип­та или при­ло­же­ния, а её резуль­тат зави­сит от кода, с кото­рым это при­ло­же­ние завершилось.

В обыч­ном режи­ме кон­тей­нер, послед­ние несколь­ко про­ве­рок кото­ро­го ока­за­лись «так себе», полу­чит атри­бут «unhealthy», в Docker events появит­ся соот­вет­ству­ю­щая запись (health_status), и на этом исто­рия закон­чит­ся. Но если речь идёт о Swarm режи­ме, то про­блем­ный кон­тей­нер без лиш­них раз­го­во­ров усы­пят и авто­ма­ти­че­ски запу­стят новый. Вот так жестоко.

Как включить проверку состояний

Есть по край­ней мере четы­ре места, где её мож­но вклю­чить и настроить:

  • в самом Dockerfile,
  • в коман­де docker run,
  • в YAML для docker-compose и docker stack
  • и в docker service create команде.

Как мини­мум в настрой­ку про­вер­ки нуж­но пере­дать коман­ду (скрипт, экзеш­ник), кото­рая эту про­вер­ку будет делать. Коман­да долж­на завер­шать­ся с кодом 0 , если кон­тей­нер здо­ров, и 1, если всё уже пло­хо. В дове­сок мож­но ука­зать, как часто запус­кать про­вер­ку (--interval), как дол­го ей мож­но длить­ся (--timeout), и сколь­ко раз (—retries) она долж­на вер­нуть 1, преж­де чем на кон­тей­не­ре поста­вят жир­ный «unhealthy» крест.

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

Пред­ставь­те, что у нас есть кон­тей­нер, в кото­ром живёт веб-сер­вер, спо­соб­ность отве­чать на запро­сы кото­ро­го мы хотим про­кон­тро­ли­ро­вать. Напри­мер, раз в 5 секунд мы будем отправ­лять ему запрос, давать мак­си­мум 10 секунд на то, что­бы тот отве­тил, и если 3 раза под­ряд он сла­жа­ет — будем подо­зре­вать нелад­ное. Так как в Dockerfile есть инструк­ция HEALTHCHECK, и её фор­мат — прост до без­об­ра­зия (  HEALTHCHECK [OPTIONS] CMD command), то при помо­щи како­го-нибудь curl нашу про­вер­ку мож­но сде­лать так:

FROM

HEALTHCHECK --interval=5s --timeout=10s --retries=3 CMD curl -sS 127.0.0.1 || exit 1

Коман­да будет запус­кать­ся изнут­ри кон­тей­не­ра, так что обра­щать­ся к сер­ве­ру мож­но и по 127.0.0.1.

Проверка состояния в docker-compose YAML

Она прак­ти­че­ски никак не отли­ча­ет­ся от оной в Dockerfile:

[codesyntax lang="php"]

[/codesyntax]

 

Проверка в docker run и service create

У этих двух син­так­сис про­ве­рок оди­на­ко­вый и тоже пред­ска­зу­е­мо понятный:

docker run --health-cmd='curl -sS http://127.0.0.1 || echo 1' \
--health-timeout=10s \
--health-retries=3 \
--health-interval=5s \
.…

Кста­ти, если в Dockerfile обра­зе, кото­рый мы запус­ка­ем, уже была про­вер­ка, то на этой ста­дии её мож­но пере­опре­де­лить или даже выклю­чить с помо­щью --no-healthcheck=true.

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

Жертва

Име­ет­ся малень­кий node.js сер­вер, глав­ной целью в жиз­ни кото­ро­го явля­ет­ся отве­чать ‘OK’ на запро­сы к пор­ту 8080, и отключаться/включаться после запро­сов к пор­ту 8081:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

 

При­мер­но так:

$ node server.js
# switch to another terminal
curl 127.0.0.1:8080
OK
curl 127.0.0.1:8081
# Shutting down…
curl 127.0.0.1:8080
# curl: (7) Failed to connect to 127.0.0.1 port 8080: Connection refused
curl 127.0.0.1:8081
# Starting up…
curl 127.0.0.1:8080
OK

 

Теперь поло­жим этот server.js в Dockerfile, доба­вим туда HEALTHCHECK, собе­рём это всё в образ по име­ни server и запустим:

FROM node

COPY server.js /

EXPOSE 8080 8081

HEALTHCHECK --interval=5s --timeout=10s --retries=3 CMD curl -sS 127.0.0.1:8080 || exit 1

CMD [ "node", "/server.js" ]

 

$ docker build . -t server:latest
# Lots, lots of output
$ docker run -d --rm -p 8080:8080 -p 8081:8081 server
# ec36579aa452bf683cb17ee44cbab663d148f327be369821ec1df81b7a0e104b
$ curl 127.0.0.1:8080
OK

Пер­вых трём сим­во­лов айдиш­ки кон­тей­не­ра — ec3 — нам будет доста­точ­но, что­бы к нему обра­тить­ся, так что самое вре­мя перей­ти к проверкам.

Мониторим состояние контейнера

Основ­ная коман­да для запро­са состо­я­ния кон­тей­не­ра в Docker — docker inspect. Она мно­го чего может рас­ска­зать, но нам все­го-то нуж­но свой­ство State.Health:

[codesyntax lang="php"]

[/codesyntax]

 

Вполне пред­ска­зу­е­мо, кон­тей­нер — в ‘healthy’ состо­я­нии, а в Log даже мож­но посмот­реть, чем отзы­вал­ся сер­вер на запро­сы. Но если мы теперь отпра­вим запрос на 8081 и подо­ждём 3*5 секунд (3 про­вер­ки под­ряд), то кое-что изме­нит­ся в инте­рес­ную сторону.

[codesyntax lang="php"]

[/codesyntax]

Я про­ма­зал мимо 15 секунд и про­пу­стил аж четы­ре про­вер­ки, поэто­му зна­че­ние в FailingStreak ста­ло рав­но четы­рём. Но в осталь­ном Ностра­да­мус не врал — кон­тей­нер пере­шёл в раз­ряд ‘unhealthy’.

Прав­да, доста­точ­но лишь одной успеш­ной про­вер­ки, что­бы кон­тей­нер офи­ци­аль­но воскрес:

$ curl 127.0.0.1:8081
# Starting up…
$ docker inspect ec3 | jq '.[].State.Health.Status'
# "healthy"

 

Узнаём статус контейнера из Docker events

Кро­ме как спра­ши­вать кон­тей­нер о его здо­ро­вье напря­мую, мож­но спро­сить у его сосе­дей — docker events:

$ docker events --filter event=health_status
# 2017-06-27T00:23:03.691677875-04:00 container health_status: healthy ec36579aa452bf683cb17ee44cbab663d148f327be369821ec1df81b7a0e104b (image=server, name=eager_swartz)
# 2017-06-27T00:23:23.998693118-04:00 container health_status: unhealthy ec36579aa452bf683cb17ee44cbab663d148f327be369821ec1df81b7a0e104b (image=server, name=eager_swartz)

Обыч­но собы­тий у Docker доста­точ­но мно­го, так что при­шлось при­глу­шить ненуж­ные пара­мет­ром --filter. Сама docker events само­сто­я­тель­но не завер­шит­ся и будет спа­мить в кон­соль до скон­ча­ния веков.

Состояние контейнера в Swarm сервисах

Что­бы начать играть­ся с сер­ви­са­ми, мне при­шлось вре­мен­но пере­ве­сти локаль­ный Docker в Swarm режим через docker swarm init. Зато теперь наш server образ мож­но запус­кать вот так:

$ docker service create -p 8080:8080 -p8081:8081 \
--name server \
--health-cmd='curl -sS 127.0.0.1:8080' \
--health-retries=3 \
--health-interval=5s \
server
#unable to pin image server to digest: errors:
#denied: requested access to the resource is denied
#unauthorized: authentication required

#ohkvwbsk06vkjyx69434ndqij

Ока­зы­ва­ет­ся, Swarm не очень любит локаль­но создан­ные обра­зы, поэто­му-то и выплю­нул в кон­соль несколь­ко оши­бок со сво­им недо­воль­ством. Но сер­вис всё-таки создал и вер­нул его айдишку:

 

curl 127.0.0.1:8080 теперь сно­ва будет воз­вра­щать OK (я про­ве­рял), а запрос на порт 8081 вре­мен­но заткнёт сер­вер. Но в отли­чие от пер­во­го при­ме­ра, при­мер­но через пол мину­ты после того, как сер­вер был явно отклю­чён, он сно­ва нач­нёт отзы­вать­ся на 8080. Как же так?

При­кол в том, что как толь­ко мы отклю­чи­ли сер­вер и его кон­тей­нер полу­чил ста­тус unhealthy, это тут же заме­тил Swarm мене­джер и понял, что заяв­лен­ная кон­фи­гу­ра­ция сер­ви­са боль­ше не выпол­ня­ет­ся. А так нель­зя, поэто­му он быст­рень­ко при­бил про­блем­ный сер­вис­ный кон­тей­нер и заме­нил его на новый, рабо­чий. Сле­ды это­го мож­но уви­деть, посмот­рев исто­рию задач для все­го сервиса:

Малень­кая предыс­то­рия: для каж­до­го кон­тей­не­ра в сер­ви­се Swarm созда­ёт зада­чу. То есть сна­ча­ла идёт зада­ча, а она уже при­во­дит к кон­тей­не­ру. Наш пер­вый кон­тей­нер «упал», поэто­му мене­джер создал новую зада­чу, и она при­ве­ла к ново­му кон­тей­не­ру. Но ста­рая зада­ча оста­лась в исто­рии! docker service ps пока­зы­ва­ет всё цепоч­ку смер­тей и реин­кар­на­ций задач, и в нашем слу­чае вид­но, что самая ста­рая зада­ча с айди pj77brhfhsjm поме­че­на как упав­шая, и через docker inspect мож­но узнать почему:

«Unhealthy container», вот почему.

Мораль

Про­вер­ки состо­я­ния кон­тей­не­ров в Docker — это воз­мож­ность регу­ляр­но запус­кать сто­рон­ние скрип­ты и экзеш­ни­ки внут­ри кон­тей­не­ра, что­бы узнать, доста­точ­но ли живо его содер­жи­мое. В одно­хо­сто­вом режи­ме докер про­сто поме­тит про­блем­ные кон­тей­не­ры как unhealthy и сге­не­ри­ру­ет health_status собы­тие. В облач­ном же режи­ме он ещё и отклю­чит этот кон­тей­нер и заме­нит его на новый, всё ещё здо­ро­вый. Быст­ро и автоматически.