В прошлом посте мы создали небольшой Consul кластер с четырьмя сервисами в нём: двумя web
сервисами и двумя db
. Но так как мы не сказали Консулу, как мониторить их состояние, Консул-агенты абсолютно упустили из виду тот факт, что ни одного сервиса на самом деле не существует. Сегодня мы посмотрим, как это можно было бы исправить: как добавить проверки состояния сервисов, и как эти проверки влияют на способность сервисы обнаружить.
Что такое проверка состояния сервисов
Consul может повесить несколько проверок состояния как на отдельные сервисы, так и на хост целиком. Проверкой может быть что-нибудь простое, вроде HTTP запроса к сервису, либо что-нибудь посложнее, вроде запуска сторонней программы или скрипта и проверка её кода выхода. В зависимости от результата такой проверки сервис попадёт в одну из трёх категорий: healthy, warning или critical. В советской терминологии это «нормально», «всё плохо», «всё очень плохо».
Первые два статуса практически не отличаются, но как только сервис попадает в critical, Consul перестаёт считать его за живого и тут же исключает из DNS списков. Как результат, другие сервисы найти его больше не могут, что даёт бедолаге время оклематься. Кроме того, Консул может полностью удалить сервис, если тот находится в критическом состоянии слишком долго, но это нужно включать отдельно.
Типы проверок состояния сервиса
Всего есть пять типов проверок: HTTP, TCP, Script, TLS и Docker.
HTTP
HTTP проверка — это обычный HTTP запрос к какому-нибудь URL. Если запрос возвращает ответ с 2xx статус кодом, то, естественно, сервис считается живым и здоровым. 429 («Too many requests»), в свою очередь, поставит сервису warning состояние. Всё остальное — critical. Что здорово, тело HTTP ответа пойдёт прямиком в заметки к результатам проверки, так что туда можно положить какие-нибудь дополнительные детали. Посмотреть их можно будет в Consul UI.
Настройка HTTP проверки тривиальная:
1 2 3 4 5 6 |
{ "id": "web", "name": "NGINX responds at port 80", "http": "http://localhost", "interval": "10s" } |
TCP
TCP проверка даже ещё проще, чем HTTP. Делается TCP запрос, и если сервис на него ответил, то всё хорошо. Если нет — всё плохо. Третьего не дано.
1 2 3 |
... "tcp": "127.0.0.1:8500", ... |
TTL
TTL (Time to live) использует совсем другой подход. Это проверка ожидает, что сервис сам будет слать HTTP запросы Консулу не реже чем раз в Х секунд (TTL). Если сервис не уложился во временные рамки, то он считается умершим (critical).
Сам сервис должен слать запросы Консулу на один из 3-х адресов:
1 2 3 |
/v1/agent/check/pass/%checkId% /v1/agent/check/warn/%checkId% /v1/agent/check/fail/%checkId% |
Любой из них сбрасывает таймер на ноль, и, смотря на какой URL пришёл запрос, переводит сервис в одно из трёх состояний. JSON конфигурации всё ещё прост:
1 2 3 4 5 6 |
{ "id": "myserv", "name": "My Service Status", "notes": "It should ping the agent at least once per 30s", "ttl": "30s" } |
Script
Как следует из названия, script запускает стороннюю программу или скрипт. То, с каким кодом выхода завершилась программа, определяет, в какое состояние перейдёт сервис. 0
— значит здоровый, 1
— значит warning, а всё остальное — дела очень, очень плохи. Весь STDOUT при этом сохраняется в качестве комментария к проверке.
1 2 3 4 5 |
... "name": "Memory usage", "script": "/home/docker/memusage.sh", "interval": "10s" ... |
Это, возможно, не сразу очевидно, но состояние сервиса — это не только его способность отвечать на HTTP и TCP запросы. Мы может проверять что-то более любопытное, вроде насколько сильно он грузит процессор, как много диска оттяпал, или памяти. Script для этого подходит идеально.
Docker
Docker проверка работает точно так же, как и script. Просто в этот раз сторонняя программа запускается внутри контейнера и через docker exec
.
1 2 3 4 5 |
... "docker_container_id": "bceff99", "shell": "/bin/bash", "script": "/root/memusage.sh" ... |
Добавляем проверки в наш кластер
Итак, самые большие проблемы в созданном прошлый раз кластере были отсутствие смысла и сервисных проверок. Со смыслом уже ничего не поделаешь, но с проверками всё решаемо. Как насчёт того, чтобы создать одну проверку состояния для web
сервиса, и одну для хоста целиком?
HTTP проверка для web сервиса
В прошлый раз мы остановились на такой конфигурации сервисов:
1 2 3 4 5 6 7 |
{ "services": [{ "name": "web" }, { "name": "db" }] } |
Проверку в неё можно добавить двумя способами: либо прямиком внутрь сервиса, либо отдельной секцией, но тогда придётся указывать, для кого она предназначена, через service_id: "id"
. Первый способ короче, так что остановимся на нём.
Итак, чтобы убедиться, что web
сервис ещё жив, мы будем слать ему запрос раз в 15 секунд:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "services": [{ "name": "web", "checks": [{ "id": "web-ping", "http": "http://127.0.0.1", "interval": "15s" }] }, { "name": "db" }] } |
Сохраняем изменения в services.json
и скармливаем файл Consul агентам на host-1
и host-2
:
1 2 3 4 5 |
docker@host-1:~$ ./consul agent -advertise 192.168.99.102 -retry-join 192.168.99.100 -data-dir /tmp/consul \ -config-file services.json docker@host-2:~$ ./consul agent -advertise 192.168.99.101 -retry-join 192.168.99.100 -data-dir /tmp/consul \ -config-file services.json |
Да, Со времени прошлого поста мой кластер успел отправиться в лучший из миров, реинкарнироваться и получить новые айпишки: 192.168.99.100 для consul-server
и 192.168.99.102 и .101 для host-1
и host-2
. Но конфигурация ни капельки не изменилась.
Как только сервисы перезапустились, идём к Консул-серверу в UI:
И прямо с порога видно, что оба web
сервиса лежат. Это, в принципе, предсказуемо, ведь их всё ещё не существует. Прежде чем мы это исправим, стоит посмотреть, как теперь выглядят DNS запросы к Консулу.
DNS запросы к упавшим сервисам
Так как db
всё ещё считается живым, убедимся, что он находится через DNS:
1 2 3 4 5 6 7 8 9 10 11 12 |
dig @192.168.99.100 -p 8600 db.service.consul SRV #... #;; ANSWER SECTION: #db.service.consul. 0 IN SRV 1 1 0 host-2.node.dc1.consul. #db.service.consul. 0 IN SRV 1 1 0 host-1.node.dc1.consul. # #;; ADDITIONAL SECTION: #host-2.node.dc1.consul. 0 IN A 192.168.99.101 #host-1.node.dc1.consul. 0 IN A 192.168.99.102 # #;; Query time: 43 msec #... |
Да, всё на месте, и хосты по прежнему рапортуют о двух db
сервисах на них. Теперь попробуем найти павший web
сервис:
1 2 3 4 5 6 7 8 9 10 |
dig @192.168.99.100 -p 8600 web.service.consul SRV #... #;; QUESTION SECTION: #;web.service.consul. IN SRV # #;; AUTHORITY SECTION: #consul. 0 IN SOA # #;; Query time: 38 msec #... |
Никого.
Это легко поправимо. Им нужен web
? Их есть у меня:
1 2 3 4 5 |
docker@host-1:~$ docker run -d --name nginx -p 80:80 nginx #cc4d9cc7284b083b981900a2e7e8737f6bb1647e605bab067fd9b257d76e7d40 docker@host-2:~$ docker run -d --name nginx -p 80:80 nginx #80b516609f5730054893501bcc385f1fd4d20b7eaef36311a4ba25e9ccc08954 |
Два nginx контейнера на 80х портах на обоих хостах должны утихомирить Consul. Смотрим UI:
Теперь всё хорошо. В описании проверки даже HTTP ответ засветился:
DNS запросы теперь тоже работают. Проверял.
Проверка состояния хоста целиком
Проверить можно и хост целиком. Попробуем увести хост в критический статус, если, например, на нём заканчивается память. Для этого я сделал простенький shell скрипт, который выводит в STDOUT текущие показатели памяти и завершается с кодом 0, если всё нормально, 1 (warning), если съедено больше 80% памяти, и 2 (капут), если больше 98%.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#!/bin/sh free memusage=$(free | awk 'NR==2{printf "%d", $3*100/$2}') echo echo "Memory usage is roughly $memusage%" if [ $memusage -gt "98" ]; then echo "Critical state" exit 2 elif [ $memusage -gt "80" ]; then echo "Warning state" exit 1 else exit 0 fi |
Конфигурация Consul агента снова немного изменилась:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "services": [{ "name": "web", //... }, { "name": "db" }], "check": { "id": "Memory usage", "script": "/home/docker/memusage.sh", "interval": "15s" } } |
Сохраняем, рестартуем и снова смотрим на UI:
На обоих хостах использовано по 6% памяти. До 98 далековато… Придётся изменить скрипт и выходить в critical статус, например, после 5%. Иначе как быстро убедиться, что проверка работает? Перезапускаем сервисы и… host-1
в панике:
Что здорово, все сервисы, которые на нём были, теперь тоже в панике:
Эдакая массовая истерия. В них теперь даже DNS не верит:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
dig @192.168.99.100 -p 8600 web.service.consul SRV #... #web.service.consul. 0 IN SRV 1 1 0 host-2.node.dc1.consul. #... #host-2.node.dc1.consul. 0 IN A 192.168.99.101 #... dig @192.168.99.100 -p 8600 db.service.consul SRV #... #db.service.consul. 0 IN SRV 1 1 0 host-2.node.dc1.consul. #... #host-2.node.dc1.consul. 0 IN A 192.168.99.101 #... |
Мораль
Сегодня мы рассмотрели несколько способов убедиться, что наши сервисы ещё живые, и другие с сервисы могут с ними работать, предварительно найдя компаньонов, например, через DNS. У Consul для этого есть и простые проверки, вроде HTTP запросов, и кое-что помощнее, вроде сторонних программ и скриптов. Проверки могут работать как на уровне отдельных сервисов, так и на хосте целиком.
Тут, правда, важно помнить, что эти проверки — не замена полноценному мониторингу хостов и приложений. Мониторинг детально отвечает на вопросы, что происходит сейчас, и как было раньше. Консулу же достаточно знать, кто из сервисов ещё жив. Больше ничего.
Вопрос: Паш, а для чего тогда подобное использование Consul-a, если ты говоришь, что полноценному мониторингу он не замена?
Такие проверки нужны для service discovery. Если у нас есть load balancer, который спрашивает у консула, кому можно отдать новых запросов, а тот начнёт возвращать адреса умирающих сервисов, то мы и сервисы добьём, и тех клиентов, которые хотели к ним обратиться, подставим. А так сервисы из пула можно пачками уводить в оффлайн, например, для обновления, и консул сам заметит это, временно удалит их из списков, и там самым перенаправит нагрузку тем, кто ещё остался.
Для меня консуловский мониторинг немного бинарен — просто проверяет можно ли с сервисом работать дальше. «Большой» мониторинг тоже может ответить на этот вопрос, но ещё он может замерить кучу других параметров и потом показать их динамику по времени. Я у себя не один баг нашёл только из-за того, что график на стене выглядел как-то непривычно.