Перейти к основному содержимому

Домен, HTTPS и Ingress в облаке

Презентация​

Ссылка на презентацию
Последняя редакция: 06.10.26

Теория​

Что нужно заранее​

Эта статья продолжает 06-K8s-cloud-cluster - предполагается, что кластер оттуда уже создан, yc настроен, а kubectl смотрит на облачный контекст (yc-conduit-cluster)
Дальше по порядку - настоящий доверенный сертификат (cert-manager), делегирование домена, DNS-записи без ручных правок (external-dns) и Ingress Controller от Yandex Cloud - gwin поверх Application Load Balancer, чтобы манифесты из 05-K8s-networking переехали в облако с минимальными правками
Все компоненты ставятся через Marketplace в консоли, а не через kubectl apply

Настоящий HTTPS: cert-manager и Cloud DNS​

В 05-K8s-networking ClusterIssuer с selfSigned работал офлайн, но браузеры такому сертификату не доверяют - для настоящего, доверенного сертификата через ACME нужен был реальный внешний IP, которого не было у minikube
На облачном кластере внешний IP есть у Service типа LoadBalancer и у балансировщика под Ingress, а домен можно завести в Yandex Cloud DNS - значит, доступен и настоящий ACME-сертификат

ACME подтверждает владение доменом двумя способами (см. также 05-K8s-networking) - HTTP01 (ACME-сервер стучится на домен по 80 порту) или DNS01 (ACME-сервер проверяет TXT-запись _acme-challenge.<domain>)
Для DNS01 cert-manager должен сам уметь создавать и удалять эту запись в Cloud DNS - отсюда ещё один сервисный аккаунт

Домен и перенос NS​

Для ACME DNS01 мало завести зону в Cloud DNS - домен должен реально резолвиться через неё, а для этого его нужно делегировать: заменить NS-записи домена у регистратора (там, где он куплен) на серверы имён Yandex Cloud

Зона создаётся одной командой:

yc dns zone create \
--name conduit-zone \
--zone example.com. \
--public-visibility

--zone - сам домен с точкой на конце (FQDN-нотация), --name - внутреннее имя ресурса в облаке, на которое дальше ссылаются и cert-manager, и external-dns

В отличие от сети и группы безопасности из 06-K8s-cloud-cluster, здесь не нужно вытаскивать никакой id переменной - у Yandex Cloud DNS одни и те же серверы имён для любой публичной зоны в любом каталоге:

ns1.yandexcloud.net.
ns2.yandexcloud.net.

Их и нужно прописать у регистратора домена вместо текущих NS-записей
Домен, купленный через саму Yandex Cloud, делегируется на эти серверы автоматически - шаг нужен только для домена, купленного у стороннего регистратора

Перенос NS не мгновенный - резолверы по всему миру кешируют NS-записи по TTL, обычно это часы, но Yandex Cloud в своей документации отдельно предупреждает, что может растянуться вплоть до 72 часов
Если делегирование планируется прямо на паре - его стоит запустить заранее, а не в процессе занятия: пока перенос не завершился, TXT-запись от cert-manager для DNS01 просто не видна снаружи, и выпуск сертификата будет падать без явной связи с настоящей причиной

Проверить, что делегирование прошло, можно без захода в панель регистратора:

dig NS example.com +short

(если dig не стоит - на macOS brew install bind, на Linux apt install dnsutils или dnf install bind-utils)

Если в ответе уже ns1.yandexcloud.net./ns2.yandexcloud.net. - домен делегирован, можно переходить к установке cert-manager
Если всё ещё старые серверы регистратора - подождать и проверить повторно, выпуск сертификата смысла пробовать ещё нет

Что нужно заранее

  • Публичная DNS-зона в Cloud DNS, в том же каталоге, что и кластер (команда и делегирование - в разделе выше)
  • Сервисный аккаунт с ролью dns.editor на этот каталог
  • Авторизованный ключ этого аккаунта в файле key.json

Всё это делается тремя командами yc (имя аккаунта любое, dns-editor-sa взято для примера)

