Настраиваем Ingress Controller Traefik и Let's Encrypt в кластере Kubernetes на Ubuntu 22.04

Ubuntu / Containers 22-09-2026, 18:47 sobir 421 0


Настроим вход в кластер Kubernetes без облачного балансировщика: Traefik принимает трафик на портах 80/443 worker-ноды, а cert-manager автоматически выпускает сертификаты Let's Encrypt. Это продолжение статьи Разворачиваем кластер Kubernetes (kubeadm) на Ubuntu 22.04 — кластер из неё используется как основа.

Особенности схемы:
  • Traefik работает без root. Вместо hostNetwork используется hostPort: внутри пода Traefik слушает непривилегированные порты 8000/8443, а Kubernetes пробрасывает на них порты 80/443 ноды. Все защитные настройки чарта по умолчанию остаются включёнными.
  • Сертификаты выпускает cert-manager и хранит их в Kubernetes Secrets. Постоянный том для Traefik не нужен.
  • Схема сразу готова к нескольким worker-нодам: для второй реплики Traefik достаточно поставить метку на ноду и поменять одну цифру.
  • Отдельный шаг проверки сети подов между нодами: без него cert-manager не устанавливается, если на control-plane несколько сетевых интерфейсов.


Версии: Kubernetes v1.37, Calico v3.31.4 (Tigera Operator), Helm 4, чарт Traefik 41.6.0 (Traefik v3.7.13), cert-manager v1.21.2.

Требования и схема


Нужен рабочий кластер kubeadm с Calico из предыдущей статьи. Примеры ниже — для одной control-plane и одной worker-ноды в приватной сети 10.10.3.0/24.

Если Helm ещё не установлен, ставим его на control-plane официальным скриптом (Helm 4):
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4
chmod 700 get_helm.sh
./get_helm.sh
helm version
rm -f get_helm.sh


Также понадобятся:
  • Домен с A-записью на публичный IP worker-ноды. Без неё Let's Encrypt не выдаст сертификат.
  • Порты 80 и 443 открыты на worker-ноде во внешнем файрволе.
  • CNI-плагин portmap на worker-ноде — он пробрасывает hostPort в под. В Calico он есть по умолчанию, проверить можно так:

ls -l /opt/cni/bin/portmap


Как проходит запрос:
Клиент --80/443--> IP worker-ноды --hostPort--> под Traefik (8000/8443) --> поды приложения
cert-manager --Secret с сертификатом--> Traefik


Traefik берёт из Kubernetes API список подов за Service и балансирует запросы напрямую между ними, в том числе между подами на разных нодах. Сервис самому Traefik не нужен: трафик приходит прямо на IP ноды.

1. Проверяем сеть подов между нодами


API-сервер на control-plane должен достучаться до подов на worker — иначе не заработает webhook cert-manager. По умолчанию Calico берёт IP с первого найденного интерфейса, и на сервере с несколькими интерфейсами это может оказаться не тот адрес.

Этот шаг лучше выполнить сразу после установки кластера, до Traefik и cert-manager.

Смотрим, какие адреса Calico выбрал для нод:
kubectl get nodes -o yaml | grep "projectcalico.org/IPv4Address"


У всех нод должны быть адреса из приватной сети (10.10.3.x). Если хотя бы у одной адрес из другой сети, явно указываем Calico, какую сеть использовать:
kubectl patch installation default --type=json -p='[{"op":"add","path":"/spec/calicoNetwork/nodeAddressAutodetectionV4","value":{"cidrs":["10.10.3.0/24"]}}]'


Именно --type=json: вариант --type=merge Calico отклонит, потому что к существующему firstFound: true добавится второй метод определения адреса.


Сразу после патча адреса не изменятся: operator сначала перезапускает поды calico-node на всех нодах. Дожидаемся окончания и повторяем проверку:
kubectl rollout status daemonset/calico-node -n calico-system
kubectl get nodes -o yaml | grep "projectcalico.org/IPv4Address"


Настройка сохраняется в объекте Installation и переживает перезагрузки; новые ноды в этой сети получат правильный адрес автоматически.

При установке кластера с нуля то же самое можно сразу прописать в custom-resources.yaml:
spec:
  calicoNetwork:
    nodeAddressAutodetectionV4:
      cidrs:
        - 10.10.3.0/24


2. Помечаем worker-ноду


Traefik будет запускаться только на нодах с меткой ingress=true — так точка входа не «переедет» на ноду, на которую не указывает DNS.
kubectl get nodes
kubectl label node <имя_worker> ingress=true
kubectl get nodes -l ingress=true


Последняя команда должна вывести одну строку с вашим worker.

3. Устанавливаем Traefik


Все настройки собраны в одном values-файле. Создаём traefik-values.yaml:
# Traefik: вход в кластер без облачного LoadBalancer
# Чарт traefik 41.6.0 (Traefik v3.7.13)
#
# Traefik работает от обычного пользователя (UID 65532, по умолчанию в чарте).
# Внутри пода он слушает порты 8000/8443, а порты 80/443 на ноде
# пробрасываются в под через hostPort (CNI-плагин portmap).

# Сколько копий Traefik запускать.
# После добавления второго worker с меткой ingress=true поставить 2.
deployment:
  replicas: 1

# Service для Traefik не нужен: трафик приходит прямо на IP worker.
service:
  enabled: false

# Проброс портов ноды в под
ports:
  web:
    hostPort: 80         # HTTP:  порт 80 ноды  -> 8000 в поде
  websecure:
    hostPort: 443        # HTTPS: порт 443 ноды -> 8443 в поде

# Запускать Traefik только на нодах с меткой ingress=true
nodeSelector:
  ingress: "true"

# Не больше одной копии Traefik на одной ноде
affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app.kubernetes.io/name: traefik
        topologyKey: kubernetes.io/hostname

# Порты 80/443 на ноде заняты старым подом, поэтому новый
# не сможет стартовать рядом с ним. При обновлении сначала
# останавливаем старый, потом запускаем новый.
updateStrategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 0
    maxUnavailable: 1


Файл проверен по JSON-схеме чарта 41.6.0: опечатку в имени параметра Helm не пропустит.

Устанавливаем, обязательно с указанием версии чарта — иначе при будущем helm repo update и helm upgrade Traefik незаметно обновится до новой мажорной версии, где параметры могут называться иначе:
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik --version 41.6.0 -n traefik --create-namespace -f traefik-values.yaml


Проверяем, что под запустился на worker:
kubectl get pods -n traefik -o wide


Ожидаем 1/1 Running на ноде с меткой ingress=true.

4. Проверяем Traefik


Сначала по внутреннему IP worker (его показывает колонка INTERNAL-IP в kubectl get nodes -o wide), затем с любого компьютера по публичному IP:
curl http://10.10.3.3
curl http://<публичный_IP_worker>


Правильный ответ в обоих случаях — 404 page not found: Traefik принимает трафик, просто маршрутов ещё нет. Если внутренний адрес отвечает, а публичный нет — закрыты порты во внешнем файрволе.

ss -tlpn на worker порт 80 не покажет, и это нормально: при hostPort порт не слушает процесс на ноде, трафик перенаправляется в под правилами iptables.


5. Устанавливаем cert-manager


Берём последнюю версию cert-manager — v1.21.2. Официально ветка 1.21 поддерживает Kubernetes от 1.33 до 1.36; на v1.37 она не тестировалась разработчиками, но в этой установке работает. Когда выйдет cert-manager 1.22 с поддержкой 1.37, стоит обновиться.
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager --version v1.21.2 -n cert-manager --create-namespace --set crds.enabled=true


Параметр crds.enabled=true обязателен: в чарте он по умолчанию выключен, и без него не появится ресурс ClusterIssuer.


Команда может выполняться до пары минут — Helm ждёт, пока пройдёт проверка готовности (startupapicheck). Проверяем:
kubectl get pods -n cert-manager


Ожидаем три пода Running: cert-manager, cert-manager-cainjector, cert-manager-webhook. Если установка упала с таймаутом на startupapicheck — см. «Возможные проблемы».

6. Настраиваем Let's Encrypt (ClusterIssuer)


ClusterIssuer говорит cert-manager, где получать сертификаты и как подтверждать домен. Создаётся один раз на весь кластер. Создаём cluster-issuer.yaml, указав свой email — на него Let's Encrypt пришлёт предупреждение, если сертификат вдруг не продлится:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-account-key
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik


kubectl apply -f cluster-issuer.yaml
kubectl get clusterissuer


В колонке READY должно быть True — cert-manager зарегистрировался в Let's Encrypt. Домен подтверждается методом HTTP-01: Let's Encrypt делает запрос на порт 80, cert-manager временно создаёт для него маршрут в Traefik.

7. Запускаем тестовое приложение


Проверим всю цепочку на traefik/whoami — простом сервере, который отвечает своим именем, IP и заголовками запроса. Перед применением убеждаемся, что DNS уже указывает на worker:
dig +short whoami.example.com


