ADG Оставить заявку
Блог DevOps 4 мин чтения

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 и несколькими виртуальными хостами одновременно. На стейджинге всё гладко, но там нагрузки нет.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.