Настройка Ingress controller в Kubernetes

Продолжаю цикл статей по настройке и эксплуатации кластера Kubernetes. Сегодня рассмотрю отдельно настройку Ingress контроллера для публикации сервисов и приложений в кластере. Принцип его работы я кратко показал в обзорной статье по работе с кластером. Сегодня рассмотрим более подробно.

Онлайн-курс по устройству компьютерных сетей.

На углубленном курсе "Архитектура современных компьютерных сетей" вы с нуля научитесь работать с Wireshark и «под микроскопом» изучите работу сетевых протоколов. На протяжении курса надо будет выполнить более пятидесяти лабораторных работ в Wireshark.

Цели статьи

  1. Рассказать о принципе работы Ingress Controller в кластере Kubernetes.
  2. Показать, как добавлять свои сертификаты в конфигурацию ingress.
  3. На конкретном примере показать работу cert-manager для управления ssl/tls сертификатами. В том числе автоматическое получение сертификатов от Let's Encrypt.
  4. Показать, где хранятся конфигурация и логи работы ingress controller.
  5. Рассказать, как устанавливать дополнительные параметры nginx в ingress контроллере.

Принцип работы Ingress контроллера

Сразу важное замечание, чтобы потом не было путаницы.

  • Ingress - сущность кластера kubernetes, в которой вы описываете конфигурацию для ingress controller.
  • Ingress Controller - занимается непосредственно обработкой траффика. Его конфигурация формируется из всех ingress, которые есть в кластере.

Реализация ingress controller может быть на базе различного софта - nginx, haproxy, traefik и т.д. Стандартно Kubernetes использует контроллер на базе Nginx. В моей статье будет именно он. Это самый простой и популярный вариант. В основном все его используют.

В проде обычно ingress контроллеры вешают на отдельные узлы с внешними ip адресами и используют в качестве точки подключения к сервисам. Ingress controller может как сам принимать на себя весь трафик, так и работать за каким-то внешним балансером. Конкретную реализацию выбирать вам. Так как под капотом там Nginx, каких-то особых проблем с безопасностью при размещении Ingress напрямую в интернет нету.

Нужно понимать, что ingress работает на 7-м уровне сетевой модели OSI. Если вам нужен 3-й уровень, используйте другие способы публикации приложений:

  • ClusterIP
  • NodePort
  • LoadBalancer
  • ExternalName
  • ExternalIP

Так как внутри Ingress контроллера обычный Nginx, вы можете очень гибко управлять распределением запросов по доменам, location и т.д. Вам доступно все, что умеет nginx в функционале proxy_pass.

Сразу приведу ссылку на официальную документацию по ingress controller на базе nginx - https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/. Там есть все то, о чем я расскажу ниже.

На простом примере работу Ingress контроллера я уже показал ранее в статье Работа с кластером Kubernetes. Здесь же я рассмотрю его настройку подробнее, особое внимание уделив работе в связке с cert-manager.

SSL/TLS сертификаты в Ingress

Начну с одного из основного функционала - настройка SSL/TLS сертификатов. Сейчас без них никуда. Для того, чтобы использовать сертификаты в конфигурации Ingress, их нужно загрузить в кластер Kubernetes. Сертификаты для Ingress хранятся в секретах Кубера. Создать такой секрет можно следующим образом.

# kubectl create secret tls my-tls --key my-tls.key --cert my-tls.cert

Дальше этот секрет можно использовать в конфигурации ingress, например вот так.

apiVersion: extensions/v1beta1
 kind: Ingress
 metadata:
   name: ingress-my-tls
   annotations:
     ingress.kubernetes.io/ssl-redirect: "true"

 spec:
   tls:
   - hosts:
     - ingress.example.com
     secretName: my-tls
   rules:
   - host: ingress.example.com
     http:
       paths:
       - path: /
         backend:
           serviceName: nginx
           servicePort: 80

Если у вас есть Wildcard-сертификат, вы можете добавить его в kubernetes и установить дефолтным для всех доменов в ingress, где явно не указан персональный сертификат. Для этого необходимо добавить дополнительный параметр default-ssl-certificate в deployment для ingress контроллера. О том, как это сделать, подробно рассказано в статье на success.docker.com.

Cert-manager

Cert-manager - утилита в кластере Kubernetes, которая умеет автоматически получать и продлевать сертификаты от различных удостоверяющих центров, в том числе от бесплатного Let's Encrypt. При этом она интегрируется с Ingress Controller.

