
TL;DR
- Чтобы добиться высокой наблюдаемости контейнеров и микросервисов, журналов и первичных метрик мало.
- Для более быстрого восстановления и повышения отказоустойчивости приложения должны применять Принцип высокой наблюдаемости (HOP, High Observability Principle).
- На уровне приложение для НОР требуется: должное журналирование, тщательный мониторинг, проверки работоспособности и трассировки производительности/переходов.
- В качестве элемента НОР используйте проверки readinessProbe и livenessProbe Kubernetes.
Что такое Шаблон проверки работоспособности?
Когда проектируешь критически важное и высокодоступное приложение, очень важно подумать о таком аспекте, как отказоустойчивость. Приложение считается отказоустойчивым, если оно быстро восстанавливается после отказа. Типичное облачное приложение использует архитектуру микросервисов – когда каждый компонент помещается в отдельный контейнер. А для того, чтобы убедиться, что приложение на k8s высокодоступно, когда проектируешь кластер, надо следовать определенным шаблонам. Среди них – Шаблон проверки работоспособности. Он определяет, как приложение сообщает k8s о своей работоспособности. Это не только информация о том, работает ли pod, а еще и о том, как он принимает запросы и отвечает на них. Чем больше Kubernetes знает о работоспоспособности pod’а, тем более умные решения принимает о маршрутизации трафика и балансировке нагрузки. Таким образом, Принцип высокой наблюдаемости приложению своевременно отвечать на запросы.
Принцип высокой наблюдаемости (НОР)
Принцип высокой наблюдаемости – это один из принципов проектирования контейнеризированных приложений. В микросеврисной архитектуре сервисам безразлично, как их запрос обрабатывается (и это правильно), но важно, как получить ответы от принимающих сервисов. К примеру, для аутентификации пользователя один контейнер посылает другому запрос HTTP, ожидая ответа в определенном формате – вот и все. Обрабатывать запрос может и PythonJS, а ответить – Python Flask. Контйнеры друг для друга – что черные ящики со скрытым содержимым. Однако принцип НОР требует, чтобы каждый сервис раскрывал несколько конечных точек API, показывающих, насколько он работоспособен, а также состояние его готовности и отказоустойчивости. Эти показатели и запрашивает Kubernetes, чтобы продумывать следующие шаги по маршрутизации и балансировке нагрузки.
Грамотно спроектированное облачное приложение журналирует свои основные события используя стандартные потоки ввода-вывода STDERR и STDOUT. Следом работает вспомогательный сервис, к примеру filebeat, logstash или fluentd, доставляющие журналы в централизованную систему мониторинга (например Prometheus) и систему сбора журналов (набор ПО ELK). На схеме ниже показано, как облачное прилоежние работает в соответствии с Шаблоном проверки работоспособности и Принципом высокой наблюдаемости.

Как применить Шаблон проверки работоспособности в Kubernetes?
Из коробки k8s мониторит состояние pod’ов при помощи одного из контроллеров (Deployments, ReplicaSets, DaemonSets, StatefulSets и проч., проч.). Обнаружив, что pod по некой причине упал, контроллер пытается отрестартить его или перешедулить на другой узел. Однако pod может сообщить, что он запущен и работает, а сам при этом не функционирует. Приведем пример: ваше приложение использует в качестве веб-сервера Apache, вы установили компонент на несколько pod’ов кластера. Поскольку библиотека была настроена некорректно – все запросы к приложению отвечают кодом 500 (внутренняя ошибка сервера). При проверке поставки проверка состояния pod`ов дает успешный результат, однако клиенты считают иначе. Эту нежелательную ситуацию мы опишем следующим образом:

В нашем примере k8s выполняет проверку работоспособности. В этом виде проверки kubelet постоянно проверяет состояние процесса в контейнере. Стоит ему понять, что процесс встал, и он его рестартит. Если ошибка устраняется простым перезапуском приложения, а программа спроектирована так, чтобы отключаться при любой ошибке, тогда вам для следования НОР и Шаблону проверки работоспособности достаточно проверки работоспособности процесса. Жаль только, что не все ошибки устраняются перезапуском. На этот случай k8s предлагает 2 более глубоких способа выявления неполадок в работе pod’а: livenessProbe и readinessProbe.
LivenessProbe
Во время livenessProbe kubelet выполняет 3 типа проверок: не только выясняет, работает ли pod, но и готов ли он получать и адекватно отвечать на запросы:
- Установить HTTP-запрос к pod’у. Ответ должен содержать HTTP-код ответа в диапазоне от 200 до 399. Таким образом, коды 5хх и 4хх сигнализируют о том, что у pod’а проблемы, пусть даже процесс работает.
- Для проверки pod’ов с не-HTTP сервисами (например, почтовый сервер Postfix), надо установить TCP-связь.
- Исполнение произвольной команды для pod’а (внутренне). Проверка считается успешной, если код завершения команды – 0.
Пример того, как это работает. Определение следующего pod’а содержит NodeJS приложение, которое на HTTP-запросы выдает ошибку 500. Чтобы убедиться, что контейнер перезапускается, получив такую ошибку, мы используем параметр livenessProbe:
apiVersion: v1 kind: Pod metadata: name: node500 spec: containers: - image: magalix/node500 name: node500 ports: - containerPort: 3000 protocol: TCP livenessProbe: httpGet: path: / port: 3000 initialDelaySeconds: 5
Это ничем не отличается от любого другого определения pod’а, но мы добавляем объект .spec.containers.livenessProbe. Параметр httpGet принимает путь, по которому отправляет HTTP GET запрос (в нашем примере это /, но в боевых сценариях может быть и нечто вроде /api/v1/status). Еще livenessProbe принимает параметр initialDelaySeconds, который предписывает операции проверки ждать заданное количество секунд. Задержка нужна, потому что контейнеру надо время для запуска, а при перезапуске он некоторое время будет недоступен.
Чтобы применить эту настройку к кластеру, используйте:
kubectl apply -f pod.yaml
Спустя несколько секунд можно проверить содержимое pod’а с помощью следующей команды:
kubectl describe pods node500
В конце вывода найдите вот что.
Как видите, livenessProbe инициировала HTTP GET запрос, контейнер выдал ошибку 500 (на что и был запрограммирован), kubelet его перезапустил.
Если вам интересно, как было запрограммировано NideJS приложение, вот файл app.js и Dockerfile, которые были использованы:
app.js
var http = require('http'); var server = http.createServer(function(req, res) { res.writeHead(500, { "Content-type": "text/plain" }); res.end("We have run into an error\n"); }); server.listen(3000, function() { console.log('Server is running at 3000') })
Dockerfile
FROM node COPY app.js / EXPOSE 3000 ENTRYPOINT [ "node","/app.js" ]
Важно обратить внимание вот на что: livenessProbe перезапустит контейнер, только при отказе. Если перезапуск не исправит ошибку, мешающую работе контейнера, kubelet не сможет предпринять меры для устранения неисправности.
readinesProbe
readinesProbe работает аналогично livenessProbes (GET-запросы, ТСР связи и исполнение команд), за исключением действий по устранению неисправностей. Контейнер, в котором зафиксирован сбой, не перезапускается, а изолируется от входящего трафика. Представьте, один из контейнеров выполняет много вычислений или подвергается тяжелой нагрузке, из-за чего вырастает время ответа на запросы. В случае livenessProbe срабатывает проверка доступности ответа (через параметр проверки timeoutSeconds), после чего kubelet перезапускает контейнер. При запуске контейнер начинает выполнять ресурсоемкие задачи, и его снова перезапускают. Это может быть критично для приложений, которым важна скорость ответа. Например, машина прямо в пути ожидает ответа от сервера, ответ задерживается – и машина попадает в аварию.
Давайте напишем определение redinessProbe, которое установит время ответа на GET-запрос не более двух секунд, а приложение будет отвечать на GET-запрос через 5 секунд. Файл pod.yaml должен выглядеть следующим образом:
apiVersion: v1 kind: Pod metadata: name: nodedelayed spec: containers: - image: afakharany/node_delayed name: nodedelayed ports: - containerPort: 3000 protocol: TCP readinessProbe: httpGet: path: / port: 3000 timeoutSeconds: 2
Развернем pod с kubectl:
kubectl apply -f pod.yaml
Выждем пару секунд, а потом глянем, как сработала readinessProbe:
kubectl describe pods nodedelayed
В конце вывода можно увидеть, что часть событий аналогична вот этому.
Как видите, kubectl не стал перезапускать pod, когда время проверки превысило 2 секунды. Вместо этого он отменил запрос. Входящие связи перенаправляются на другие, рабочие pod’ы.
Заметьте: теперь, когда с pod’а снята лишняя нагрузка, kubectl снова направляет запросы ему: ответы на GET-запрос больше не задерживаются.
Для сравнения: ниже приведен измененный файл app.js:
var http = require('http'); var server = http.createServer(function(req, res) { const sleep = (milliseconds) => { return new Promise(resolve => setTimeout(resolve, milliseconds)) } sleep(5000).then(() => { res.writeHead(200, { "Content-type": "text/plain" }); res.end("Hello\n"); }) }); server.listen(3000, function() { console.log('Server is running at 3000') })
TL;DR
До появления облачных приложений основным средством мониторинга и проверки состояния приложений были логи. Однако не было средств предпринимать какие-то меры по устранению неполадок. Логи и сегодня полезны, их надо собирать и отправлять в систему сборки логов для анализа аварийных ситуаций и принятия решений. [это все можно было делать и без облачных приложений с помощью monit, к примеру, но с k8s это стало гораздо проще 🙂 – прим.ред. ]
Сегодня же исправления приходится вносить чуть ли не в режиме реального времени, поэтому приложения больше не должны быть черными ящиками. Нет, они должны показывать конечные точки, позволяющие системам мониторинга запрашивать и собирать ценные данные о состоянии процессов, чтобы в случае необходимости реагировать мгновенно. Это называется Шаблон проектирования проверки работоспособности, который следует Принципу высокой наблюдаемости (НОР).
Kubernetes по умолчанию предлагает 2 вида проверки работоспособности: readinessProbe и livenessProbe. Оба используют одинаковые типы проверок (HTTP GET запросы, ТСР-связи и исполнение команд). Отличаются они в том, какие решения принимают в ответ на неполадки в pod’ах. livenessProbe перезапускает контейнер в надежде, что ошибка больше не повторится, а readinessProbe изолирует pod от входящего трафика – до устранения причины неполадки.
Правильное проектирование приложения должно включать и тот, и другой вид проверки, и чтобы они собирали достаточно данных, особенно когда создана исключительная ситуация. Оно также должно показывать необходимые конечные точки API, передающие системе мониторинга (тому же Prometheus) важные метрики состояния работоспособности.
ссылка на оригинал статьи https://habr.com/ru/company/southbridge/blog/467155/
Добавить комментарий