Создаём whoami.yaml, заменив whoami.example.com на свой домен в двух местах:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
spec:
  replicas: 2
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: traefik/whoami:v1.11.0
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
          resources:
            requests:
              cpu: 10m
              memory: 16Mi
            limits:
              memory: 32Mi
---
apiVersion: v1
kind: Service
metadata:
  name: whoami
spec:
  selector:
    app: whoami
  ports:
    - port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: whoami
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
  ingressClassName: traefik
  tls:
    - hosts:
        - whoami.example.com
      secretName: whoami-tls
  rules:
    - host: whoami.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: whoami
                port:
                  number: 80


Аннотация cert-manager.io/cluster-issuer просит cert-manager выпустить сертификат для домена из блока tls и положить его в Secret whoami-tls (в том же namespace, что и Ingress), откуда его берёт Traefik.
kubectl apply -f whoami.yaml
kubectl get certificate -w


Когда в колонке READY появится True (обычно за 1–2 минуты), открываем https://whoami.example.com. На странице видно весь путь запроса:
  • X-Forwarded-Proto: https — соединение зашифровано.
  • X-Forwarded-Server — имя пода Traefik, который принял запрос.
  • Hostname — под приложения, который ответил.
  • X-Real-Ip — настоящий IP клиента: схема с hostPort его сохраняет.


Продлевать сертификат не нужно — cert-manager делает это сам.

Проверяем балансировку


В браузере Hostname при обновлении страницы, скорее всего, меняться не будет. Traefik чередует поды на каждый запрос, но браузер вместе со страницей запрашивает ещё и /favicon.ico: страница всё время попадает в один под, а иконка — в другой. Поэтому балансировку проверяем через curl, он запрашивает только страницу:
for i in 1 2 3 4 5 6; do curl -s https://whoami.example.com | grep Hostname; done


Имена подов должны строго чередоваться:
Hostname: whoami-77955ddff9-rdhzs
Hostname: whoami-77955ddff9-fnbh9
Hostname: whoami-77955ddff9-rdhzs
...


Тестовое приложение здесь для простоты запускается в namespace default. Для настоящих приложений лучше создавать отдельные namespace: так приложение удаляется одной командой kubectl delete namespace <имя>, и имена объектов разных приложений не конфликтуют.

8. Добавляем второй worker


Когда в кластере появится второй worker, Traefik масштабируется без переделки схемы.

1. Помечаем новую ноду:
kubectl label node <worker2> ingress=true


2. В traefik-values.yaml ставим replicas: 2 и обновляем релиз с той же версией чарта:
helm upgrade traefik traefik/traefik --version 41.6.0 -n traefik -f traefik-values.yaml


3. Добавляем в DNS вторую A-запись домена на публичный IP нового worker.

Anti-affinity разнесёт реплики по разным нодам, а сертификаты обе копии Traefik возьмут из одного Secret. Клиенты будут распределяться между нодами через DNS, а каждая копия Traefik — балансировать запросы по подам всего кластера.

Уже запущенные поды приложений на новую ноду не переедут: Kubernetes не перераспределяет работающие поды. Чтобы они разошлись по нодам, пересоздаём их:
kubectl rollout restart deployment whoami
kubectl get pods -l app=whoami -o wide


Планировщик старается разносить реплики по нодам, но не гарантирует этого. Для гарантии в Deployment добавляют topologySpreadConstraints.

У DNS round-robin есть ограничение: если worker упадёт, часть клиентов какое-то время будет попадать на недоступный IP, пока его A-запись не уберут.


Почему hostPort и cert-manager


Во многих инструкциях Traefik без облачного балансировщика запускают с hostNetwork и встроенным ACME-клиентом. Здесь от обоих вариантов отказались по двум причинам.

hostNetwork требует root. Под с hostNetwork сам занимает порты 80/443 на ноде, а порты ниже 1024 в Linux может открыть только привилегированный процесс. Traefik пришлось бы запускать от root с capability NET_BIND_SERVICE, отключив защиту чарта, и дополнительно задавать dnsPolicy: ClusterFirstWithHostNet. С hostPort Traefik слушает непривилегированные порты, а привилегированную часть берёт на себя CNI.

Встроенный ACME не рассчитан на несколько реплик. В бесплатной версии Traefik каждая копия сама получает и продлевает сертификаты, поэтому две реплики конкурируют за один acme.json. К тому же для файла нужен постоянный том, а том local-path привязан к одной ноде. cert-manager хранит сертификаты в Secrets, доступных всем копиям Traefik, и не требует хранилища.

