Автоматический TLS с cert-manager, Let's Encrypt и Traefik IngressRoutes

Published: 2026-04-15

Каждый поддомен на этом сервере получает действующий TLS-сертификат автоматически. Никаких ручных продлений, никаких cronjob с certbot, никаких wildcard-сертификатов. Вот полная картина — от ClusterIssuer до замочка в браузере.


Два issuer: staging и production

yamlapiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-staging
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik

Staging-issuer не расходует лимиты Let's Encrypt (50 сертификатов/домен/неделю). Всегда использовать staging при настройке нового домена — если HTTP-01 challenge провалится (неправильный DNS, заблокирован порт 80), лимиты production не сгорят.


Механика HTTP-01 challenge

Solver http01 доказывает владение доменом, отдавая токен по адресу:

http://<domain>/.well-known/acme-challenge/<token>

cert-manager временно создаёт объект Ingress для этого пути. Traefik подхватывает его и маршрутизирует запрос ACME-сервера к поду cert-manager.

Обязательные условия:

  1. A-запись домена должна указывать на публичный IP сервера до запроса сертификата.
  2. Порт 80 должен быть открыт. Traefik слушает на 80 с hostNetwork: true и перенаправляет на 443 — но ACME challenge обслуживается на 80 до срабатывания редиректа.

Объект Certificate

yamlapiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: site-tls
  namespace: default
spec:
  secretName: site-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - example.com
    - www.example.com
    - blog.example.com
    - cv.example.com
    - proxy.example.com
  renewBefore: 720h  # за 30 дней до истечения

Все поддомены используют общий сертификат с несколькими SAN. cert-manager:

  1. Запрашивает единый сертификат для всех имён
  2. Сохраняет ключ и цепочку сертификатов в Secret site-tls
  3. Автоматически продлевает за ~30 дней до истечения
  4. Запускает ротацию при добавлении новых записей в dnsNames

Привязка в IngressRoute

Traefik использует собственный CRD вместо стандартного Ingress. Секция TLS ссылается на тот же Secret:

yamlapiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: site-main
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`example.com`)
      kind: Rule
      services:
        - name: site-backend
          port: 8000
    - match: Host(`www.example.com`)
      kind: Rule
      middlewares:
        - name: www-redirect
      services:
        - name: site-backend
          port: 8000
  tls:
    secretName: site-tls

Middleware: поддомен → path prefix

FastAPI-приложение обслуживает всё через один роутер. Поддомены переписываются в path-префиксы через Traefik Middleware:

yamlapiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: blog-prefix
spec:
  addPrefix:
    prefix: /weblog
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: www-redirect
spec:
  redirectRegex:
    regex: "^https://www\\.(.+)"
    replacement: "https://${1}"
    permanent: true

blog.example.com/ → приложение видит /weblog/. Один под, один Service, один Deployment обрабатывает все поддомены.


Проверка статуса сертификата

bash# Список всех сертификатов
kubectl get certificate -A

# Детали конкретного сертификата
kubectl describe certificate site-tls

# ACME order (если сертификат ожидает выпуска)
kubectl get orders -A
kubectl describe order <order-name>

# Challenge (если order ожидает)
kubectl get challenges -A
kubectl describe challenge <challenge-name>

Условие Ready: True — сертификат выпущен и сохранён. Если статус застрял в False, отследить цепочку order → challenge.


Отладка HTTP-01 challenge вручную

bash# Проверить, что solver-под cert-manager запущен
kubectl get pods -n cert-manager

# Проверить, что Traefik подхватил временный Ingress
kubectl get ingress -A | grep acme

# Проверить доступность порта 80
curl -v http://example.com/.well-known/acme-challenge/test

Принудительное обновление сертификата

cert-manager продлевает автоматически. Для принудительного обновления:

bashkubectl delete secret site-tls

cert-manager обнаруживает отсутствие Secret, повторно запускает ACME-процесс и создаёт новый сертификат. Traefik короткое время отдаёт старый сертификат из памяти (обычно менее 60 секунд).


Лимиты Let's Encrypt

  • 50 сертификатов на зарегистрированный домен в неделю
  • 5 ошибок на аккаунт на hostname в час
  • У staging лимитов нет (но браузеры таким сертификатам не доверяют)

При превышении лимитов проверить все выпущенные сертификаты: https://crt.sh/?q=example.com.