Продолжаю цикл статей по настройке и эксплуатации кластера Kubernetes. Сегодня рассмотрю отдельно настройку Ingress контроллера для публикации сервисов и приложений в кластере. Принцип его работы я кратко показал в обзорной статье по работе с кластером. Сегодня рассмотрим более подробно.
На углубленном курсе "Архитектура современных компьютерных сетей" вы с нуля научитесь работать с Wireshark и «под микроскопом» изучите работу сетевых протоколов. На протяжении курса надо будет выполнить более пятидесяти лабораторных работ в Wireshark.
Содержание:
Цели статьи
- Рассказать о принципе работы Ingress Controller в кластере Kubernetes.
- Показать, как добавлять свои сертификаты в конфигурацию ingress.
- На конкретном примере показать работу cert-manager для управления ssl/tls сертификатами. В том числе автоматическое получение сертификатов от Let's Encrypt.
- Показать, где хранятся конфигурация и логи работы ingress controller.
- Рассказать, как устанавливать дополнительные параметры 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
Проверяем, что все необходимые поды стартовали.
# 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
Все в порядке. Сертификат выпущен, 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 с сертификатом применилась. Сайт с магазином носков должен быть доступен по доменному имени. Теперь посмотрим, что у нас с сертификатами.
В момент написания статьи у меня возникли проблемы с подтверждением сертификата от 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 создает 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
А тут уже ошибка:
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
Указанный 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 с нуля или систематизировать уже имеющиеся знания можно на углубленном онлайн-курcе по администрированию MikroTik. Автор курcа – сертифицированный тренер MikroTik Дмитрий Скоромнов. Более 40 лабораторных работ по которым дается обратная связь. В три раза больше информации, чем в MTCNA.
для 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 уже не поддерживается
apiextensions.k8s.io/v1beata1 is deprecated in k8s v1.22.0 version.
https://kubernetes.io/blog/2021/07/14/upcoming-changes-in-kubernetes-1-22/#:~:text=The%20beta%20CustomResourceDefinition%20API%20(apiextensions.k8s.io/v1beta1)
Добрый день! У меня такая схема. Сервак с 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, можно ссылку на мануал?
Отработало с proxy_pass http://node1/ Теперь осталось пробросить сертификат на сервер.
Поймал такую ошибку в http-01 challenge: Hostname: hello-app-646fb5f544-prv9c
) did not match expected (QEyWgKNnMSOAznisFj-gDN._mMVhMVH_n1TTyPbw)
Добрый день, не подскажите случаем если я использую Ingress Controller - Traefik, то у него свой провайдер выпуска сертификатов или можно использовать тот же cert-manager? Просто прочитав доки складывается впечатление, что cert-manager только для Nginx
Не могу подсказать, так как не знаю. Если узнаете наверняка, прошу поделиться информацией.
По идее, все должно работать. Cert-manager хранит все сертификаты в секретах. Так что нет разницы, для какого сервиса они будут использоваться.
Спасибо за труд! Пол дня сегодня читаю ваши статьи по кубику) давно не был на вашем сайте, а тут зашёл и такая актуальная информация)
Из интересного думаю еще истио хорошо зайдет, если имеется опыт)
Не очень понял о чем речь. Что есть истио?
Mesh сервис для кубика. Очень функциональный ингресс
Приветствую!
Подскажите, можно ли через cert-manager отозвать выпущенный сертификат?
Возникла необходимость отозвать не используемый более сертификат и не получать письма об истечении его срока.
Какие сертификаты имеются ввиду? Let's Encrypt?
Да.
А зачем его отзывать? Можно просто удалить из системы, сам протухнет со временем.
Проблема в том, что когда он протухает, LE начинает слать письма-уведомления, мол, ваш сертификат протухает, срочно обновите. И чем ближе дата протухания, тем чаще шлются письма. Вот чтоб таких писем не было, надо отзывать. Левый мыл указать, к сожалению, нельзя. У CLI-версии есть команда revoke. А как ее вызвать через cert-manager - вопрос, на который гугл ответа не дает. Ну, или я как-то не так спрашиваю...
Я что-то в доках тоже не нашел инфу про отзыв. Можно сделать костыль - экспорт сертификата в файл, потом revoke через certbot и удаление из кластера. Баш скриптом это просто сделать.
"Kube Way" костыль. Подготовить образ, который умеет только отзывать сертификат. Монтировать сертификат прямо из секрета. Запускать перед удалением инфры.
Или так :)) Я еще не привык к куберу, решаю проблемы по старинке, через баш. Но мысль уже работает. Я сначала подумал, как это сделать через ансибл, потом понял, что через баш я это сделаю за 10 мин.