Чем эта схема отличается от варианта с hostNetwork и встроенным ACME:
  • Вход на 80/443: было hostNetwork — стало hostPort.
  • Пользователь Traefik: было root (UID 0) — стало UID 65532.
  • Сертификаты: было ACME Traefik и acme.json на PVC — стало cert-manager и Kubernetes Secret.
  • Хранилище для Traefik: было нужно — стало не нужно.
  • Несколько реплик: было нельзя — стало можно.


Возможные проблемы


Установка cert-manager падает на startupapicheck
Error: INSTALLATION FAILED: failed post-install: resource Job/cert-manager/cert-manager-startupapicheck not ready. status: InProgress, message: Job in progress
context deadline exceeded


При этом поды cert-manager, cainjector и webhook в статусе Running, а startupapicheck уходит в CrashLoopBackOff. Причина — API-сервер на control-plane не может достучаться до пода webhook на worker. Проверяем с control-plane, подставив IP пода webhook из kubectl get pods -n cert-manager -o wide:
curl -k -m 5 https://<IP_пода_webhook>:10250/healthz


Таймаут вместо ok означает, что не работает сеть подов между нодами. Чаще всего Calico выбрал для control-plane не тот IP — проверяем и исправляем, как в шаге 1. После этого удаляем неудачный релиз и ставим заново:
helm uninstall cert-manager -n cert-manager
helm install cert-manager jetstack/cert-manager --version v1.21.2 -n cert-manager --create-namespace --set crds.enabled=true


Под Traefik висит в Pending

В kubectl describe pod -n traefik … видно, что ни одна нода не подошла под nodeSelector. Забыли поставить метку ingress=true — ставим её, и Scheduler сам запустит под, переустанавливать ничего не нужно.

Сертификат не выпускается

kubectl get certificate долго показывает READY False. Смотрим, на каком этапе застряла проверка домена:
kubectl describe certificate whoami-tls
kubectl get challenges -A
kubectl describe challenge -A


Типичные причины: A-запись домена ещё не указывает на worker (dig +short <домен>) или порт 80 закрыт во внешнем файрволе — Let's Encrypt проверяет домен именно по HTTP.
Похожие новости
Разворачиваем кластер Kubernetes (kubeadm) на Ubuntu 22.04

Разворачиваем полноценный кластер Kubernetes через kubeadm на трёх серверах с Ubuntu 22.04....

Подробнее
Поднимаем прозрачный прокси Squid на Ubuntu 22.04

Рассмотрим настройку прозрачного проксирования в Squid без подмены сертификатов на базе ОС Ubuntu 22.04....

Подробнее
Получаем валидные сертификаты SSL от Let's Encrypt для NGINX

Получаем валидные сертификаты SSL от Let's Encrypt для NGINX с использованием различных плагинов....

Подробнее
Поднимаем SSL VPN OpenConnect с Let's Encrypt в Ubuntu 20.04

Поднимаем сервер SSL VPN OpenConnect для удаленного доступа пользователям....

Подробнее
Настройка обратного прокси сервера NGINX с SSL-терминацией в CentOS 8 / RHEL 8

Настраиваем обратный прокси сервер NGINX с SSL-терминацией и генерируем бесплатные валидные сертификаты SSL...

Подробнее
Установка веб сервера NGINX в CentOS 8 / RHEL 8

Установка последней стабильной версии популярного веб сервера NGINX....

Подробнее
Комментарии (0)
Новые комментарии
sobir8 ноября 2024 12:42

Цитата: FidoNet Цитата: sobir Цитата: FidoNet Спасибо за статью. Остальные

img
К комментарию
FidoNet7 ноября 2024 03:26

Цитата: sobir Цитата: FidoNet Спасибо за статью. Остальные статьи что находил

img
К комментарию
sobir27 октября 2024 19:02

Цитата: FidoNet Цитата: sobir Цитата: FidoNet Можт быть дело в

img
К комментарию
FidoNet25 октября 2024 22:04

Цитата: sobir Цитата: FidoNet Можт быть дело в dnsmasq.service? Ошибка Failed

img
К комментарию
sobir25 октября 2024 16:48

Цитата: FidoNet Цитата: sobir Цитата: FidoNet Спасибо за статью. Остальные

img
К комментарию
FidoNet20 октября 2024 12:12

Можт быть дело в dnsmasq.service? Ошибка Failed to set DNS configuration: Unit

img
К комментарию
Все комментарии
Какой дистрибутив Linux вы часто используете?
Календарь
«    Ноябрь 2026    »
ПнВтСрЧтПтСбВс
 1
2345678
9101112131415
16171819202122
23242526272829
30 
Подпишись на канал