yc iam service-account create --name dns-editor-sa

yc resource-manager folder add-access-binding \
--id "$(yc config get folder-id)" \
--service-account-name dns-editor-sa \
--role dns.editor

yc iam key create \
--service-account-name dns-editor-sa \
--format json \
--output key.json

key.json - секрет, дающий право менять DNS-записи в каталоге, коммитить его в git нельзя

Документация к вебхуку просит ещё два правила исходящего трафика - TCP 10250 для группы безопасности кластера и TCP 443 для группы безопасности узлов, оба на 0.0.0.0/0
У нашей conduit-sg из 06-K8s-cloud-cluster исходящий трафик уже открыт целиком (egress с protocol=any на 0.0.0.0/0), так что отдельно ничего добавлять не нужно - но если группа безопасности настраивалась иначе, эти правила стоит проверить первым делом

cert-manager здесь ставится не kubectl apply, а вручную через Marketplace в консоли Yandex Cloud

  1. Открыть кластер → вкладка "Другое" → Marketplace
  2. Найти "cert-manager с плагином Yandex Cloud DNS ACME webhook" и запустить установку
  3. Заполнить параметры приложения
    • пространство имён - новое, например cert-manager-dns-space
    • имя приложения - например cert-manager
    • ключ сервисного аккаунта - содержимое файла key.json
    • идентификатор каталога - id каталога, в котором лежит DNS-зона
    • email для уведомлений Let's Encrypt
    • адрес сервера Let's Encrypt - боевой https://acme-v02.api.letsencrypt.org/directory или staging https://acme-staging-v02.api.letsencrypt.org/directory
  4. Запустить установку и дождаться статуса Deployed

Для отладки удобнее staging - у боевого сервера Let's Encrypt жёсткие лимиты на выпуск сертификатов, у staging они намного мягче
Цена - браузеры сертификату от staging не доверяют, как и selfSigned из 05-K8s-networking, так что когда цепочка отлажена, адрес сервера нужно сменить на боевой

Проверить, что вебхук поднялся - под должен дойти до Running

kubectl get pods --namespace=cert-manager-dns-space -l app=cert-manager-webhook-yandex -w
# Ctrl+C, когда статус Running

Установка сама создаёт ClusterIssuer с DNS01-решателем через вебхук acme.yandex.cloud - руками писать Issuer/ClusterIssuer, как в 05-K8s-networking, тут не нужно

# создаётся автоматически при установке из Marketplace
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: yc-clusterissuer
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: you@example.com
privateKeySecretRef:
name: letsencrypt-key
solvers:
- dns01:
webhook:
groupName: acme.yandex.cloud
solverName: yandex-cloud-dns

Дальше сертификат запрашивается как обычно - явным Certificate либо аннотацией cert-manager.io/cluster-issuer: yc-clusterissuer на Ingress, в точности как в 05-K8s-networking, только на реальном домене и с боевым (не staging) сервером ACME

Явный Certificate на реальный домен выглядит так
Secret с сертификатом создаётся в том же пространстве имён, что и сам Certificate, поэтому оно должно совпадать с пространством имён Ingress, который этот Secret потом читает

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: domain-name
namespace: <пространство-имён-ingress>
spec:
secretName: domain-name-secret
issuerRef:
name: yc-clusterissuer
kind: ClusterIssuer
dnsNames:
- <домен>
kubectl apply -f certificate.yaml
kubectl get certificate

Когда сертификат выпущен, в колонке READY будет True

external-dns: DNS-записи без ручных правок​

Домен есть, сертификат есть - остаётся не забывать руками добавлять A-запись в Cloud DNS каждый раз, когда меняется внешний IP Ingress/LoadBalancer
external-dns делает это сам - следит за Ingress и Service в кластере и синхронизирует записи в Cloud DNS автоматически

Нужен тот же сервисный аккаунт с ролью dns.editor, что заводили для cert-manager, и его key.json - отдельный аккаунт ради external-dns заводить не нужно

В Marketplace есть два приложения с похожими названиями
Актуальное - "ExternalDNS с Webhook Yandex Cloud DNS", а прежнее "ExternalDNS c плагином для Yandex Cloud DNS" издатель больше не поддерживает и не обновляет, ставить нужно первое

Установка вручную через Marketplace в консоли

  1. Открыть кластер → вкладка "Другое" → Marketplace
  2. В разделе "Доступные для установки приложения" выбрать "ExternalDNS с Webhook Yandex Cloud DNS" и нажать "Перейти к установке"
  3. Заполнить параметры приложения
    • Пространство имён - новое, например externaldns-space
      С пространством имён по умолчанию ExternalDNS может работать некорректно
    • Название приложения - любое
    • Ключ сервисной учётной записи - содержимое key.json, у аккаунта должна быть роль dns.editor
    • ID каталога - каталог, в котором лежит зона Cloud DNS
  4. Нажать "Установить" и дождаться статуса Deployed

Как external-dns узнаёт, какие записи создавать

  • Для Ingress - ничего дополнительно не нужно, хосты из spec.rules[].host подхватываются сами
  • Для Service типа LoadBalancer - нужна аннотация с именем записи, а TTL записи в секундах задаётся отдельной аннотацией external-dns.alpha.kubernetes.io/ttl
apiVersion: v1
kind: Service
metadata:
annotations:
external-dns.alpha.kubernetes.io/hostname: myapp.example.com
external-dns.alpha.kubernetes.io/ttl: "300"
spec:
type: LoadBalancer

gwin: Ingress поверх Application Load Balancer​

Смысл переезда в облако - чтобы манифесты из 05-K8s-networking работали с минимальными правками
В облаке для этого есть gwin - официальный контроллер Yandex Cloud, который по Ingress (и по Gateway/HTTPRoute, тому самому Gateway API из 05-K8s-networking) поднимает Application Load Balancer (ALB) - настоящий managed L7-балансировщик
Yandex Cloud рекомендует его вместо прежних отдельных контроллеров, ALB Ingress Controller и контроллера Gateway API

cert-manager и external-dns от контроллера не зависят - они читают cert-manager.io/*, spec.tls и spec.rules[].host, поэтому с Ingress под gwin работают так же, как на minikube

Ставить в облако ingress-nginx смысла нет: проект retired и остаётся без исправлений безопасности (подробнее в 05-K8s-networking)
Если манифесты уже написаны под nginx, у Yandex Cloud есть гайд миграции на gwin с таблицей соответствия аннотаций (ссылка в "Дополнительно")

Что меняется в манифесте по сравнению с minikube​

Ingress из 05-K8s-networking переезжает с четырьмя правками

  • Класс: ingressClassName: nginx заменяется на ingressClassName: gwin-default
  • Аннотации nginx.ingress.kubernetes.io/* заменяются на gwin.yandex.cloud/* (в тесте ниже это groupName, externalIPv4Address и securityGroups)
  • Бэкенд - Service типа NodePort вместо ClusterIP: так сделано в официальном примере gwin и в нашем тесте, а с ClusterIP мы gwin не проверяли
  • Группа безопасности для самого балансировщика, о ней ниже

Без изменений остаются аннотация cert-manager.io/cluster-issuer, spec.tls с secretName и spec.rules

Что нужно заранее​

  • Кластер и группа узлов с группами безопасности (создаются в 06-K8s-cloud-cluster)
  • cert-manager с yc-clusterissuer, external-dns и делегированная зона из разделов выше
  • Группа безопасности для самого балансировщика
    В conduit-sg из 06-K8s-cloud-cluster есть то, что нужно узлам - health-check'и балансировщика, диапазон NodePort (30000-32767) и порт 10501 для проверок состояния бэкендов, а правил для приёма трафика самим ALB (порты 80 и 443) нет, поэтому заводится отдельная alb-sg
  • Сервисный аккаунт, от имени которого контроллер создаёт ресурсы ALB, с ролями на каталог
    • alb.editor - создание ресурсов Application Load Balancer
    • vpc.publicAdmin - управление внешней сетевой связностью
    • certificate-manager.certificates.downloader - чтобы контроллер мог брать сертификаты из Yandex Certificate Manager
    • certificate-manager.editor - чтобы контроллер мог создавать в Certificate Manager облачный сертификат по Secret от cert-manager
      Без этой роли балансировщик не создаётся, а ошибка видна только в логах контроллера
    • compute.viewer - чтобы узлы кластера можно было использовать в целевых группах балансировщика
    • k8s.viewer - чтобы контроллер определил, в какой сети разворачивать балансировщик
    • smart-web-security.editor (опционально) - подключение профиля безопасности Yandex Smart Web Security к виртуальному хосту балансировщика
    • logging.writer (опционально) - запись логов балансировщика в Yandex Cloud Logging, если в Gateway указана лог-группа
  • Способ аутентификации gwin в API Yandex Cloud - авторизованный ключ сервисного аккаунта или федерация сервисных аккаунтов

Группа безопасности балансировщика

ALB_SG_ID=$(yc vpc security-group create \
--name alb-sg \
--network-name conduit-network \
--rule "direction=ingress,protocol=tcp,port=80,v4-cidrs=[0.0.0.0/0]" \
--rule "direction=ingress,protocol=tcp,port=443,v4-cidrs=[0.0.0.0/0]" \
--rule "direction=egress,protocol=tcp,from-port=0,to-port=65535,v4-cidrs=[10.10.0.0/24]" \
--format json | jq -r .id)

Правила - приём 80 и 443 из интернета и исходящий трафик к узлам из подсети кластера
Порт 10501 для проверок состояния бэкендов уже открыт на узлах в conduit-sg, он добавлен в 06 при создании группы
Правила взяты со страницы документации Yandex Cloud про группы безопасности для ALB, она написана для ALB Ingress Controller (отдельной страницы про gwin нет), ссылка в "Дополнительно"
10.10.0.0/24 - подсеть conduit-subnet из 06, если у вас другая, подставьте её

Сервисный аккаунт заводится так же, как dns-editor-sa для cert-manager (имя gwin-sa взято для примера)

yc iam service-account create --name gwin-sa

for role in alb.editor vpc.publicAdmin certificate-manager.certificates.downloader \
certificate-manager.editor compute.viewer k8s.viewer; do
yc resource-manager folder add-access-binding \
--id "$(yc config get folder-id)" \
--service-account-name gwin-sa \
--role "$role"
done

Роли выдаются на тот каталог, который потом указывается в параметре "ID каталога" при установке gwin - если каталоги разные, у контроллера не окажется прав на создание ресурсов

Аутентификация идёт по IAM-токену с ограниченным сроком жизни, получить его из кластера можно двумя способами

  • Авторизованный ключ - долгоживущий файл (RSA-2048 или RSA-4096), который передаётся при установке, как key.json для cert-manager
    Проще всего, именно он используется ниже
  • Федерация сервисных аккаунтов (WLIF) - связь кластера с Yandex Cloud по OpenID Connect без долгоживущих ключей
    Безопаснее, но настройка длиннее
    1. Включить поддержку федерации в кластере и группе узлов
    2. Создать федерацию - в iss и aud указать URL эмитента, в адрес JWKS - URL набора ключей, оба значения выдаются при настройке кластера
    3. Привязать к федерации сервисный аккаунт IAM, внешний субъект - system:serviceaccount:<пространство-имён>:<имя-сервисного-аккаунта-kubernetes>, имя по умолчанию gwin

Ключ выпускается так же, как key.json для cert-manager

yc iam key create \
--service-account-name gwin-sa \
--format json \
--output sa-key.json

sa-key.json - такой же секрет, как key.json, коммитить его в git нельзя

Установка gwin​

Ставится вручную через Marketplace в консоли

  1. Открыть кластер → вкладка "Другое" → Marketplace
  2. В разделе "Доступные для установки приложения" выбрать Gwin и нажать "Перейти к установке"
  3. Заполнить параметры приложения
    • Пространство имён - новое, например gwin-space
      С пространством имён по умолчанию gwin может работать некорректно
    • Название приложения - любое
    • ID каталога - каталог, в котором будет работать gwin и на который выданы роли выше
    • Ключ сервисного аккаунта - содержимое sa-key.json, а при WLIF вместо ключа выбирается сервисный аккаунт, привязанный к федерации
  4. Нажать "Установить" и дождаться статуса Deployed

Проверить, что контроллер запустился

kubectl get pods --namespace=gwin-space
kubectl get ingressclass

Под gwin должен быть Running, а в IngressClass появится gwin-default

Тестовое приложение с HTTPS​

Манифест ниже собирает всю цепочку целиком - приложение, Service для ALB и Ingress, который одновременно просит сертификат у cert-manager и балансировщик у gwin
Домен берётся из вашей зоны (в примере example.com), а id группы alb-sg - из yc vpc security-group get alb-sg или из переменной ALB_SG_ID выше

apiVersion: v1
kind: Namespace
metadata:
name: nginx-test
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: nginx-test
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: nginx-test
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
nodePort: 30080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
namespace: nginx-test
annotations:
cert-manager.io/cluster-issuer: yc-clusterissuer
gwin.yandex.cloud/groupName: nginx-test
gwin.yandex.cloud/externalIPv4Address: auto
gwin.yandex.cloud/securityGroups: <id-группы-alb-sg>
spec:
ingressClassName: gwin-default
tls:
- hosts:
- example.com
secretName: example-com-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80

Аннотации gwin.yandex.cloud/* означают

  • groupName - Ingress с одинаковым именем группы собираются в один ALB
  • externalIPv4Address: auto - балансировщик получает динамический публичный IP-адрес
  • securityGroups - id групп безопасности балансировщика

Применить и проследить за цепочкой

kubectl apply -f nginx-test.yaml
kubectl get certificate -n nginx-test -w # READY: True через 1-3 минуты
kubectl get ingress -n nginx-test -w # ADDRESS появится через несколько минут
yc alb load-balancer list # балансировщик, который создал gwin
yc certificate-manager certificate list # сертификат, который gwin создал по Secret
yc dns zone list-records conduit-zone # A-запись от external-dns
curl -I https://example.com

Если cert-manager стоит со staging-сервером Let's Encrypt, curl без -k сертификату не поверит - это ожидаемо
Пока запись в DNS не обновилась, балансировщик можно проверить напрямую, подставив его адрес: curl --resolve example.com:443:IP-АДРЕС https://example.com

Как это устроено​

Три компонента работают с одним Ingress, друг о друге они ничего не знают

  1. cert-manager (ingress-shim) видит аннотацию и spec.tls, создаёт Certificate, подтверждает домен через DNS01 и кладёт сертификат в Secret example-com-tls
  2. gwin берёт этот Secret, сам создаёт по нему сертификат в Certificate Manager и строит ALB с публичным адресом, который записывает в статус Ingress
  3. external-dns берёт хост из spec.rules[].host и адрес из status.loadBalancer у Ingress и создаёт A-запись в зоне

Порядок важен: gwin проверяет Ingress сразу, а Secret появляется только после выпуска сертификата
Пока его нет, в событиях kubectl describe ingress будет Failed to validate ingress group ...: secret example-com-tls not found - это нормально первые минуты
Если Certificate уже READY, а балансировщика всё нет, обновите Ingress любой правкой, например kubectl annotate ingress nginx -n nginx-test touch="$(date +%s)" --overwrite

Если сертификат уже лежит в Certificate Manager, Secret и cert-manager для него не нужны: в tls.secretName указывается yc-certmgr-cert-id-<id сертификата>, это основной вариант из официального примера gwin

Если у Ingress нет адреса​

Сначала события, потом логи контроллера - после первой проверки gwin пишет свои ошибки только в логи

kubectl describe ingress nginx -n nginx-test | tail -10
kubectl get pods -A | grep -i gwin
kubectl logs -n gwin-space deploy/gwin --tail=200 | grep -E "level=(ERROR|INFO)"

Что искать в логах

  • failed to create certificate: ... PermissionDenied - у аккаунта нет права создавать сертификаты в Certificate Manager
    Проверьте роль certificate-manager.editor и что роли выданы на тот же каталог, что указан в параметре "ID каталога" при установке gwin
    Контроллер повторяет попытку раз в минуту, после выдачи роли его не нужно перезапускать
  • PermissionDenied на других ресурсах - не хватает следующей роли из списка выше
  • Про подсети, зону или группу безопасности - проверьте, что alb-sg находится в той же сети (conduit-network), что и кластер

Список ролей аккаунта в каталоге

GWIN_SA_ID=$(yc iam service-account get gwin-sa --format json | jq -r .id)
yc resource-manager folder list-access-bindings "$(yc config get folder-id)" | grep "$GWIN_SA_ID"

Если балансировщик создался, но отвечает ошибкой 502 или не отвечает, откройте его в консоли и посмотрите состояние бэкендов: UNHEALTHY означает, что проверки состояния не доходят до узлов, и нужно смотреть правила alb-sg и conduit-sg

Что ещё умеет gwin​

Поддерживаемые ресурсы

  • Стандартные ресурсы Kubernetes и Gateway API
    • Ingress - возможности расширяются аннотациями
    • Service - сервисы, которые используются как бэкенды
    • Gateway - правила приёма входящего трафика и выбора маршрутов
    • HTTPRoute, GRPCRoute, TLSRoute - маршрутизация HTTP/HTTPS-, gRPC- и TLS-трафика по бэкендам
  • Собственные CRD Yandex Cloud
    • IngressPolicy - дополнительные возможности для всех ресурсов ALB
    • GatewayPolicy - дополнительные возможности для балансировщиков, роутеров и обработчиков
    • RoutePolicy - дополнительные возможности для виртуальных хостов и маршрутов
    • ServicePolicy - настройка целевых групп
    • IngressBackendGroup - настройка групп бэкендов
    • YCStorageBucket и YCCertificate - бакет Yandex Object Storage и сертификат Yandex Certificate Manager как ресурсы кластера

Собственные CRD - тот же приём расширения API, что разбирался в разделе про CRD в 05-K8s-networking
Для возможностей ALB, которых нет в спецификации Kubernetes, например автомасштабирования, логирования и интеграции с Yandex Smart Web Security, есть механизм политик - они настраиваются через CRD или аннотации
Если Application Load Balancer уже есть, его инфраструктуру можно переиспользовать для ресурсов Kubernetes через gwin - для этого есть механизм Attachment

Балансировщик, который создаёт gwin, принадлежит кластеру - менять и удалять его и его дочерние ресурсы через консоль, Terraform/OpenTofu, CLI или API Yandex Cloud нельзя, только через ресурсы Kubernetes, иначе кластер может начать работать некорректно
Сам ALB тарифицируется отдельно от кластера - плата за его вычислительные ресурсы идёт, пока он существует

В примере балансировщику выдаётся динамический публичный IP-адрес - при удалении балансировщика он пропадает вместе с ним, external-dns подхватит новый адрес сам
Для продакшена IP-адрес лучше заранее зарезервировать и использовать статический (резервирование - в документации VPC)

Если под вебхука cert-manager не стартует​

В событиях MountVolume.SetUp failed for volume "certs": secret ... not found - секрет с сертификатом вебхука выпускает сам cert-manager двумя Certificate из установленного приложения (...-yc-ca и ...-yc-webhook-tls), пока они не READY, под ждёт
Первые минуты после установки это нормально
Если READY так и не появляется, нужно проверить два случая из раздела "Если поды не стартуют: сеть кластера" в 06-K8s-cloud-cluster - скачались ли образы cert-manager и достучался ли мастер до узлов

kubectl get certificate,issuer -n cert-manager-dns-space

Примеры​

Проверка cert-manager, external-dns и gwin после установки через Marketplace (сами приложения ставятся консолью, а не kubectl apply - см. теорию выше)

kubectl get certificate -A # READY: True - сертификат выпущен
kubectl get clusterissuer # yc-clusterissuer в списке
kubectl get ingressclass # gwin-default - класс gwin
kubectl get ingress -A # ADDRESS у Ingress - IP балансировщика ALB
yc alb load-balancer list # ALB, который создал gwin
yc dns zone list-records conduit-zone # A-запись для домена создана external-dns

Уборка - как в 06-K8s-cloud-cluster: кластер и узлы удаляются теми же командами, но сначала нужно убрать балансировщик
Он тарифицируется отдельно, пока существует
ALB от gwin принадлежит gwin, поэтому убирается удалением Ingress, а не вручную в консоли

Все Ingress и Service типа LoadBalancer убираются командами ниже, пока gwin ещё запущен

kubectl delete ingress --all --all-namespaces

kubectl get svc --all-namespaces -o json \
| jq -r '.items[] | select(.spec.type=="LoadBalancer") | "\(.metadata.namespace) \(.metadata.name)"' \
| while read -r ns name; do kubectl delete svc "$name" -n "$ns"; done

Перед удалением кластера стоит проверить списки

yc alb load-balancer list
yc load-balancer network-load-balancer list

Оба списка должны быть пустыми (второй - сетевые балансировщики под Service типа LoadBalancer, если вы их создавали)
Группу alb-sg можно удалить только после того, как исчез ALB

Если кластер поднят через OpenTofu, порядок сноса другой - сначала то, что создали приложения в кластере, и только потом tofu destroy, подробнее в 08-K8s-opentofu

Проверь себя​

1. Чем ACME DNS01 через cert-manager + Cloud DNS отличается от selfSigned ClusterIssuer, который использовался на minikube в 05-K8s-networking?

selfSigned работал офлайн и без домена, но браузеры такому сертификату не доверяют
DNS01 запрашивает настоящий сертификат у Let's Encrypt, подтверждая владение доменом через TXT-запись в Cloud DNS, а не через доступность по HTTP - для этого cert-manager должен сам уметь писать в DNS-зону, отсюда отдельный сервисный аккаунт с ролью dns.editor

2. Нужно ли отдельно настраивать external-dns, чтобы он подхватил домен из Ingress?

Нет - для Ingress external-dns сам читает хосты из spec.rules[].host
Аннотация external-dns.alpha.kubernetes.io/hostname нужна только для Service типа LoadBalancer, у которого своего понятия "хост" в манифесте нет

3. Почему при переезде в облако в Ingress из 05-K8s-networking меняются класс и аннотации, а cert-manager.io/cluster-issuer и spec.tls остаются?

Класс и аннотации - договор с конкретным контроллером
В облаке вместо ingress-nginx стоит gwin, поэтому ingressClassName: nginx и аннотации nginx.ingress.kubernetes.io/* заменяются на gwin-default и gwin.yandex.cloud/*, а Service бэкенда делается NodePort
cert-manager и external-dns от класса не зависят - они читают cert-manager.io/*, spec.tls и spec.rules[].host, так что эти части манифеста не меняются, кроме имени ClusterIssuer и реального домена
Для переноса готовых манифестов у Yandex Cloud есть таблица соответствия аннотаций в гайде миграции с Ingress NGINX на gwin

4. Как убрать ALB, который создал gwin, и как убедиться, что он исчез?

ALB принадлежит gwin и управляется только через ресурсы Kubernetes - менять и удалять его вручную в консоли, через Terraform/OpenTofu, CLI или API нельзя
Поэтому убирается он удалением Ingress, после чего нужно проверить yc alb load-balancer list

  • список должен быть пустым
    Пока ALB существует, он тарифицируется отдельно от кластера и держит за собой группу безопасности alb-sg
5. gwin - это то же самое, что ALB Ingress Controller из документации Yandex Cloud?

Нет, это разные продукты - ALB Ingress Controller более старый и работает только с Ingress, через аннотации ingress.alb.yc.io/*
gwin - более новый контроллер, которым Yandex Cloud рекомендует пользоваться вместо него И вместо отдельного контроллера Gateway API - один и тот же контроллер поднимает один и тот же ALB что из Ingress, что из Gateway/HTTPRoute

6. Что делает gwin с Secret, который выпустил cert-manager, и что будет, если у его аккаунта нет роли certificate-manager.editor?

gwin сам создаёт по Secret сертификат в Certificate Manager и подключает его к ALB
Без роли certificate-manager.editor (или если роли выданы на другой каталог, чем указан в параметрах gwin) создание сертификата падает с PermissionDenied, и ALB не строится
В событиях Ingress такой ошибки нет - она видна только в логах контроллера
А secret ... not found в событиях в первые минуты нормален: Secret появляется только после того, как cert-manager выпустит сертификат

Практика​

  1. Заведи зону для домена (или поддомена) в Cloud DNS и делегируй на неё NS у регистратора заранее, перенос может занять часы
    Затем разверни cert-manager с DNS01-решателем через Marketplace и получи настоящий доверенный сертификат для своего приложения
  2. Добавь external-dns, чтобы A-запись для домена создавалась и обновлялась сама, без ручных правок в консоли
  3. Установи gwin, заведи группу безопасности alb-sg и примени тестовый манифест из раздела про gwin со своим доменом
    Убедись, что сертификат выпустился, ALB создан, A-запись появилась, а curl по HTTPS отвечает
  4. Возьми Ingress из 05-K8s-networking и перепиши его под gwin по списку правок из теории - класс, аннотации, тип Service
  5. Перед удалением кластера убери балансировщик - удали Ingress, проверь yc alb load-balancer list и yc load-balancer network-load-balancer list, затем удали alb-sg
    После этого удали кластер и группу узлов, как в практике 06-K8s-cloud-cluster - чем больше в кластере компонентов, тем проще что-то из них забыть выключить руками

Итог / чек-лист​

  • Настоящий доверенный HTTPS на облачном кластере - через cert-manager с DNS01-решателем (Cloud DNS), ставится через Marketplace, а не kubectl apply, в отличие от selfSigned на minikube
  • Домен делегируется на Cloud DNS через фиксированные ns1.yandexcloud.net./ ns2.yandexcloud.net. - одни и те же для любой публичной зоны, но перенос NS у регистратора не мгновенный (часы, иногда до 72) и его стоит запускать заранее, а не на самом занятии
  • external-dns сам создаёт и обновляет DNS-записи по Ingress/Service - для Ingress ничего дополнительно не нужно, для Service типа LoadBalancer нужна аннотация external-dns.alpha.kubernetes.io/hostname
  • gwin - рекомендуемый Yandex Cloud контроллер поверх Application Load Balancer, заменяет и отдельный ALB Ingress Controller, и отдельный контроллер Gateway API - один и тот же контроллер поднимает один и тот же ALB что из Ingress (ingressClassName: gwin-default), что из Gateway/HTTPRoute
  • Манифест из 05-K8s-networking переезжает правкой класса, аннотаций gwin.yandex.cloud/* и типа Service (NodePort), а cert-manager.io/*, spec.tls и spec.rules остаются, поэтому cert-manager и external-dns работают как на minikube
  • gwin сам создаёт в Certificate Manager сертификат по Secret от cert-manager, для этого его аккаунту нужна роль certificate-manager.editor, а ошибки прав видны только в логах контроллера, не в событиях Ingress
  • Группа безопасности самого ALB (alb-sg) заводится отдельно - в conduit-sg нет правил для приёма трафика на 80 и 443
  • ingress-nginx в облако ставить смысла нет (retired), для готовых манифестов есть гайд миграции на gwin

Дополнительно​