K3s приходит в мир с готовым Traefik внутри, и это одновременно радость и повод разобраться. Радость - потому что ingress-контроллер уже крутится в кластере с момента первой загрузки, достаточно написать манифест Ingress и маршрутизация заработает. Повод разобраться - потому что дефолтная конфигурация скромная, дашборд выключен, автоматические сертификаты не настроены, а в продакшене всё это нужно почти всегда.

Traefik интересен тем, что не требует ни cert-manager, ни внешних балансировщиков для того, чтобы приложения в кластере получили валидные TLS-сертификаты. Встроенный ACME-клиент умеет сам пройти HTTP-01 challenge и получить сертификат от Let's Encrypt. Дашборд даёт живую картину того, какие роутеры, сервисы и middleware сейчас работают - неоценимая вещь при отладке сложных цепочек маршрутизации.

Что именно K3s ставит под капот и как это выглядит после установки

При стандартной установке K3s (curl -sfL https://get.k3s.io | sh -) Traefik разворачивается в namespace kube-system. Проверка показывает текущее состояние:

kubectl get pods -n kube-system -l app.kubernetes.io/name=traefik
kubectl get svc -n kube-system traefik

Сервис traefik имеет тип LoadBalancer, и именно за ним стоит Klipper LB - встроенный в K3s облегчённый балансировщик, который занимает 80 и 443 порты на всех нодах кластера. Это принципиально важно понять, потому что именно Klipper делает ingress доступным извне без необходимости в MetalLB или облачном балансировщике.

Если при установке K3s был передан флаг --disable traefik, контроллера не будет. Вернуть его можно переустановкой без этого флага либо ручной установкой через Helm. Для большинства сценариев селф-хостинга стандартная сборка подходит идеально, трогать её незачем.

Версия Traefik в свежих K3s - ветка v3 (начиная с K3s 1.28+), которая сильно отличается от v2 по синтаксису некоторых возможностей. Статьи двух-трёхлетней давности часто содержат устаревшие примеры конфигурации, это стоит иметь в виду при поиске решений.

Как Traefik конфигурируется в K3s и почему HelmChartConfig это главный инструмент

K3s устанавливает Traefik через встроенный helm-controller, и редактировать значения напрямую в чарте бесполезно - при следующем рестарте они перезапишутся. Правильный путь - создать HelmChartConfig в namespace kube-system, где описываются переопределения значений для чарта Traefik. Файл /var/lib/rancher/k3s/server/manifests/traefik-config.yaml подхватывается автоматически, без рестартов и ручных apply.

Базовый шаблон конфига выглядит так:

apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
spec:
  valuesContent: |-
    additionalArguments:
      - "--api.dashboard=true"
      - "--certificatesresolvers.letsencrypt.acme.email=Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript."
      - "--certificatesresolvers.letsencrypt.acme.storage=/data/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
    persistence:
      enabled: true
      size: 128Mi
      path: /data
    ports:
      web:
        redirectTo:
          port: websecure
          priority: 10
      websecure:
        tls:
          enabled: true

Каждая строка тут решает конкретную задачу. Первый аргумент включает dashboard-API. Три следующих настраивают ACME-резолвер с именем letsencrypt, который будет использоваться в ингрессах для автоматической выдачи сертификатов. Persistence обязательна - без неё файл acme.json с сохранёнными сертификатами будет теряться при каждом рестарте пода, Let's Encrypt быстро заблокирует по rate limit. Redirect с web на websecure закрывает вопрос автоматического перевода всего трафика на HTTPS.

После создания файла K3s применит его автоматически. Проверка:

kubectl get pods -n kube-system -l app.kubernetes.io/name=traefik -w
kubectl logs -n kube-system -l app.kubernetes.io/name=traefik | grep -i acme

В логах должны появиться строки про регистрацию с ACME-сервером.

DNS-подготовка и первая публикация приложения через Ingress

До того как Let's Encrypt начнёт выдавать сертификаты, DNS-записи должны указывать на публичный адрес кластера. Для единичного сервера это A-запись на IP мастера. Для кластера из нескольких нод проще всего использовать wildcard:

*.k8s.example.com.    A    203.0.113.42

Wildcard избавляет от необходимости создавать отдельную запись для каждого нового сервиса. Traefik дальше сам разберётся по заголовку Host, куда направлять запрос.

Тестовое приложение whoami - стандартный пример для проверки ingress. Развёртывание:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
spec:
  replicas: 1
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: traefik/whoami:latest
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: whoami
spec:
  selector:
    app: whoami
  ports:
    - port: 80
      targetPort: 80

Ингресс с автоматическим получением TLS от Let's Encrypt:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: whoami
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
    traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
spec:
  ingressClassName: traefik
  rules:
    - host: whoami.k8s.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: whoami
                port:
                  number: 80
  tls:
    - hosts:
        - whoami.k8s.example.com

Ключевая аннотация - traefik.ingress.kubernetes.io/router.tls.certresolver со значением letsencrypt. Это имя резолвера, которое было задано в HelmChartConfig. Traefik увидит новый ингресс, запустит HTTP-01 challenge, Let's Encrypt проверит владение доменом, и сертификат появится в acme.json автоматически. Процесс занимает от нескольких секунд до минуты, в логах пода traefik всё хорошо видно.

Проверка браузером или curl:

curl -v https://whoami.k8s.example.com

Ответ должен содержать заголовки от whoami и валидный сертификат, подписанный Let's Encrypt. Первые пару попыток стоит делать с помощью staging-сервера ACME, чтобы не упереться в rate-limit продакшена при отладке. URL staging-сервера - https://acme-staging-v02.api.letsencrypt.org/directory, его подставляют вместо production-эндпоинта в аргументы Traefik.

Дашборд Traefik и как открыть его безопасно

Дашборд Traefik показывает все роутеры, сервисы, middleware и точки входа в реальном времени. По умолчанию он недоступен извне, и это правильное поведение - открывать его в интернет без защиты категорически не стоит. Проверить работу локально можно через port-forward:

kubectl port-forward -n kube-system deployment/traefik 9000:9000

После этого по адресу http://localhost:9000/dashboard/ откроется интерфейс. Слеш в конце обязателен - Traefik чувствителен к этому моменту, без него вернёт 404.

Для долгосрочной работы удобнее опубликовать дашборд через IngressRoute с Basic Auth. Сначала создаётся секрет с паролем:

htpasswd -nb admin StrongPassword123 | base64

Полученная строка помещается в секрет. Дальше нужен middleware и IngressRoute:

apiVersion: v1
kind: Secret
metadata:
  name: traefik-dashboard-auth
  namespace: kube-system
type: Opaque
data:
  users: YWRtaW46JGFwcjEkOC5LTS5NdVkkRXg5...
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: dashboard-auth
  namespace: kube-system
spec:
  basicAuth:
    secret: traefik-dashboard-auth
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
  namespace: kube-system
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`traefik.k8s.example.com`)
      kind: Rule
      services:
        - name: api@internal
          kind: TraefikService
      middlewares:
        - name: dashboard-auth
  tls:
    certResolver: letsencrypt

Сервис api@internal - это специальное имя, которое Traefik резолвит сам в свой API-эндпоинт, физически такого сервиса в кластере нет. Middleware dashboard-auth добавляет проверку Basic Auth перед допуском к дашборду. Сертификат выдаётся тем же резолвером letsencrypt, что и для приложений.

После применения манифеста по адресу https://traefik.k8s.example.com откроется форма ввода логина-пароля, за ней - привычный интерфейс Traefik с вкладками HTTP, TCP, UDP, где видны все активные роутеры и их состояния. Полезнейший инструмент для отладки, когда Ingress вроде бы применён, а приложение почему-то не откликается.

Middleware как мощный слой между клиентом и приложением

Traefik отличается от классического nginx-ingress наличием продвинутых middleware - промежуточных обработчиков запросов, которые можно подключать к роутерам. Несколько сценариев, которые сильно упрощают жизнь:

  1. Редирект HTTPS с сохранением параметров, если глобальный редирект не подходит
  2. Базовая аутентификация для внутренних инструментов, пример с дашбордом выше
  3. Rate limiting для защиты API от перебора и DDoS
  4. Whitelist по IP для админских endpoint-ов
  5. Модификация заголовков, добавление security headers
  6. Сжатие ответов через gzip или brotli
  7. Retry с экспоненциальной задержкой для сглаживания временных ошибок бэкенда

