Kubernetes 1.2: Ingress-контроллер и HPA вместо ручных правок nginx
Обновили кластер до k8s 1.2 и подняли nginx-ingress-controller. Теперь HTTP-роутинг - это ресурсы k8s, а не конфиг nginx на отдельной VM.
Kubernetes 1.2 вышел в марте 2016 с Ingress API, улучшенным HPA и поддержкой кластеров до 1000 нод
Kubernetes 1.2 вышел на прошлой неделе, и мы довольно быстро обновили кластер на сопровождении. Не потому что торопились, - там есть одна конкретная вещь, которую ждали: стабильный Ingress API. В январе писали, что следующий шаг - ingress-контроллер, и вот он.
Расскажем что именно обновили, что заработало лучше, и зачем вообще нужен Ingress если уже есть Service типа LoadBalancer.
Что было до Ingress
До 1.2 схема входящего трафика выглядела так: снаружи кластера стоит VM с nginx, в конфиге которого написано что api.example.com идёт на порт 30081, а app.example.com - на 30082. NodePort-ы. Работает, но:
- добавляешь новый сервис - правишь nginx-конфиг на VM вне кластера
- делаешь rolling update - nginx ничего не знает, может временно слать трафик на умирающий под
- хочешь TLS termination - настраиваешь вручную на той же VM
- хочешь canary-деплой по заголовку - добавляешь хитрый location в nginx
По итогу nginx-конфиг становится источником истины наравне с манифестами k8s, только неверсионируемым и требующим доступа к отдельному хосту. Два места где нужно делать изменения - это уже плохо.
Ingress как k8s-ресурс
В 1.2 Ingress стал официальным API-объектом. Идея простая: описываешь правила роутинга в манифесте внутри кластера, а Ingress-контроллер их читает и применяет. Мы взяли nginx-ingress-controller - отдельный pod, который следит за Ingress-ресурсами через API и динамически перегенерирует nginx-конфиг внутри себя.
Типовой Ingress выглядит так:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: app-ingress
namespace: prod
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
backend:
serviceName: app-api
servicePort: 80
- host: app.example.com
http:
paths:
- path: /
backend:
serviceName: app-frontend
servicePort: 80
Применяешь манифест - nginx-ingress-controller через несколько секунд перечитывает конфиг и трафик идёт куда надо. Никаких SSH на отдельный хост, никаких ручных nginx -s reload.
TLS подключается через Secret с сертификатом:
spec:
tls:
- hosts:
- api.example.com
secretName: api-tls-secret
Secret создаётся отдельно (kubectl create secret tls) и хранится в namespace-е. Сертификат из Let's Encrypt, который мы автоматизировали в январе, теперь попадает в кластер через CI и больше не лежит на отдельном сервере.
HPA в 1.2: появились аннотации для cooldown
HorizontalPodAutoscaler был и в 1.1, но в 1.2 добавили несколько флагов контроллера для управления cooldown-периодами - время между scale-up и scale-down теперь можно регулировать. По умолчанию scale-down происходит через 5 минут после того как нагрузка спала. Для нашего кейса с непредсказуемыми пиками это важно - не хочется чтобы кластер агрессивно убивал поды сразу после пика.
Конфигурация HPA не изменилась принципиально, но поведение стало предсказуемее. По метрикам CPU всё работало и раньше, разница заметна именно в нервных сценариях с быстро меняющейся нагрузкой.
Обновление кластера
Обновляли по стандартной процедуре: сначала мастер, потом ноды по одной через drain. Версия 1.1 -> 1.2 прошла без неожиданностей примерно за 40 минут с учётом ожидания пересоздания подов. Downtime был нулевым - rolling update держал сервис живым пока мы обновляли ноды.
Один момент: nginx-ingress-controller - это отдельный компонент, который надо деплоить самому. Он не идёт в комплекте с k8s. Взяли официальный образ с GitHub kubernetes/ingress, подняли как Deployment с двумя репликами в namespace kube-system.
Что изменилось в итоге
Роутинг теперь декларативный. Новый сервис - новый Ingress-манифест в том же репозитории что и Deployment. Ревью через pull request, история в git, откат через kubectl apply предыдущей версии.
Внешняя nginx-VM больше не нужна. Сэкономили один хост в инфраструктуре клиента. Не принципиально в деньгах, но убрать лишнюю движущуюся часть - всегда хорошо.
Healthcheck-интеграция работает. nginx-ingress-controller смотрит на readiness пробы подов и не шлёт трафик на те, что ещё не готовы. Это то, чего не было в ручном nginx снаружи - он просто бил по NodePort независимо от состояния пода.
Есть одна шероховатость: аннотации в Ingress-манифесте пока единственный способ задавать nginx-специфичные параметры (таймауты, proxy_read_timeout и т.д.). Синтаксис через аннотации - не самый удобный способ работать с конфигом, но альтернативы в рамках текущего API нет. Принимаем как данность.
Следующее на очереди - посмотреть как nginx-ingress-controller ведёт себя под реальной нагрузкой с TLS и несколькими виртуальными хостами одновременно. На стейджинге всё гладко, но там нагрузки нет.