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 - промежуточных обработчиков запросов, которые можно подключать к роутерам. Несколько сценариев, которые сильно упрощают жизнь:
- Редирект HTTPS с сохранением параметров, если глобальный редирект не подходит
- Базовая аутентификация для внутренних инструментов, пример с дашбордом выше
- Rate limiting для защиты API от перебора и DDoS
- Whitelist по IP для админских endpoint-ов
- Модификация заголовков, добавление security headers
- Сжатие ответов через gzip или brotli
- 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 за пятнадцать минут.