Настраиваем 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 с сертификатом--> TraefikTraefik берёт из 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"]}}]'Сразу после патча адреса не изменятся: 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/242. Помечаем 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 принимает трафик, просто маршрутов ещё нет. Если внутренний адрес отвечает, а публичный нет — закрыты порты во внешнем файрволе.
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Команда может выполняться до пары минут — 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: traefikkubectl 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=true2. В traefik-values.yaml ставим replicas: 2 и обновляем релиз с той же версией чарта:
helm upgrade traefik traefik/traefik --version 41.6.0 -n traefik -f traefik-values.yaml3. Добавляем в 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.
Почему 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....
ПодробнееРассмотрим настройку прозрачного проксирования в Squid без подмены сертификатов на базе ОС Ubuntu 22.04....
ПодробнееПолучаем валидные сертификаты SSL от Let's Encrypt для NGINX с использованием различных плагинов....
ПодробнееПоднимаем сервер SSL VPN OpenConnect для удаленного доступа пользователям....
ПодробнееНастраиваем обратный прокси сервер NGINX с SSL-терминацией и генерируем бесплатные валидные сертификаты SSL...
ПодробнееУстановка последней стабильной версии популярного веб сервера NGINX....
ПодробнееНовые комментарии
Цитата: FidoNet Цитата: sobir Цитата: FidoNet Спасибо за статью. Остальные
Цитата: sobir Цитата: FidoNet Спасибо за статью. Остальные статьи что находил
Цитата: FidoNet Цитата: sobir Цитата: FidoNet Можт быть дело в
Цитата: sobir Цитата: FidoNet Можт быть дело в dnsmasq.service? Ошибка Failed
Цитата: FidoNet Цитата: sobir Цитата: FidoNet Спасибо за статью. Остальные
Можт быть дело в dnsmasq.service? Ошибка Failed to set DNS configuration: Unit
Какой дистрибутив Linux вы часто используете?
Календарь
| « Ноябрь 2026 » | ||||||
|---|---|---|---|---|---|---|
| Пн | Вт | Ср | Чт | Пт | Сб | Вс |
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | ||||||