В своей работе cert-manager использует CustomResourceDefinitions, которые нужно будет предварительно добавить в API кубернетиса - Issuer, ClusterIssuer, Certificate. Давайте установим cert-manager в свой кластер Kubernetes. Я буду использовать для этого helm и инструкцию с официального сайта.

# kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.11/deploy/manifests/00-crds.yaml
# kubectl create namespace cert-manager
# helm repo add jetstack https://charts.jetstack.io
# helm repo update
# helm install cert-manager --namespace cert-manager --version v0.11.0 jetstack/cert-manager

Установка Cert-Manager в Kubernetes

Проверяем, что все необходимые поды стартовали.

# kubectl get pods --namespace cert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-584d89d5f9-w46jh              1/1     Running   0          3m25s
cert-manager-cainjector-6df9c84978-lcnb4   1/1     Running   0          3m25s
cert-manager-webhook-69c9b44f6-rnkwz       1/1     Running   0          3m25s

Теперь проверим, что cert-manager нормально установился и работает. Для этого можно выпустить самоподписанный сертификат. Создаем манифест test-resources.yaml.

apiVersion: v1
kind: Namespace
metadata:
  name: cert-manager-test
---
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: test-selfsigned
  namespace: cert-manager-test
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: selfsigned-cert
  namespace: cert-manager-test
spec:
  commonName: example.com
  secretName: selfsigned-cert-tls
  issuerRef:
    name: test-selfsigned

Применяем.

# kubectl apply -f test-resources.yaml
namespace/cert-manager-test created
issuer.cert-manager.io/test-selfsigned created
certificate.cert-manager.io/selfsigned-cert created

Проверяем, что получилось.

# kubectl describe certificate -n cert-manager-test

Выпуск сертификата в кластере Kubernetes

Все в порядке. Сертификат выпущен, cert-manager работает. Посмотреть информацию о сертификате можно командой.

# kubectl -n cert-manager-test get certificate selfsigned-cert -o yaml

Описание секрета, где хранится сертификат.

# kubectl -n cert-manager-test describe secret selfsigned-cert-tls

А вот так можно увидеть сам сертификат из секрета.

# kubectl -n cert-manager-test get secret selfsigned-cert-tls -o yaml

Для того, чтобы узнать, кем был выпущен сертификат, нужно посмотреть информацию об issuer.

# kubectl -n cert-manager-test get issuer
NAME              AGE
test-selfsigned   8m16s

Напоминаю, что сущности Issuer, Certificat по-умолчанию отсутствуют в Kubernetes. Мы их добавили через CustomResourceDefinitions перед установкой cert-manager.

Мы убедились, что все нормально работает. Почистим за собой все тестовые сущности, удалив namespace, где работали.

# kubectl delete ns cert-manager-test
namespace "cert-manager-test" deleted

Переходим к выпуску сертификатов с помощью Let's Encrypt. Для этого добавляем нового ClusterIssuer с помощью манифеста clusterissuer.yaml.

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: root@serveradmin.ru
    privateKeySecretRef:
      name: letsencrypt
    solvers:
    - http01:
        ingress:
          class: nginx
# kubectl apply -f clusterissuer.yaml
clusterissuer.cert-manager.io/letsencrypt created

Давайте теперь подключим сертификат от Let's Encrypt к магазину носков, который я рассматривал в статье по работе с кластером. Напомню, что взять его можно по ссылке - https://github.com/microservices-demo/microservices-demo. Там в директории /deploy/kybernetes есть готовый complete-demo.yaml, где описаны все сущности kubernetes для установки. Очень удобно использовать для тестов. Единственный нюанс - я изменил namespace sock-shop, просто убрав его, чтобы поставить магазин в namespace default, чтобы потом не указывать постоянно namespace в командах. Это просто ради удобства в написании статьи и отладки, чтобы было быстрее. Вы можете этого не делать.

Качаем, редактируем и применяем манифест.

# wget https://raw.githubusercontent.com/microservices-demo/microservices-demo/master/deploy/kubernetes/complete-demo.yaml
# kubectl apply -f complete-demo.yaml

У вас должны подняться поды и все остальное от этого магазина. В частности нас интересуют service.

Запуск тестового приложения в кластере

Для сервиса front-end настраиваем ingress, сразу выпуская сертификат.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-socks-tls
  annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt"
spec:
  rules:
  - host: kuber.zeroxzed.ru
    http:
      paths:
      - backend:
          serviceName: front-end
          servicePort: 80
  tls:
  - hosts:
    - kuber.zeroxzed.ru
    secretName: socks-tls-2

Применяем и проверяем.

# kubectl apply -f ingress-sock-tls.yaml
# kubectl describe ingress ingress-socks-tls

Настройка Ingress Controller

Настройка ingress с сертификатом применилась. Сайт с магазином носков должен быть доступен по доменному имени. Теперь посмотрим, что у нас с сертификатами.

В момент написания статьи у меня возникли проблемы с подтверждением сертификата от lets encrypt, хотя ранее по такой же схеме я успешно с ними работал. В процессе разбирательств я выяснил, в чем проблема. Оказывается, перед тем, как отправить запрос на подтверждение в lets encrypt, cert-manager сам выполняет проверку домена по доменному имени. У меня кластер был без внешнего IP, скрыт за шлюзом, с которого просто выполнялся проброс портов через NAT. При такой схеме изнутри кластера при обращении к внешнему доменному имени, запрос не попадал на pod с ingress controller.

Чтобы все эта конструкция работала правильно с точки зрения cert-manager и его проверок, надо было либо настраивать корректно перенаправление портов не только с внешнего интерфейса внутрь кластера, но и с внутренней сети обратно внутрь на ноду с ingress controller. Либо настраивать внутренний dns сервер, который по внешнему доменному имени будет возвращать локальный ip адрес ноды с ingress controller.

Мне не захотелось всем этим заниматься в тестовом кластере. Но это даже хорошо, так как я дальше сразу покажу, как разбирать проблемы и куда смотреть в случае, если они появятся. В общем случае того, что мы уже сделали достаточно для того, чтобы сертификаты от Let's Encrypt заработали в кластере Kubernetes. Итак, проверяем выпущенный сертификат.

# kubectl get certificate
NAME          READY   SECRET        AGE
socks-tls-2   False   socks-tls-2   73m

Он выпущен, но по какой-то причине не готов. Смотрим его описание.

# kubectl describe certificate socks-tls-2

Проверка сертификата

Посмотрим внимательно на запрос.

# kubectl describe CertificateRequest socks-tls-2-2550861952

Детали запроса

Проверяем order, который в статусе pending.

# kubectl describe order socks-tls-2-2550861952-2613688448

Статус Order

Order создает Challenge, который должен быть виден после создания order в Events, но у меня уже нет инфы. Смотрю на Challendge через kubectl.

# kubectl get challenge
NAME                                           STATE     DOMAIN              AGE
socks-tls-2-2550861952-2613688448-2720565039   pending   kuber.zeroxzed.ru   98m
# kubectl describe Challenge socks-tls-2-2550861952-2613688448-2720565039

Статус Challendge

А тут уже ошибка:

Waiting for http-01 challenge propagation: wrong status code '404', expected '200'

Суть ошибки ясна. Cert-manager хочет проверить url, но вместо кода ответа 200, получает 404. Смотрим, что конкретно он проверяет. Для этого смотрим список ingress в кластере.

# kubectl get ingress
NAME                        HOSTS               ADDRESS     PORTS     AGE
cm-acme-http-solver-4rq89   kuber.zeroxzed.ru   10.1.4.39   80        101m
ingress-socks-tls           kuber.zeroxzed.ru   10.1.4.39   80, 443   134m

Ингресс cm-acme-http-solver-4rq89 поднял временно cert-manager, чтобы проверить домен. Смотрим внимательно на этот ingress.

# kubectl describe ingress cm-acme-http-solver-4rq89

Настройка сертификатов let's encrypt в kubernetes

Указанный url должен успешно работать как снаружи, так и изнутри кластера. Без этого cert-manager и lets encrypt не смогут подтвердить сертификат.

Сам выпущенный сертификат можно посмотреть в секрете, в котором он хранится (формат base64).

# kubectl get secret socks-tls-2 -o yaml

На этом по Cert-Manager в кластере Kubernetes все. Надеюсь, у меня получилось понятно объяснить и показать как с ним работать.

Логи и конфиг Ingress Controller

Для того, чтобы дебажить работу ingress controller, надо как минимум иметь доступ к его логам и файлу конфигурации. По своей сути это просто docker образ с nginx внутри. Посмотреть логи можно следующим образом.

# kubectl logs ingress-nginx-controller-twwpb -n ingress-nginx

Перед этим посмотрите, какое имя и в каком namespace работает интересующий вас pod с ingress controller на борту. По-умолчанию он в ingress-nginx будет запущен.

# kubectl get pod -A | grep ingress-nginx

Логи можно направить куда-нибудь в файл, чтобы было удобно читать и анализировать. Конфигурацию nginx внутри ingress controller можно посмотреть следующим образом. Ее лучше тоже сразу в файл выгружать, чтобы удобнее было читать.

# kubectl exec -it ingress-nginx-controller-twwpb -n ingress-nginx cat /etc/nginx/nginx.conf > ~/ingress-nginx.conf

Посмотрите на конфиг. Там по сути обычный nginx. Можете разобраться в проблеме, если считаете, что что-то работает не так, как вы ожидаете.

Можно зайти в сам контейнер с ingress controller и там посмотреть все сертификаты в исходном виде. Например вот так.

# kubectl exec -it ingress-nginx-controller-twwpb -n ingress-nginx cat /etc/ingress-controller/ssl/default-fake-certificate.pem > ~/default-fake-certificate.pem

Это дефолтный сапоподписанный сертификат, который выпускает ingress, когда вы включаете в нем tls. Если у домена включен tls, но по какой-то причине не работает его сертификат, будет подставлен дефолтный.

Конфигурация Nginx для Ingress Controller

Как я уже говорил, в моем примере под капотом Ingress контроллера работает обычный Nginx с помощью специально подготовленного DaemonSet в неймспейсе ingress-nginx.

# kubectl get all -n ingress-nginx
NAME                                 READY   STATUS    RESTARTS   AGE
pod/ingress-nginx-controller-twwpb   1/1     Running   4          75d
NAME                                      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                          AGE
daemonset.apps/ingress-nginx-controller   1         1         1       1            1           node-role.kubernetes.io/ingress=true   75d

Для того, чтобы задать глобальные параметры для ingress можно использовать configmap. Давайте для примера изменим некоторые глобальные параметры ingress. Напомню, что он запущен в namespace ingress-nginx. Добавим туда configmap для ingress-nginx и применим его.

kind: ConfigMap
apiVersion: v1
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
data:
  proxy-connect-timeout: "222"
  proxy-read-timeout: "222"
  proxy-send-timeout: "222"
# kubectl apply -f nginx-config.yaml
configmap/ingress-nginx configured

Проверяем конфигурацию ingress, выгрузив конфиг nginx в файл.

# kubectl exec -it ingress-nginx-controller-twwpb -n ingress-nginx cat /etc/nginx/nginx.conf > ~/ingress-nginx.conf

Во всех виртуальных хостах должны измениться параметры:

proxy_connect_timeout 222s;
proxy_send_timeout 222s;
proxy_read_timeout 222s;

Так же конфигурацию nginx можно задавать с помощью аннотаций. Возьмем для примера тот же магазин носок и его ingress манифест. Добавим туда аннотации.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-socks-tls
  annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "301"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "301"
spec:
  rules:
  - host: kuber.zeroxzed.ru
    http:
      paths:
      - backend:                                       
          serviceName: front-end                       
          servicePort: 80                              
  tls:                                                 
  - hosts:                                             
    - kuber.zeroxzed.ru                                
    secretName: socks-tls-2

Применяем.

# kubectl apply -f ingress-sock-tls.yaml 
ingress.extensions/ingress-socks-tls configured

Проверяем конфигурацию nginx. В виртуальном хосте из манифеста ingress должны установиться указанные параметры nginx.

proxy_connect_timeout 222s;
proxy_send_timeout 301s;
proxy_read_timeout 301s;

Если что-то пойдет не так, смотрите логи пода с Ingress Controller. Там будут видны ошибки. Вот пример ошибок, когда я в манифестах пытался сразу же поставить значения с секундами.

W1203 13:11:07.756139       6 configmap.go:339] unexpected error merging defaults: 2 error(s) decoding:

* cannot parse 'proxy-connect-timeout' as int: strconv.ParseInt: parsing "20s": invalid syntax
* cannot parse 'proxy-read-timeout' as int: strconv.ParseInt: parsing "20s": invalid syntax

Секунды проставляются автоматом при передаче значений в nginx. В манифестах их указывать не нужно. Я так понимаю, это особенность установки ingress controller через Kubespray. Если будете ставить через Helm или как-то еще, то это может работать по-другому. Например, во многих примерах в интернете я видел, что параметры nginx для ingress controller в манифестах указывают в виде 20s, 30m и т.д.

Заключение

Не понравилась статья и хочешь научить меня администрировать? Пожалуйста, я люблю учиться. Комментарии в твоем распоряжении. Расскажи, как сделать правильно!

На этом рассказ про Ingress завершаю. Постарался описать все основные моменты, с которыми сталкиваешься при начальной настройке Ingress Controller. Этой базы достаточно, чтобы опубликовать реальный интернет ресурс в кластере Kubernetes.

Другие статьи по k8s:

Напоминаю, что эта статья является частью единого и последовательного цикла статей по настройке и управлению кластером Kubernetes.

Углубленный онлайн-курс по MikroTik.

Научиться настраивать MikroTik с нуля или систематизировать уже имеющиеся знания можно на углубленном онлайн-курcе по администрированию MikroTik. Автор курcа – сертифицированный тренер MikroTik Дмитрий Скоромнов. Более 40 лабораторных работ по которым дается обратная связь. В три раза больше информации, чем в MTCNA.

Помогла статья? Подписывайся на telegram канал автора

Анонсы всех статей, плюс много другой полезной и интересной информации, которая не попадает на сайт.

Автор Zerox

Владимир, системный администратор, автор сайта. Люблю настраивать сервера, изучать что-то новое, делиться знаниями, писать интересные и полезные статьи. Открыт к диалогу и сотрудничеству. Если вам интересно узнать обо мне побольше, то можете послушать интервью. Запись на моем канале - https://t.me/srv_admin/425 или на сайте в контактах.

19 комментариев

  1. Николай

    для kubectl версии 1.23.4

    сработало :
    kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.6.0/cert-manager.yaml

    Иначе получал ошибку:

    unable to recognize "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.11/deploy/manifests/00-crds.yaml": no matches for kind "CustomResourceDefinition" in version "apiextensions.k8s.io/v1beta1"

    Так как "apiextensions.k8s.io/v1beta1" для версий больше 1.21 уже не поддерживается

  2. Дмитрий

    Добрый день! У меня такая схема. Сервак с proxmox смотрит в инет, плюс plesk с сайтами. В proxmox master, node1, node2. Настроен NAT для VM iptables. В плеске сделал *.apps.example.com proxy_pass http://node1:80/ Прокси работает - ingress отдает страницы. Проблема с сертификатом как у Вас в статье - "Waiting for http-01 challenge propagation: wrong status code '404', expected '200' cert-manager". Как пофиксить проброс или DNS, можно ссылку на мануал?

  3. Евгений

    Добрый день, не подскажите случаем если я использую Ingress Controller - Traefik, то у него свой провайдер выпуска сертификатов или можно использовать тот же cert-manager? Просто прочитав доки складывается впечатление, что cert-manager только для Nginx

    • Не могу подсказать, так как не знаю. Если узнаете наверняка, прошу поделиться информацией.

    • По идее, все должно работать. Cert-manager хранит все сертификаты в секретах. Так что нет разницы, для какого сервиса они будут использоваться.

  4. Аноним

    Спасибо за труд! Пол дня сегодня читаю ваши статьи по кубику) давно не был на вашем сайте, а тут зашёл и такая актуальная информация)
    Из интересного думаю еще истио хорошо зайдет, если имеется опыт)

  5. Михаил

    Приветствую!

    Подскажите, можно ли через cert-manager отозвать выпущенный сертификат?
    Возникла необходимость отозвать не используемый более сертификат и не получать письма об истечении его срока.

    • Какие сертификаты имеются ввиду? Let's Encrypt?

        • А зачем его отзывать? Можно просто удалить из системы, сам протухнет со временем.

          • Михаил

            Проблема в том, что когда он протухает, LE начинает слать письма-уведомления, мол, ваш сертификат протухает, срочно обновите. И чем ближе дата протухания, тем чаще шлются письма. Вот чтоб таких писем не было, надо отзывать. Левый мыл указать, к сожалению, нельзя. У CLI-версии есть команда revoke. А как ее вызвать через cert-manager - вопрос, на который гугл ответа не дает. Ну, или я как-то не так спрашиваю...

            • Я что-то в доках тоже не нашел инфу про отзыв. Можно сделать костыль - экспорт сертификата в файл, потом revoke через certbot и удаление из кластера. Баш скриптом это просто сделать.

              • Михаил

                "Kube Way" костыль. Подготовить образ, который умеет только отзывать сертификат. Монтировать сертификат прямо из секрета. Запускать перед удалением инфры.

                • Или так :)) Я еще не привык к куберу, решаю проблемы по старинке, через баш. Но мысль уже работает. Я сначала подумал, как это сделать через ансибл, потом понял, что через баш я это сделаю за 10 мин.

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

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

Нажимая кнопку "Отправить комментарий" Я даю согласие на обработку персональных данных.
Используешь Telegram? Подпишись на канал автора →
This is default text for notification bar