Хабрахабр

Kubernetes в production: сервисы

На первый взгляд задача достаточно простая: нужно развернуть кластер, написать спецификации приложений и вперед. Полгода назад мы закончили миграцию всех наших stateless сервисов в kubernetes. Больше всего вопросов у меня возникало ко всему, что касается сети. Из-за одержимости в вопросе обеспечения стабильности в работе нашего сервиса пришлось сразу начать разбираться с тем, как работает k8s и тестировать различные сценарии отказов. Один из таких "скользких" моментов — работа сервисов (Services) в kubernetes.

В документации нам говорят:

  • выкатите приложение
  • задайте liveness/readiness пробы
  • создайте сервис
  • дальше все будет работать: балансировка нагрузки, обработка отказов итд.

Давайте посмотрим, как оно работает на самом деле. Но на практике все несколько сложнее.

Немного теории

Далее я подразумеваю, что читатель уже знаком с устройством kubernetes и его терминологией, вспомним лишь, что такое сервис.

Сервис — сущность k8s, которая описывает совокупность подов и методов доступа к ним.

Например, мы запустили наше приложение:

apiVersion: apps/v1
kind: Deployment
metadata: name: webapp
spec: selector: matchLabels: app: webapp replicas: 2 template: metadata: labels: app: webapp spec: containers: - name: webapp image: defaultxz/webapp command: ["/webapp", "0.0.0.0:80"] ports: - containerPort: 80 readinessProbe: httpGet: initialDelaySeconds: 1 periodSeconds: 1

$ kubectl get pods -l app=webapp
NAME READY STATUS RESTARTS AGE
webapp-5d5d96f786-b2jxb 1/1 Running 0 3h
webapp-5d5d96f786-rt6j7 1/1 Running 0 3h

Теперь чтобы обратиться к нему, мы должны создать сервис, в котором определяем к каким именно подам мы хотим иметь доступ (selector) и по каким портам:

kind: Service
apiVersion: v1
metadata: name: webapp
spec: selector: app: webapp ports: - protocol: TCP port: 80 targetPort: 80

$ kubectl get svc webapp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
webapp ClusterIP 10.97.149.77 <none> 80/TCP 1d

Теперь мы с любой машины кластера можем обратиться к нашему сервису:

curl -i http://10.97.149.77
HTTP/1.1 200 OK
Date: Mon, 24 Sep 2018 11:55:14 GMT
Content-Length: 2
Content-Type: text/plain; charset=utf-8

Как это все работает

Очень упрощенно:

  • вы сделали kubectl apply спецификации Deployment
  • происходит магия, детали которой не важны в данном контексте
  • в результате на каких-то нодах оказались работающие поды приложения
  • раз в интервал kubelet (агент k8s на каждой ноде) выполняет liveness/readiness пробы всех запущенных на его ноде подов, результаты он отправляет в apiserver (интерфейс к мозгам k8s)
  • kube-proxy на каждой ноде получает уведомления от apiserver о всех изменениях сервисов и подов, которые участвуют в сервисах
  • kube-proxy все изменения отражает в конфигурации нижележащих подсистем (iptables, ipvs)

В iptables у нас для нашего виртуального ip 10. Для простоты рассмотрим дефолтный способ проксирования — iptables. 149. 97. 77:

-A KUBE-SERVICES -d 10.97.149.77/32 -p tcp -m comment --comment "default/webapp: cluster IP" -m tcp --dport 80 -j KUBE-SVC-BL7FHTIPVYJBLWZN

трафик уходит в цепочку KUBE-SVC-BL7FHTIPVYJBLWZN, в которой распределяется между 2 другими цепочками

-A KUBE-SVC-BL7FHTIPVYJBLWZN -m comment --comment "default/webapp:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-UPKHDYQWGW4MVMBS
-A KUBE-SVC-BL7FHTIPVYJBLWZN -m comment --comment "default/webapp:" -j KUBE-SEP-FFCBJRUPEN3YPZQT

это уже наши поды:

-A KUBE-SEP-UPKHDYQWGW4MVMBS -p tcp -m comment --comment "default/webapp:" -m tcp -j DNAT --to-destination 10.244.0.10:80
-A KUBE-SEP-FFCBJRUPEN3YPZQT -p tcp -m comment --comment "default/webapp:" -m tcp -j DNAT --to-destination 10.244.0.11:80

Тестируем отказ одного из подов

Мое тестовое приложение webapp умеет переключаться в режим "сыпь ошибками", для этого нужно сделать дернуть урл "/err".

Результаты ab -c 50 -n 20000 в середине теста дернули "/err" на одном из подов:

Complete requests: 20000
Failed requests: 3719

В целом мы выкинули "плохой" под из балансировки, но в момент переключения клиент сервиса получал ошибки. Дело тут не в конкретном количестве ошибок (их количество будет меняться в зависимости от нагрузки), а в том, что они есть. Причину ошибок достаточно легко объяснить: readiness пробы выполняются kubelet раз в секунду + еще небольшое время на распространение информации о том, что под не ответил на пробу.

Поможет ли IPVS бэкенд для kube-proxy (experimental)?

Он решает задачу оптимизации проксирования, предлагает настраиваемый алгоритм балансировки, но никак не решит проблему обработки отказов. На самом деле нет!

Как быть

Другими словами, для http нам нужен L7 балансировщик. Данную проблему может решить только балансировщик, который умеет повторные попытки (retries). Такие балансировщики для kubernetes уже во всю используются либо в виде ingress (подразумевался как точка в хода в кластер, но по большому счету делает ровно то, что нужно), либо как реализацию отдельного слоя — service mesh, например istio.

Подобные абстракции, на мой взгляд, помогают в тех случаях, когда нужно часто конфигурировать большое количество сервисов. У себя в production мы не стали пока использовать ни ingress, ни service mesh из-за дополнительной сложности. Вы будете тратить дополнительное время, чтобы понять, как настроить рертаи, таймауты для конкретного сервиса. Но при этом вы "платите" управляемостью и простой инфраструктуры.

Как делаем мы

У таких сервисов нет виртуального ip и соответственно в их работе kube-proxy и iptables не участвует. Мы используем headless сервисы k8s. Для каждого такого сервиса можно получить список живых подов либо через DNS, либо через API.

Evoy периодически получает актуальных список подов для всех нужных сервисов через DNS, и самое главное умеет делать повторные попытки запросов на другие поды в случае ошибки. Для приложений, которые взаимодействуют с другими сервисами мы делаем sidecar контейнер с envoy. Так как потребление ресурсов этим прокси достаточно небольшое, мы решили использовать его именно в варианте sidecar контейнера. Можно запустить его в виде DaemonSet на каждой ноде, но тогда при отказе этого инстанса, перестали бы работать все приложения, которые его используют.

Возможно этот баланс изменится, и мы начнем использовать что-нибудь типа istio. Это по сути ровно то, что делает istio, но в нашем случае баланс сместился в сторону простоты (не нужно изучать istio, нарываться на его баги).

Поддержка мониторинга k8s в нашем сервисе на подходе, следите за новостями! У нас в okmeter.io kubernetes определенно прижился, и мы верим в его дальнейшее распространение.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть