Traefik — обратный прокси и балансировщик нагрузки, написанный на Go. Его главная особенность в том, что маршруты он собирает не из одного большого конфига, а из данных провайдеров: Docker, Podman, Kubernetes, файлов YAML. Добавили контейнер с метками — Traefik сам создал роутер и сервис, перечитал состояние и начал проксировать трафик. Для домашней лаборатории и небольшого продакшена это экономит много времени, но требует понимания модели объектов и дисциплины в диагностике. Ниже — рабочий сценарий для Docker с проверкой каждого шага.

Как устроена модель Traefik

В основе лежат четыре сущности. Entrypoint — порт, который слушает прокси, например 80 и 443. Router — правило вида Host(`app.example.com`), которое решает, куда отправить запрос. Service — адрес и порт бэкенда, куда проксирует роутер. Middleware — промежуточный слой: редирект, заголовки, базовая авторизация, ограничение скорости. Провайдер Docker читает метки контейнеров и превращает их в эти объекты; файловый провайдер делает то же самое из YAML.

  • Маршруты обновляются без перезапуска процесса.
  • Один контейнер может обслуживать десятки доменов.
  • Сертификаты Let’s Encrypt выпускаются автоматически.
  • Конфигурация хранится рядом с приложением, а не в отдельном файле.

Быстрый запуск в Docker Compose

Начните с отдельной сети: к ней будут подключаться прокси и приложения. Сокет Docker монтируется только для чтения — этого достаточно для обнаружения контейнеров. Каталог для сертификатов лучше вынести на хост, чтобы acme.json пережил пересоздание контейнера.

services:
  traefik:
    image: traefik:v3.3
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.le.acme.email=admin@example.com
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks: [proxy]

networks:
  proxy:
    name: proxy

Запуск и проверка состояния выполняются обычными командами Compose. Если контейнер падает, смотрите не только статус, но и полный лог: Traefik часто пишет причину отказа от сертификата или ошибку разбора метки.

docker compose up -d
docker compose ps
docker compose logs -f --tail=100 traefik
curl -I http://localhost

Маршрутизация через метки

Приложение не обязано публиковать порт на хост. Достаточно подключить его к сети proxy и описать правила. Параметр exposedbydefault=false важен: без него любой контейнер с портом попадёт в маршрутизацию, что создаёт лишнюю поверхность атаки.

services:
  app:
    image: nginx:alpine
    networks: [proxy]
    labels:
      - traefik.enable=true
      - traefik.http.routers.app.rule=Host(`app.example.com`)
      - traefik.http.routers.app.entrypoints=websecure
      - traefik.http.routers.app.tls.certresolver=le
      - traefik.http.services.app.loadbalancer.server.port=80
      - traefik.docker.network=proxy

Обратите внимание на имя сервиса: traefik.http.services.app.... Оно должно совпадать с именем в ссылке роутера, иначе Traefik создаст сервис с другим именем и вернёт 502. Внутренний порт указывается тот, который слушает контейнер, а не опубликованный на хосте.

HTTPS, редирект и защита

HTTP-роутер нужен только для редиректа и прохождения ACME-проверки. Не закрывайте 80-й порт полностью: HTTP-01 challenge должен быть доступен извне. Для служебных панелей добавляйте базовую авторизацию и заголовки безопасности.

labels:
  - traefik.http.middlewares.redirect.redirectscheme.scheme=https
  - traefik.http.routers.app-http.rule=Host(`app.example.com`)
  - traefik.http.routers.app-http.entrypoints=web
  - traefik.http.routers.app-http.middlewares=redirect
  - traefik.http.middlewares.sec.headers.stsSeconds=31536000
  - traefik.http.middlewares.sec.headers.contentTypeNosniff=true
  - traefik.http.middlewares.sec.headers.frameDeny=true
  - traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$hash

Хеш для basic auth получают через htpasswd -nb admin пароль. Символы доллара в Compose нужно удваивать, иначе переменная подставится как пустая строка. HSTS включайте только после того, как убедились в работоспособности HTTPS: браузер запомнит заголовок надолго.

Файловый провайдер и горячая перезагрузка

Не всё живёт в контейнерах: старый сервис на виртуальной машине или демон на самом хосте тоже нужно опубликовать. Для этого подключают файловый провайдер и описывают роутеры в отдельном YAML. Режим watch перечитывает каталог при изменении, поэтому перезапуск контейнера не требуется. При синтаксической ошибке Traefik сохраняет последнюю рабочую конфигурацию и пишет причину в лог — это лучше, чем молча потерять маршрут.

providers:
  file:
    directory: /etc/traefik/dynamic
    watch: true

http:
  routers:
    legacy:
      rule: "Host(`legacy.example.com`)"
      service: legacy
  services:
    legacy:
      loadBalancer:
        servers:
          - url: "http://192.168.1.20:8080"

Файлы удобно держать в git и проверять локально тем же образом, что и в продакшене. Не смешивайте в одном каталоге временные файлы редактора: провайдер попытается разобрать их как конфигурацию и напишет ошибку.

Панель управления и API

Дашборд удобен для отладки, но в интернет его выставлять нельзя. Включайте API, закрывайте отдельным роутером с авторизацией и разрешайте доступ по IP или через локальную сеть. Сырые данные API показывают все роутеры, сервисы и middleware — это лучший способ понять, почему запрос ушёл не туда.

command:
  - --api.dashboard=true
  - --api.insecure=false

curl -s http://127.0.0.1:8080/api/rawdata | jq '.routers'
curl -s http://127.0.0.1:8080/api/overview | jq

Диагностика типовых сбоев

Код 404 означает, что ни один роутер не совпал с запросом: проверьте Host, entrypoint и то, что контейнер действительно в сети proxy. Код 502 говорит, что роутер есть, а сервис недоступен: обычно неверный внутренний порт или приложение слушает только localhost. Ошибки сертификата почти всегда связаны с DNS: имя должно указывать на внешний IP, а 80-й порт — быть открыт.

docker inspect $(docker compose ps -q app) --format '{{json .NetworkSettings.Networks}}'
curl -v --resolve app.example.com:443:127.0.0.1 https://app.example.com
openssl s_client -connect app.example.com:443 -servername app.example.com </dev/null 2>/dev/null | openssl x509 -noout -dates
docker run --rm --network proxy curlimages/curl -s http://app:80/ | head

Чек-лист перед продакшеном

  • Тег образа зафиксирован, а не равен latest.
  • Сокет Docker смонтирован read-only; в идеале — через ограничивающий прокси.
  • Каталог acme.json имеет права 600 и входит в резервное копирование.
  • Включены access-логи и метрики, настроен внешний мониторинг.
  • Дашборд не доступен публично без авторизации.
  • Проверены редирект, HSTS, лимиты и поведение при недоступном бэкенде.
  • Отдельно проверено поведение при медленном DNS и истёкшем сертификате.

Итог

Traefik выигрывает там, где контейнеры появляются и исчезают часто: маршруты живут рядом с приложением, а сертификаты обновляются сами. Начните с минимального Compose-файла, проверьте один домен, затем добавляйте middleware и мониторинг. Держите конфигурацию в git, документируйте метки и не открывайте API наружу — и автоконфигурация останется удобством, а не источником неожиданностей.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте