Имеется приложение на Go, API-бекенд.
Периодически начинает возвращать 502 ошибку, хотя сам под работает и в статусе Running.
Что бы рассмотреть, как и почему Ingress и Service могут возвращать 502, и как работают readinessProbe
и livenessProbe
в Kubernetes Deployment – напишем простой веб-сервер на Go, в котором опишем два ендпоинта – один будет возвращать нормальный ответ, а во втором – выполнение программы будет прерываться.
Затем задеплоим его в AWS Elastic Kubernetes, создадим Kubernetes Ingress, который создаст AWS Application Load balancer, и потрестируем работу приложения.
Содержание
Golang HTTP server
Пишем приложение на Go, которое потом упакуем в Docker-контейнер, и запустим в Kubernetes:
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request){ fmt.Fprintf(w, "pong") }) http.HandleFunc("/err", func(w http.ResponseWriter, r *http.Request){ panic("Error") }) fmt.Printf("Starting server at port 8080\n") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
Тут мы запускаем http.ListenAndServe()
на порту 8080, и определяем два роута:
/ping
– при обращении сюда всегда возвращаем 200/err
– при обращении сюда прерываем выполнение функции сpanic
, что сэмулировать некорретный ответ приложения
Проверяем локально.
Запускаем:
Проверяем роут /ping
:
И URI /err
, который вызовет panic
:
Лог приложения:
Docker образ
Пишем Dockefile:
FROM golang:latest WORKDIR /app COPY . . RUN go build -o main . EXPOSE 8080 CMD ["./main"]
Собираем образ и пушим в Docker Hub:
Kubernetes
Deployment
Описываем запуск пода с этим образом – создаём 1 под, Service для него, и Ingress, который создаст AWS Application Load Balancer.
Начнём с Deployment:
apiVersion: apps/v1 kind: Deployment metadata: name: go-http labels: app: go-http spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: go-http template: metadata: labels: app: go-http spec: containers: - name: go-http image: setevoy/go-http ports: - containerPort: 8080 imagePullPolicy: Always livenessProbe: httpGet: path: /ping port: 8080 initialDelaySeconds: 1 periodSeconds: 1 readinessProbe: httpGet: path: /ping port: 8080 initialDelaySeconds: 1 periodSeconds: 1 restartPolicy: Always
Тут создаём один под, который слушает порт 8080.
В strategy
деплоймента указываем Recreate, что бы при тестах не оставались старые поды.
Для него описываем проверки – livenessProbe
и readinessProbe
, обе проверки ходят на URI /ping
, где получают ответ 200.
Позже мы поменяем путь в проверках, и посмотрим, к чему это приведёт.
Service
Создаём Kubernetes Service, который откроет на WorkerNode порт для ALB, и будет роутить трафик к нашему поду на порт 8080 (см. Kubernetes: Service, балансировка нагрузки, kube-proxy и iptables):
--- apiVersion: v1 kind: Service metadata: name: go-http-svc spec: type: NodePort ports: - port: 80 targetPort: 8080 protocol: TCP selector: app: go-http
Ingress
Добавляем Ingress, который создаст AWS Application Load Balancer, который будет направлять трафик к go-http-svc Service:
--- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: go-http-ingress annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/healthcheck-path: /ping alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]' labels: app: go-http spec: rules: - http: paths: - path: /* backend: serviceName: go-http-svc servicePort: 80
Создаём ресурсы:
Проверяем под:
И его Ingress:
Ждём, пока наш DNS увидит новый URL e172ad3e-default-gohttping-ec00-691779486.us-east-2.elb.amazonaws.com, и проверяем работу приложения – выполняем запрос к /ping
:
Kubernetes Ingress 502
А теперь – обращаемся к ендпоинту /err
, который в Go-приложении вызовет panic
, и ловим 502 ошибку:
Логи пода:
Тут всё логично – load-balancer отправил наш запрос к поду, под не ответил (вспомним curl: (52) Empty reply from server в наших первых тестах), и мы получили ответ 502 от балансировщика aka Ingress.
readinessProbe
и livenessProbe
Теперь посмотрим, как изменение в readinessProbe
и livenessProbe
повлияют на ответы Ingress и работу самого пода.
readinessProbe
Документация – тут>>>.
readinessProbe
используется для проверки того, готов ли под принимать трафик.
Сейчас наш под в статусе Ready:
Или так:
И запрос к /ping
возвращает нам ответ 200:
Меняем readinessProbe
– задаём path=/err
, что бы проверка постоянно фейлилась:
... readinessProbe: httpGet: path: /err ...
Передеплоиваем:
И проверяем:
Если мы теперь отправим запрос даже на ендпоинт /ping
– всё-равно получим 502, т.к. бекенд Сервиса, т.е. под, не принимает трафик, потому что не прошёл readinessProbe
:
Пробуем:
livenessProbe
Документация – тут>>>.
Вернём readinessProbe
в /ping
, что бы трафик на под пошёл, но изменим livenessProbe
– зададим path
в /err
, а initialDelaySeconds
и periodSeconds
установим в 15 секунд, плюс добавим failureThreshold
равным одной попытке:
... livenessProbe: httpGet: path: /err port: 8080 initialDelaySeconds: 15 periodSeconds: 15 failureThreshold: 1 readinessProbe: httpGet: path: /ping ...
Теперь после запуска пода Kubernetes выждет 15 секунд, затем выполнит livenessProbe
и будет повторять её каждые следующие 15 секунд.
Передеплоиваем, и проверяем:
Проверяем запрос к ендпоинту /ping
:
Всё хорошо.
И /err
нам ожидаемо вернёт 502:
Через 15 секунд, после выполнения первой live проверки – под будет перезапущен:
Events этого пода:
Контейнер в поде не прошёл проверку livenessProbe
, и Kubernetes перезапускает под в попытке “починить” его.
Если получится попасть на сам момент перезапуска контейнера – увидим стаус CrashLoopBackOff, а запрос к /ping
снова вернёт нам 502:
Выводы
Используем readinessProbe
для проверки того, что приложение запустилось, в данном случае – Go-бинарник начал прослушивать порт 8080, и на него можно направлять трафик, и используем livenessProbe
во время работы пода для проверки того, что приложение в нём все ещё живо.
Если приложение начинает отдавать 502 на определённые запросы – то следует поискать проблему именно в запросах, т.к. если бы была проблема в настройках Ingress/Service – получали бы 502 постоянно.
Самое важное – понимать принципиальную разницу между readinessProbe
и livenessProbe
:
- если фейлится
readinessProbe
– процесс aka контейнер в поде останется в том же состоянии, в котором был на момент сфейленой ready-проверки, но под будет отключен от трафика к нему - если фейлится
livenessProbe
– трафик на под продолжает идти, но контейнер будет перезапущен
Итак, имеем ввиду, что:
- Если
readinessProbe
не задана вообще –kubelet
будет считать, что под готов к работе, и направит к нему трафик сразу после старта пода. При этом если на запуск пода уходит минута – то клиенты, которые к нему были направлены после его запуска, будут ждать эту минуту, пока он ответит. - Если приложение ловит ошибку, которую не может обработать – оно должно завершить свой процесс, и Kubernetes сам перезапустит контейнер.
- Используем
livenessProbe
для проверки состояний, которые нельзя обработать в самом приложении, например – deadlock или бесконечный цикл, при которых контейнер не может ответить на ready-проверку. В таком случае если нетlivenessProbe
, которая может перезапустить процесс, то под будет отключен от Service – но останется в статусе Running, продолжая потреблять реурсы WorkerNode.
Ссылки по теме
- Pod Lifecycle
- Configure Liveness, Readiness and Startup Probes
- Kubernetes best practices: Setting up health checks with readiness and liveness probes
- Kubernetes Liveness and Readiness Probes Revisited: How to Avoid Shooting Yourself in the Other Foot
- Kubernetes production best practices
- Kubernetes: Best Practices for Liveness Probes