Блог и новости

Ingress и доступ из k8s кластера к внешнему домену

Часть нашей инфраструктуры хостится в Timeweb Cloud в Кубернетес кластере. Недавно мы столкнулись с интересной проблемой с использованием Ingress и cert-manager. Здесь мы опишем, что это за проблема, как мы ее временно решили и какое постоянное решение хотелось бы видеть (но оно требует изменений от команды Timeweb).

Суть проблемы

Для хостинга веб приложений мы используем связку load balancer + ingress nginx + cert-manager.
Load Balancer просто выставляет ingress наружу, ingress nginx делает свою работу по автоматизации настройки веб сервисов, а cert-manager выдает краткосрочные сертификаты от Let’s Encrypt.
Чтобы получать реальный IP адрес пользователя мы включили и на балансировщике, и на ingress Proxy Protocol. Теперь ingress ожидает заголовка Proxy Protocol и без него не знает что делать.
Это стало неожиданной проблемой для cert-manager и верификации let’s encrypt. Прежде чем отправлять запрос к ACME сервису, cert-manager пытается проверить сам доступность сайта. Контроллер посылает запрос к сайту, на который вы хотите выписать сертификат. Кубернетес не хочет нагружать внешний балансировщик, и трафик заворачивается через kube-proxy сразу в Ingress.
Что вы видите в результате:
  • Сертификат никогда не выпишется. В логах cert-manager будет такая ошибка:
E0413 12:25:57.580259 1 controller.go:196] certificates controller: Re-queuing item "kube-system/therealhost.example.com" due to error processing: error waiting for key to be available for domain "therealhost.example.com": context deadline exceeded
  • А в логах ingress контроллера можно увидеть ругань на Proxy Protocol:
2018/04/13 12:27:55 [error] 1837#1837: *10321 broken header: "GET /.well-known/acme-challenge/9oQ5DbRUHNpnIsqvlvFUcb-km2OgpckyaXXEQh9cQQk HTTP/1.1
Host: therealhost.example.com
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip

" while reading PROXY protocol, client: 10.129.2.0, server: 0.0.0.0:80
  • Для HTTPS запросов можно увидеть похожую кашу:
2025/04/18 15:57:57 [error] 27#27: *984 broken header: "?R?{?z????k??????\J&??6?? -???!?82K?#S" while reading PROXY protocol, client: 10.244.1.1, server: 0.0.0.0:443
Эта проблема не новая. Этот github тред берет начало в августе 2018: cert-manager/issues/466

Как быстро решить проблему

  1. Отказаться от proxy protocol
  2. Использовать временное решение в виде compumike/hairpin-proxy
Выключение proxy протокола для нас не вариант, так как нам важно получать реальные IP адреса пользователей:
  • Для правильного rate limiting
  • Некоторые статус страницы используют фильтрацию по IP-адресам
Удивительно, но hairpin-proxy был создан 5 лет назад с единственной целью - решить эту проблему. Решение максимально простое:
  1. Оператор следит за ingress объектами
  2. Для каждого ingress объекта создает в CoreDNS запись, которая направит трафик на внутренний адрес сервиса, минуя ingress
Например, у нас в ConfigMap от CoreDNS можно увидеть вот такие записи:
apiVersion: v1
data:
  Corefile: |-
    .:53 {
        rewrite name api.pingera.ru hairpin-proxy.hairpin-proxy.svc.cluster.local # Added by hairpin-proxy
        rewrite name app.pingera.ru hairpin-proxy.hairpin-proxy.svc.cluster.local # Added by hairpin-proxy
…

ipMode для Load Balancer

hairpin-proxy это временное решение, т.к. выглядит немного “костыльно”. Начиная с 1.32 Кубернетес добавили в stable функционал, который позволяет направлять трафик к балансировщику напрямую, без магии kube-proxy. Называется Load balancer IP address mode или ipMode. Более подробное описание можно найти в анонсе альфы и в KEP-1080.
Теперь у сервиса Load Balancer в статусе появится дополнительное поле ipMode:
kubectl get service mylb -o yaml
…
status:
  loadBalancer:
    ingress:
    - ip: 123.123.123.123
      ipMode: VIP
По умолчанию значение VIP: kube-proxy заворачивает трафик сразу на service объект. Если же выставить значение в Proxy, то получим желаемый результат - трафик пройдет через Load Balancer, как будто он поступает извне.
Чтобы этот функционал заработал, облачным провайдером нужно добавить либо аннотацию, либо метку для балансировщиков. Например, вот как это сделал Oracle Cloud: https://docs.public.oneportal.content.oci.oraclecloud.com/en-us/iaas/releasenotes/conteng/conteng-LoadBalancer-IPMode-Release-Notes.htm
Пока что Timeweb Cloud это не поддерживает, но hairpin-proxy отлично справляется. Будем ждать.
Блог Статус Страницы