Пример middleware для добавления security headers:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: security-headers
  namespace: default
spec:
  headers:
    frameDeny: true
    contentTypeNosniff: true
    browserXssFilter: true
    stsSeconds: 31536000
    stsIncludeSubdomains: true
    referrerPolicy: "strict-origin-when-cross-origin"
    permissionsPolicy: "geolocation=(), microphone=(), camera=()"

Подключение к ингрессу через аннотацию:

traefik.ingress.kubernetes.io/router.middlewares: default-security-headers@kubernetescrd

Формат namespace-name@kubernetescrd обязателен - именно так Traefik различает middleware из разных пространств имён. Ошибка в этой строке приводит к тому, что middleware тихо игнорируется.

Wildcard-сертификаты и переход на DNS-01 challenge

HTTP-01 challenge работает отлично для одиночных доменов, но не умеет выдавать wildcard-сертификаты на *.example.com. Для них нужен DNS-01 challenge, когда ACME-сервер проверяет владение доменом через TXT-записи в DNS. Traefik поддерживает десятки DNS-провайдеров из коробки, от Cloudflare до DigitalOcean.

Конфигурация DNS-01 требует передать токен API провайдера через переменные окружения и добавить аргументы в HelmChartConfig. Пример для Cloudflare:

additionalArguments:
  - "--certificatesresolvers.wildcard.acme.email=Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript."
  - "--certificatesresolvers.wildcard.acme.storage=/data/acme-wildcard.json"
  - "--certificatesresolvers.wildcard.acme.dnschallenge=true"
  - "--certificatesresolvers.wildcard.acme.dnschallenge.provider=cloudflare"
env:
  - name: CF_API_EMAIL
    valueFrom:
      secretKeyRef:
        name: cloudflare-api
        key: email
  - name: CF_API_KEY
    valueFrom:
      secretKeyRef:
        name: cloudflare-api
        key: apikey

В ингрессе указывается соответствующий резолвер через аннотации, а в spec.tls добавляются домены с wildcard. Один такой сертификат закрывает весь поддомен, и не нужно создавать новый ингресс для каждого сервиса только ради TLS. Удобство, которое оценивается по достоинству после первой дюжины развёрнутых приложений.

Несколько привычек, которые отличают рабочую установку от хрупкой

Первое - persistent volume для acme.json обязателен. Без него после каждого рестарта Traefik ходит за новыми сертификатами, и Let's Encrypt с его лимитом в 50 сертификатов на домен в неделю быстро закрывает кран. PersistentVolume даже на 128 Mi решает проблему навсегда.

Второе - staging-сервер Let's Encrypt при любых экспериментах. Переключение одной строкой в аргументах превращает продакшен-резолвер в тестовый, сертификаты получаются невалидные (браузер будет ругаться), зато rate limit на порядки выше. Когда всё заработало - меняется URL на production и выпускается настоящий сертификат.

Третье - мониторинг срока действия сертификатов. Traefik продлевает их автоматически за 30 дней до истечения, но если что-то пошло не так (изменились DNS-записи, сломалась связность), хочется узнать об этом заранее. Prometheus-метрики Traefik содержат информацию о certificate expiration, и простой alert в Grafana спасает от неожиданного падения TLS в пятницу вечером.

Четвёртое - разделение entrypoints для публичных и приватных сервисов. Если в кластере соседствуют приложения для интернета и внутренние админки, разумно завести отдельный entrypoint для internal-сервисов, привязанный к интерфейсу или локальной подсети. Traefik это поддерживает через конфигурацию, и один контроллер аккуратно обслуживает оба сценария без риска случайно открыть что-то лишнее наружу.

Traefik в K3s - это как раз тот случай, когда сложная задача (публикация сервисов наружу с TLS) решается несколькими строками конфига. Дашборд наглядно показывает, что происходит внутри, ACME-клиент молча выдаёт и обновляет сертификаты, middleware закрывают тонкие места. За простотой стоит немалая инженерная работа, и знакомство с ней окупается быстро, как только появляется первое приложение, которое стало нужно выложить в интернет под HTTPS за пятнадцать минут.