- uncomment and finalize port protocols and backend schemes in traefik.yml - adjust longhorn UI basicauth to disabled for simplified access - add reportdb-pgsql, salt-publish, and salt-request ports to traefik-values template - fix conditional execution in helm task to prevent unintended skip - fix uyuni database credentials by replacing dynamic password lookups with static values - increase uyuni PVC storage requests from 1-20Mi to 300Mi for consistency |
||
|---|---|---|
| .. | ||
| defaults | ||
| tasks | ||
| templates | ||
| README.md | ||
Traefik — стандарт публикации сервисов в кластере
Этот документ описывает стандарт подключения новых HTTP-сервисов к Traefik в нашем кластере.
Является эталоном для разработчиков и DevOps при добавлении любого нового сервиса.
Содержание
- Архитектура: как Traefik работает в кластере
- Единственный способ добавить сервис: traefik_port_map
- Пример 1: сервис без аутентификации
- Пример 2: сервис с BasicAuth
- Пример 3: сервис в том же namespace что и Traefik
- Пример 4: несколько маршрутов для одного приложения
- Создание BasicAuth-секрета
- Добавление нового сервиса: пошаговая инструкция
- Ограничения и правила именования
- Диагностика и типичные ошибки
- Таблица активных сервисов
1. Архитектура: как Traefik работает в кластере
Схема
Пользователь
│
│ HTTP :10001, :10002, :10003, ... (порты 10000-10999)
▼
k8s-worker-01 (10.203.0.96)
│
│ hostPort (DaemonSet Pod слушает на хосте)
▼
Traefik Pod (namespace: traefik)
│
│ IngressRoute CRD → EntryPoint → Service
│
├──► kubernetes-dashboard.kubernetes-dashboard:443
├──► longhorn-frontend.longhorn-system:80
└──► my-service.my-namespace:8080
Ключевые решения архитектуры
| Параметр | Значение | Причина |
|---|---|---|
| Режим деплоя | DaemonSet | Pod на каждой worker-ноде; при добавлении нод — автоматически |
| Тип доступа | hostPort | Прямое связывание порта Pod с портом хоста; не нужен NodePort или LoadBalancer |
| Маршрутизация | IngressRoute CRD | Не Ingress; только CRD Traefik — полный контроль над entryPoint |
| Namespace | traefik |
Изолированный namespace; IngressRoute живут здесь, сервисы — в своих |
| TLS | выключен | Внутренняя сеть, доверенные клиенты; TLS добавить при необходимости |
| hostname routing | нет | Каждый сервис на своём порту; нет нужды в DNS-именах |
allowCrossNamespace |
true | IngressRoute в traefik ссылается на Service в других namespace |
Диапазон портов
10000–10999 — зарезервировано для Traefik-сервисов
открыт на firewalld на всех worker-нодах
Порт 10000 — зарезервирован (не использовать).
Порты 10001–10999 — свободны для сервисов, назначаются последовательно.
2. Единственный способ добавить сервис: traefik_port_map
Вся топология Traefik определяется через одну переменную в inventory/prod/group_vars/traefik.yml.
Не создавайте IngressRoute и Middleware вручную — они будут перезаписаны при следующем запуске плейбука.
Структура записи в traefik_port_map
traefik_port_map:
- name: my-service # ≤15 символов! становится именем entryPoint и ресурсов
description: "Описание" # только для читаемости, не влияет на деплой
port: 10003 # уникальный порт из диапазона 10001-10999
backend:
namespace: my-namespace # namespace, где живёт целевой Service
service: my-service-svc # имя Service (не Pod, не Deployment)
port: 8080 # порт Service (не containerPort)
scheme: http # http или https (если backend использует TLS)
basicauth:
enabled: false # true — включить BasicAuth для этого маршрута
# secret_name: traefik-auth-my-service # нужно только если enabled: true
Что происходит после добавления записи
При запуске ansible-playbook playbooks/setup_traefik.yml:
- Helm перерендеривает
traefik-values.yml— добавляет новый entryPointmy-serviceна порт 10003 - Middleware (если
basicauth.enabled: true) — создаёт объектMiddlewareв namespacetraefik - IngressRoute — создаёт объект
IngressRouteв namespacetraefik, ссылающийся на backend Service - Helm upgrade применяет изменения; Traefik Pod перезапускается (DaemonSet rolling update)
3. Пример 1: сервис без аутентификации
Сценарий: развернули веб-приложение my-app в namespace my-app, хотим сделать его доступным на порту 10003.
Шаг 1: убедиться что Service существует
# Это должно быть в манифестах приложения (fleet-repo или kubectl apply)
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: my-app
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP # ClusterIP достаточно — Traefik обращается по внутренней сети
Шаг 2: добавить запись в traefik_port_map
# inventory/prod/group_vars/traefik.yml
traefik_port_map:
# ... существующие записи ...
- name: my-app # ≤15 символов
description: "My Application"
port: 10003
backend:
namespace: my-app
service: my-app
port: 80
scheme: http
basicauth:
enabled: false
Шаг 3: запустить плейбук
ansible-playbook -i inventory/prod playbooks/setup_traefik.yml
Шаг 4: проверить доступность
# С любой машины в сети
curl http://10.203.0.96:10003/
# Проверить что IngressRoute создан
kubectl get ingressroute -n traefik
kubectl describe ingressroute route-my-app -n traefik
4. Пример 2: сервис с BasicAuth
Сценарий: Grafana в namespace monitoring, доступ только с паролем.
Шаг 1: создать Secret с учётными данными
# Сгенерировать хэш пароля и создать Secret вручную (один раз)
kubectl create secret generic traefik-auth-grafana \
--from-literal=users="$(openssl passwd -apr1 'SecurePassword' | xargs -I{} echo 'admin:{}')" \
-n traefik
Secret создаётся в namespace
traefik, а не в namespace приложения.
Шаг 2: добавить запись
# inventory/prod/group_vars/traefik.yml
traefik_port_map:
- name: grafana
description: "Grafana Monitoring Dashboard"
port: 10004
backend:
namespace: monitoring
service: grafana
port: 3000
scheme: http
basicauth:
enabled: true
secret_name: traefik-auth-grafana # имя Secret из шага 1
Шаг 3: запустить плейбук
ansible-playbook -i inventory/prod playbooks/setup_traefik.yml
Плейбук создаст Middleware объект:
# Автогенерируется из basicauth-middleware.yml.j2
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: basicauth-grafana
namespace: traefik
spec:
basicAuth:
secret: traefik-auth-grafana
removeHeader: true # не передавать Authorization header в backend
Шаг 4: проверить
# Без пароля — должен вернуть 401
curl -v http://10.203.0.96:10004/
# С паролем — должен вернуть 200
curl -u admin:SecurePassword http://10.203.0.96:10004/
5. Пример 3: сервис в том же namespace что и Traefik
Сценарий: какое-то приложение развёрнуто прямо в namespace traefik (нетипично, но возможно).
Для сервисов в том же namespace traefik параметр allowCrossNamespace не нужен — он уже есть.
Запись в traefik_port_map стандартная, просто namespace: traefik:
- name: whoami
description: "Debug: whoami service"
port: 10010
backend:
namespace: traefik # тот же namespace
service: whoami
port: 80
scheme: http
basicauth:
enabled: false
6. Пример 4: несколько маршрутов для одного приложения
Сценарий: у приложения есть отдельный API и UI, их нужно опубликовать на разных портах независимо (например, для разных уровней доступа).
traefik_port_map:
# UI — с BasicAuth
- name: myapp-ui
description: "MyApp Frontend"
port: 10005
backend:
namespace: myapp
service: myapp-frontend
port: 80
scheme: http
basicauth:
enabled: true
secret_name: traefik-auth-myapp
# API — без BasicAuth (аутентификация на уровне приложения)
- name: myapp-api
description: "MyApp Backend API"
port: 10006
backend:
namespace: myapp
service: myapp-backend
port: 8080
scheme: http
basicauth:
enabled: false
Почему не один порт с path-routing? Traefik в нашей конфигурации использует
PathPrefix(/)для всех маршрутов — это намеренное упрощение. Каждый сервис получает свой порт. Path-routing возможен, но усложняет конфигурацию и не соответствует нашему стандарту порт-прокси.
7. Создание BasicAuth-секрета
Формат
Secret должен содержать ключ users с htpasswd-строками (один пользователь на строку):
admin:$apr1$xyz$hashedpassword
Один пользователь
kubectl create secret generic traefik-auth-<service-name> \
--from-literal=users="$(openssl passwd -apr1 'PASSWORD' | xargs -I{} echo 'USERNAME:{}')" \
-n traefik
Несколько пользователей
# Сгенерировать файл htpasswd
htpasswd -c /tmp/auth admin # первый пользователь (создаёт файл)
htpasswd /tmp/auth developer # второй пользователь (добавляет)
# Создать Secret из файла
kubectl create secret generic traefik-auth-myapp \
--from-file=users=/tmp/auth \
-n traefik
rm /tmp/auth
Обновить пароль существующего пользователя
# Удалить старый Secret и создать новый — Traefik подхватит без перезапуска
kubectl delete secret traefik-auth-myapp -n traefik
kubectl create secret generic traefik-auth-myapp \
--from-literal=users="$(openssl passwd -apr1 'NewPassword' | xargs -I{} echo 'admin:{}')" \
-n traefik
Traefik читает Secret динамически. После замены Secret новые пароли начинают работать в течение 30-60 секунд без перезапуска Pod.
Именование секретов
Формат: traefik-auth-<service-name>, где <service-name> совпадает с полем name в traefik_port_map.
Пример: name: grafana → secret_name: traefik-auth-grafana.
8. Добавление нового сервиса: пошаговая инструкция
1. Выбрать свободный порт из диапазона 10001-10999
└─ Проверить таблицу активных сервисов в этом README
2. Придумать name (≤15 символов, только строчные буквы, цифры, дефис)
└─ Пример: my-service, grafana, pgadmin, api-v2
3. Убедиться что Service существует в кластере
└─ kubectl get svc -n <namespace> <service-name>
4. (если BasicAuth нужен) Создать Secret в namespace traefik
└─ kubectl create secret generic traefik-auth-<name> ...
5. Добавить запись в inventory/prod/group_vars/traefik.yml
6. Обновить таблицу в этом README (раздел 11)
7. Запустить плейбук:
└─ ansible-playbook -i inventory/prod playbooks/setup_traefik.yml
8. Проверить:
└─ curl http://10.203.0.96:<port>/
└─ kubectl get ingressroute -n traefik
└─ kubectl describe ingressroute route-<name> -n traefik
9. Ограничения и правила именования
Критические ограничения
| Ограничение | Причина | Последствие нарушения |
|---|---|---|
name ≤ 15 символов |
Используется как имя Kubernetes container port | helm upgrade падает с ошибкой валидации |
name только [a-z0-9-] |
Kubernetes naming convention | Ошибка применения манифеста |
| Порты уникальны | hostPort — один порт = один Pod на ноде | Конфликт портов, Pod не стартует |
Secret в namespace traefik |
IngressRoute и Middleware в traefik |
Middleware не найдёт Secret, маршрут недоступен (404) |
| Service тип ClusterIP | Достаточно для Traefik → Service связки | NodePort и LoadBalancer избыточны |
Что происходит при отсутствии BasicAuth-секрета
Если Secret указан в secret_name, но не существует:
- Traefik полностью отключает маршрут (возвращает 404, не 401)
- Другие маршруты продолжают работать
- В логах Traefik:
middleware "traefik/basicauth-X" does not exist
Всегда создавайте Secret до запуска плейбука.
Rolling update DaemonSet: возможное зависание
При обновлении Traefik (новый Helm values) DaemonSet пересоздаёт Pod на каждой ноде.
Если старый Pod не успел завершиться, новый не может занять hostPort:
# Симптом: Pod застрял в Pending
kubectl get pods -n traefik -o wide
# Решение: принудительно удалить старые Pod
kubectl delete pod -n traefik -l app.kubernetes.io/name=traefik
10. Диагностика и типичные ошибки
Основные команды
# Список всех IngressRoute
kubectl get ingressroute -n traefik
# Детали конкретного маршрута
kubectl describe ingressroute route-my-service -n traefik
# Список Middleware
kubectl get middleware -n traefik
# Логи Traefik (последние 100 строк)
kubectl logs -n traefik -l app.kubernetes.io/name=traefik --tail=100
# Access логи (каждый запрос)
kubectl logs -n traefik -l app.kubernetes.io/name=traefik --tail=50 | grep '"method"'
# Traefik Dashboard (если включён — у нас выключен по умолчанию)
# kubectl port-forward -n traefik svc/traefik 9000:9000
# open http://localhost:9000/dashboard/
# Проверить что порт слушается на хосте
# (выполнить через SSH на worker-ноде)
ss -tlnp | grep 10003
# Проверить firewalld
firewall-cmd --list-ports | grep 10000-10999
Таблица ошибок
| Симптом | Причина | Решение |
|---|---|---|
| 404 на порту | IngressRoute не создан | Проверить kubectl get ingressroute -n traefik |
| 404 при включённом BasicAuth | Secret не существует | Создать Secret в namespace traefik |
| 401 без запроса пароля | BasicAuth не применился | Проверить kubectl get middleware -n traefik |
| Connection refused | Порт не открыт на firewall | firewall-cmd --add-port=10003/tcp --permanent && firewall-cmd --reload |
| Connection refused | Traefik Pod не запущен | kubectl get pods -n traefik |
| Bad Gateway (502) | Backend Service недоступен | kubectl get svc -n <namespace>, проверить Endpoints |
helm upgrade ошибка |
Имя name > 15 символов |
Сократить имя в traefik_port_map |
| Pod застрял в Pending | hostPort конфликт | Удалить старый Pod вручную |
Проверка backend: доступен ли Service из Pod Traefik
# Запустить временный Pod и обратиться к backend
kubectl run -it --rm debug --image=curlimages/curl --restart=Never \
-- curl -v http://my-service.my-namespace.svc.cluster.local:80/health
# Или через exec в Pod Traefik
kubectl exec -n traefik \
$(kubectl get pod -n traefik -l app.kubernetes.io/name=traefik -o name | head -1) \
-- wget -qO- http://my-service.my-namespace.svc.cluster.local:80/health
Проверка конфигурации без применения
# Посмотреть что сгенерирует шаблон (dry-run Ansible)
ansible-playbook -i inventory/prod playbooks/setup_traefik.yml --check --diff
# Просмотреть текущий Helm values в кластере
helm get values traefik -n traefik
11. Таблица активных сервисов
Обновлять при каждом добавлении или удалении сервиса.
| Порт | Name | Сервис | Namespace | BasicAuth | Secret |
|---|---|---|---|---|---|
| 10001 | k8s-dashboard |
kubernetes-dashboard:443 |
kubernetes-dashboard |
нет | — |
| 10002 | longhorn-ui |
longhorn-frontend:80 |
longhorn-system |
да | traefik-auth-longhorn |
Следующий свободный порт: 10003