Traefik
Traefik — обратный прокси и терминатор TLS для Meduza. Он автоматически обнаруживает сервисы, зарегистрированные в каталоге сервисов Nomad, и строит правила маршрутизации на основе их тегов — без ручных конфигурационных файлов.
Как работает обнаружение сервисов
Traefik работает как системная задача в кластере Nomad и настроен на использование Nomad в качестве провайдера. Процесс обнаружения:
sequenceDiagram
participant Nomad
participant Traefik
participant LetsEncrypt as Let's Encrypt
participant Client
Nomad->>Traefik: Service registered with traefik.* tags
Traefik->>Traefik: Parse tags → build router + service
Traefik->>LetsEncrypt: Request TLS certificate for domain
LetsEncrypt-->>Traefik: Certificate issued
Client->>Traefik: HTTPS request (Host: app.meduza.io)
Traefik->>Traefik: Match router rule → resolve backend
Traefik->>Nomad: Forward to healthy task allocationКогда Nomad-задача деплоится с тегами сервисов traefik.*, Traefik автоматически подхватывает их и:
- Создаёт роутер с указанным правилом
Host() - Привязывает его к точке входа
websecure(порт 443) - Запрашивает TLS-сертификат у Let's Encrypt, если он ещё не закеширован
- Создаёт сервис (балансировщик нагрузки), указывающий на адрес и порт задачи
TIP
Перезапуск Traefik не требуется при деплое новых сервисов или обновлении существующих. Обнаружение полностью динамическое.
Точки входа
Traefik настроен с двумя точками входа:
| Точка входа | Порт | Назначение |
|---|---|---|
web | 80 | HTTP — перенаправляет на HTTPS |
websecure | 443 | HTTPS — весь продакшн-трафик |
Точка входа web настроена на постоянное перенаправление всего трафика на websecure:
# traefik static configuration (excerpt)
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"Маршрутизация на основе тегов
Маршрутизация полностью настраивается через теги сервисов Nomad. Вот паттерн, используемый для всех фронтенд-сервисов Meduza:
service {
name = "hub-${var.env}-frontend"
port = "http"
provider = "nomad"
tags = [
"traefik.enable=true",
"traefik.http.routers.hub-${var.env}-frontend.rule=Host(`${var.domain}`)",
"traefik.http.routers.hub-${var.env}-frontend.entrypoints=websecure",
"traefik.http.routers.hub-${var.env}-frontend.tls.certresolver=letsencrypt",
]
}Расшифровка тегов
Каждый тег соответствует директиве конфигурации Traefik:
| Тег | Назначение |
|---|---|
traefik.enable=true | Указывает Traefik подхватить этот сервис (сервисы без этого тега игнорируются) |
traefik.http.routers.NAME.rule=Host(...) | Сопоставление запросов, у которых заголовок Host равен указанному домену |
traefik.http.routers.NAME.entrypoints=websecure | Прослушивание только на HTTPS точке входа (порт 443) |
traefik.http.routers.NAME.tls.certresolver=letsencrypt | Использование Let's Encrypt для автоматического выпуска и обновления сертификатов |
WARNING
Имя роутера (например, hub-prod-frontend) должно быть уникальным среди всех сервисов. Если два сервиса используют одно имя роутера, Traefik объединит их конфигурации непредсказуемо. Всегда включайте окружение в имя.
Соглашение об именовании
Имя роутера следует паттерну: {service}-{env}-{role}
| Окружение | Имя роутера | Домен |
|---|---|---|
| prod | hub-prod-frontend | Продакшн-домен |
| stage | hub-stage-frontend | Стейджинг-домен |
| dev | hub-dev-frontend | Dev-домен |
TLS с Let's Encrypt
Traefik автоматически выпускает и обновляет TLS-сертификаты, используя протокол ACME с Let's Encrypt.
Конфигурация
# traefik static configuration (excerpt)
certificatesResolvers:
letsencrypt:
acme:
email: admin@meduza.io
storage: /data/acme.json
httpChallenge:
entryPoint: webКак это работает
- Когда новый роутер ссылается на
certresolver=letsencrypt, Traefik проверяет, есть ли действительный сертификат вacme.json. - Если нет, инициируется HTTP-01 challenge: Let's Encrypt отправляет запрос на
http://<domain>/.well-known/acme-challenge/<token>. - Traefik автоматически отвечает на challenge через точку входа
web. - После валидации сертификат сохраняется в
acme.jsonи используется для всех последующих HTTPS-запросов. - Traefik обновляет сертификаты автоматически до истечения срока (обычно за 30 дней до 90-дневного срока).
TIP
Файл acme.json содержит приватные ключи. Он должен быть сохранён на томе хоста и иметь права 600. Потеря этого файла означает, что все сертификаты будут запрошены заново при перезапуске.
Балансировка нагрузки
Когда Nomad-задача имеет несколько аллокаций (например, backend_replicas = 2), Traefik автоматически балансирует нагрузку между всеми здоровыми экземплярами. Стратегия по умолчанию — взвешенный round-robin.
flowchart LR
Client -->|HTTPS| Traefik
Traefik -->|round-robin| A1["Backend Alloc 1\n:dynamic-port"]
Traefik -->|round-robin| A2["Backend Alloc 2\n:dynamic-port"]Traefik маршрутизирует только на аллокации, чьи проверки здоровья Nomad проходят успешно. Если аллокация становится нездоровой, она автоматически удаляется из пула балансировщика.
Отладка
Проверка Traefik Dashboard
SSH-туннель к Traefik Dashboard:
ssh -L 8080:localhost:8080 user@kvm-hostЗатем откройте http://localhost:8080 в браузере. Dashboard показывает:
- Все обнаруженные роутеры и их правила
- Все сервисы и их статус здоровья
- Статус TLS-сертификатов
- Конфигурацию точек входа
Проверка роутеров через API
# List all HTTP routers
curl -s http://localhost:8080/api/http/routers | jq .
# Check a specific router
curl -s http://localhost:8080/api/http/routers/hub-prod-frontend@nomad | jq .Типичные проблемы
| Симптом | Вероятная причина | Решение |
|---|---|---|
| 404 на валидном домене | Теги сервиса отсутствуют или несовпадение имени роутера | Проверьте nomad job inspect на корректность тегов |
| Ошибка TLS-сертификата | Не прошёл ACME challenge (порт 80 заблокирован?) | Убедитесь, что порт 80 открыт и точка входа web работает |
| 502 Bad Gateway | Бэкенд не работает или проверка здоровья падает | Проверьте nomad alloc status для целевого сервиса |
| Запросы уходят не в тот сервис | Дублирование имён роутеров между задачами | Убедитесь, что имена роутеров содержат ${var.env} |