Новый деплой = правка nginx вручную: переходим на Traefik для 8 микросервисов

Задача клиента: каждый деплой — ручная правка конфига

В феврале 2026 года к нам в АйТи Фреш обратился стартап «АппЛаб» из Москвы — команда из 12 разработчиков, создающая мобильные приложения для ритейла. Бэкенд состоял из 8 микросервисов в Docker: API gateway, auth-сервис, каталог товаров, корзина, платежи, уведомления, аналитика и admin-панель. Всё работало на двух серверах за nginx reverse proxy.

Проблема звучала просто, но отравляла жизнь всей команде: каждый деплой требовал ручной правки nginx.conf. Новый сервис? Правь конфиг, добавляй upstream, перезагружай nginx. Изменился порт? Правь конфиг. Canary-деплой? Вручную меняй веса в upstream. SSL-сертификаты? Certbot renew по cron, и однажды он сломался, и сертификат истёк в пятницу вечером.

За последний месяц команда 14 раз правила nginx.conf вручную, и дважды это привело к даунтайму из-за опечаток.

«Мы тратим время DevOps-инженера не на автоматизацию, а на ручные правки конфига. Каждый второй PR в нашем репозитории инфраструктуры — это изменение nginx.conf. Это не масштабируется» — CTO «АппЛаб».

Аудит текущей конфигурации

Мы проанализировали nginx-конфиг и Docker-инфраструктуру:

# Текущий docker-compose.yml (упрощённо)
# 8 сервисов, каждый на своём порту
services:
  api-gateway:    # :3000
  auth-service:   # :3001
  catalog:        # :3002
  cart:           # :3003
  payments:       # :3004
  notifications:  # :3005
  analytics:      # :3006
  admin-panel:    # :3007

# nginx.conf — 247 строк, 8 upstream-блоков
cat /etc/nginx/conf.d/applab.conf | wc -l
# 247

# Каждый сервис — отдельный upstream + location:
# upstream api_gateway {
#     server 172.18.0.2:3000;
# }
# upstream auth_service {
#     server 172.18.0.3:3001;
# }
# ... и так 8 раз

# Git log инфраструктурного репозитория:
git log --oneline --since="2026-01-01" -- nginx/ | wc -l
# 14 коммитов только в nginx-конфиги за январь

# Certbot — сертификаты через cron:
crontab -l | grep certbot
# 0 3 * * 1 certbot renew --nginx --quiet
# Последний renew: FAILED (nginx config test failed after changes)

Проблемы:

  • Ручное управление — IP-адреса контейнеров захардкожены в upstream
  • Нет service discovery — при пересоздании контейнера IP меняется
  • Нет health checks — nginx продолжал слать трафик на мёртвые контейнеры
  • Certbot на cron — ненадёжно, при ошибке никто не узнаёт
  • Нет метрик — отсутствует мониторинг latency, error rate по сервисам
  • Canary-деплои — вручную через upstream weights

Почему Traefik

Мы рассмотрели три варианта замены nginx:

РешениеПлюсыМинусы
nginx + consul-templateЗнакомый nginx, автогенерация конфигаТребует Consul, сложная настройка
Envoy ProxyМощный, xDS APIСложный, overengineered для 8 сервисов
Traefik v3Нативный Docker provider, auto-discovery, Let's EncryptЧуть ниже производительность на high-traffic

Traefik победил: он был создан именно для Docker и микросервисов. Docker provider автоматически обнаруживает контейнеры и создаёт маршруты на основе labels — не нужно править конфиг вообще. Let's Encrypt встроен. Middleware для rate-limit, auth, headers — из коробки. При 8 микросервисах и текущих нагрузках (5 000 RPS) производительности Traefik хватает с запасом.

Установка Traefik с Docker Provider

Traefik разворачивается как Docker-контейнер и подключается к Docker socket для автоматического обнаружения сервисов. Все настройки маршрутизации определяются через Docker labels на самих сервисах.

Базовая конфигурация Traefik

Создаём статическую конфигурацию Traefik:

# /opt/traefik/traefik.yml — статическая конфигурация

# Точки входа (entrypoints)
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letsencrypt
    # HTTP/2 и HTTP/3
    http3: {}
  # Метрики (внутренний порт)
  metrics:
    address: ":8082"

# Провайдеры
providers:
  # Docker — автоматическое обнаружение контейнеров
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false  # только контейнеры с label traefik.enable=true
    watch: true
    network: traefik-public
  # File provider — для legacy-сервисов вне Docker
  file:
    directory: "/etc/traefik/dynamic/"
    watch: true

# Let's Encrypt — автоматические TLS-сертификаты
certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@applab.dev
      storage: /etc/traefik/acme.json
      httpChallenge:
        entryPoint: web
      # Для wildcard-сертификатов:
      # dnsChallenge:
      #   provider: cloudflare

# Dashboard
api:
  dashboard: true
  insecure: false  # доступ только через роутер с аутентификацией

# Логирование
log:
  level: INFO
  filePath: "/var/log/traefik/traefik.log"

accessLog:
  filePath: "/var/log/traefik/access.log"
  bufferingSize: 100
  fields:
    headers:
      defaultMode: drop
      names:
        User-Agent: keep
        X-Forwarded-For: keep

# Метрики Prometheus
metrics:
  prometheus:
    entryPoint: metrics
    addEntryPointsLabels: true
    addRoutersLabels: true
    addServicesLabels: true

Docker Compose: Traefik + сервисы

Создаём Docker-сеть и запускаем Traefik:

# Создаём сеть для Traefik
docker network create traefik-public

# /opt/traefik/docker-compose.yml
version: '3.8'

services:
  traefik:
    image: traefik:v3.1
    restart: always
    ports:
      - "80:80"
      - "443:443"
      - "8082:8082"  # метрики (только из внутренней сети)
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./dynamic/:/etc/traefik/dynamic/:ro
      - ./acme.json:/etc/traefik/acme.json
      - traefik-logs:/var/log/traefik
    networks:
      - traefik-public
    labels:
      # Traefik Dashboard — роутер с аутентификацией
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.applab.dev`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
      - "traefik.http.routers.dashboard.middlewares=dashboard-auth"
      - "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$xyz$$hashedpassword"

volumes:
  traefik-logs:

networks:
  traefik-public:
    external: true

Инициализируем файл для сертификатов и запускаем:

# Создаём файл для ACME-сертификатов (Let's Encrypt)
touch /opt/traefik/acme.json
chmod 600 /opt/traefik/acme.json

# Создаём директорию для dynamic config (file provider)
mkdir -p /opt/traefik/dynamic

# Запускаем Traefik
cd /opt/traefik && docker compose up -d

# Проверяем логи
docker logs traefik-traefik-1 --tail 20
# level=info msg="Starting provider aggregator"
# level=info msg="Starting provider *docker.Provider"
# level=info msg="Starting provider *file.Provider"
# level=info msg="Starting TCP entryPoint web 0.0.0.0:80"
# level=info msg="Starting TCP entryPoint websecure 0.0.0.0:443"

Автоматический Service Discovery через Docker Labels

Главное преимущество Traefik — сервисы регистрируют себя сами через Docker labels. Никаких отдельных конфигурационных файлов — всё описано в docker-compose.yml каждого сервиса.

Конфигурация микросервисов с Traefik labels

Переписываем docker-compose.yml приложения с Traefik labels:

# /opt/applab/docker-compose.yml
version: '3.8'

services:
  # API Gateway — api.applab.dev
  api-gateway:
    image: applab/api-gateway:${TAG:-latest}
    restart: always
    networks:
      - traefik-public
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=Host(`api.applab.dev`)"
      - "traefik.http.routers.api.entrypoints=websecure"
      - "traefik.http.routers.api.tls.certresolver=letsencrypt"
      - "traefik.http.services.api.loadbalancer.server.port=3000"
      - "traefik.http.services.api.loadbalancer.healthcheck.path=/health"
      - "traefik.http.services.api.loadbalancer.healthcheck.interval=10s"
      - "traefik.http.routers.api.middlewares=api-ratelimit,security-headers"

  # Auth Service — api.applab.dev/auth/*
  auth-service:
    image: applab/auth-service:${TAG:-latest}
    restart: always
    networks:
      - traefik-public
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.auth.rule=Host(`api.applab.dev`) && PathPrefix(`/auth`)"
      - "traefik.http.routers.auth.entrypoints=websecure"
      - "traefik.http.routers.auth.tls.certresolver=letsencrypt"
      - "traefik.http.services.auth.loadbalancer.server.port=3001"
      - "traefik.http.services.auth.loadbalancer.healthcheck.path=/health"
      - "traefik.http.routers.auth.middlewares=api-ratelimit,security-headers"

  # Catalog — api.applab.dev/catalog/*
  catalog:
    image: applab/catalog:${TAG:-latest}
    restart: always
    networks:
      - traefik-public
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.catalog.rule=Host(`api.applab.dev`) && PathPrefix(`/catalog`)"
      - "traefik.http.routers.catalog.entrypoints=websecure"
      - "traefik.http.routers.catalog.tls.certresolver=letsencrypt"
      - "traefik.http.services.catalog.loadbalancer.server.port=3002"
      - "traefik.http.routers.catalog.middlewares=api-ratelimit,security-headers"

  # Cart — api.applab.dev/cart/*
  cart:
    image: applab/cart:${TAG:-latest}
    restart: always
    networks:
      - traefik-public
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.cart.rule=Host(`api.applab.dev`) && PathPrefix(`/cart`)"
      - "traefik.http.routers.cart.entrypoints=websecure"
      - "traefik.http.services.cart.loadbalancer.server.port=3003"
      - "traefik.http.routers.cart.middlewares=api-ratelimit,security-headers,auth-forward"

  # Payments — api.applab.dev/payments/*
  payments:
    image: applab/payments:${TAG:-latest}
    restart: always
    networks:
      - traefik-public
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.payments.rule=Host(`api.applab.dev`) && PathPrefix(`/payments`)"
      - "traefik.http.routers.payments.entrypoints=websecure"
      - "traefik.http.services.payments.loadbalancer.server.port=3004"
      - "traefik.http.routers.payments.middlewares=payments-ratelimit,security-headers,auth-forward"

  # Notifications — внутренний, без внешнего доступа
  notifications:
    image: applab/notifications:${TAG:-latest}
    restart: always
    networks:
      - internal
    # Нет traefik.enable — сервис не виден снаружи

  # Analytics — api.applab.dev/analytics/*
  analytics:
    image: applab/analytics:${TAG:-latest}
    restart: always
    networks:
      - traefik-public
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.analytics.rule=Host(`api.applab.dev`) && PathPrefix(`/analytics`)"
      - "traefik.http.routers.analytics.entrypoints=websecure"
      - "traefik.http.services.analytics.loadbalancer.server.port=3006"
      - "traefik.http.routers.analytics.middlewares=security-headers,auth-forward"

  # Admin Panel — admin.applab.dev
  admin-panel:
    image: applab/admin-panel:${TAG:-latest}
    restart: always
    networks:
      - traefik-public
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.admin.rule=Host(`admin.applab.dev`)"
      - "traefik.http.routers.admin.entrypoints=websecure"
      - "traefik.http.routers.admin.tls.certresolver=letsencrypt"
      - "traefik.http.services.admin.loadbalancer.server.port=3007"
      - "traefik.http.routers.admin.middlewares=admin-ipwhitelist,security-headers"

networks:
  traefik-public:
    external: true
  internal:
    driver: bridge

Обратите внимание: ни одного IP-адреса или порта в конфигурации Traefik. Traefik сам обнаруживает контейнеры через Docker socket, читает labels и создаёт маршруты. При деплое нового сервиса достаточно добавить labels в его docker-compose — Traefik подхватит автоматически за секунды.

Health Checks и автоматическое исключение

Traefik регулярно проверяет здоровье каждого сервиса и автоматически исключает нездоровые инстансы из балансировки:

# Health check конфигурация (через labels):
# loadbalancer.healthcheck.path=/health — путь для проверки
# loadbalancer.healthcheck.interval=10s — проверка каждые 10 секунд
# loadbalancer.healthcheck.timeout=3s — таймаут запроса

# В каждом микросервисе реализован эндпоинт /health:
# Пример для api-gateway (Node.js / Express):
# app.get('/health', (req, res) => {
#   const checks = {
#     uptime: process.uptime(),
#     memory: process.memoryUsage(),
#     db: await checkDatabaseConnection(),
#     redis: await checkRedisConnection()
#   };
#   const healthy = checks.db && checks.redis;
#   res.status(healthy ? 200 : 503).json(checks);
# });

# Traefik автоматически:
# 1. Опрашивает /health каждые 10 секунд
# 2. Если сервис вернул не 2xx — помечает как unhealthy
# 3. Перестаёт отправлять трафик на unhealthy-инстанс
# 4. Продолжает проверять — если сервис восстановился, возвращает в пул

# Логи Traefik при падении сервиса:
# level=warn msg="Health check failed for service catalog@docker"
# url=http://172.20.0.5:3002/health error="connection refused"

Middlewares: rate-limit, аутентификация, безопасность

Traefik middlewares — это цепочки обработки запросов, которые применяются до передачи трафика сервису. Мы настроили несколько middleware для безопасности и контроля нагрузки.

Rate Limiting и Security Headers

Определяем middleware через dynamic configuration (file provider):

# /opt/traefik/dynamic/middlewares.yml

http:
  middlewares:
    # Rate limiting для API — 100 запросов/сек per IP
    api-ratelimit:
      rateLimit:
        average: 100
        burst: 200
        period: 1s
        sourceCriterion:
          ipStrategy:
            depth: 1  # учитываем X-Forwarded-For

    # Более строгий rate limit для платежей — 10 запросов/сек
    payments-ratelimit:
      rateLimit:
        average: 10
        burst: 20
        period: 1s
        sourceCriterion:
          ipStrategy:
            depth: 1

    # Security Headers
    security-headers:
      headers:
        browserXssFilter: true
        contentTypeNosniff: true
        frameDeny: true
        sslRedirect: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
        customFrameOptionsValue: "SAMEORIGIN"
        customResponseHeaders:
          X-Powered-By: ""
          Server: ""

    # IP whitelist для admin-панели
    admin-ipwhitelist:
      ipWhiteList:
        sourceRange:
          - "185.xx.xx.xx/32"    # офис
          - "10.10.100.0/24"     # VPN
          - "127.0.0.1/32"

    # Forward Auth — проверка токена через auth-сервис
    auth-forward:
      forwardAuth:
        address: "http://auth-service:3001/verify"
        trustForwardHeader: true
        authResponseHeaders:
          - "X-User-Id"
          - "X-User-Role"

    # Compress middleware — gzip для ответов
    compress:
      compress:
        excludedContentTypes:
          - "text/event-stream"

Middleware применяются к роутерам через labels. Например, payments-роутер имеет строгий rate-limit (10 RPS) и обязательную аутентификацию через ForwardAuth, а catalog — стандартный rate-limit (100 RPS) без аутентификации.

ForwardAuth: централизованная аутентификация

Middleware forwardAuth — один из самых мощных инструментов Traefik. Перед каждым запросом к защищённому сервису Traefik делает подзапрос к auth-сервису:

# Как работает ForwardAuth:

# 1. Клиент → Traefik: GET /cart/items (Header: Authorization: Bearer eyJ...)
# 2. Traefik → Auth Service: GET /verify (копирует все headers клиента)
# 3. Auth Service проверяет JWT:
#    - Если OK → 200 + X-User-Id: 12345 + X-User-Role: customer
#    - Если нет → 401 Unauthorized
# 4. Traefik при 200:
#    - Копирует X-User-Id, X-User-Role в запрос к cart-сервису
#    - Передаёт запрос: GET /cart/items (Header: X-User-Id: 12345)
# 5. Cart-сервис получает проверенный User-Id без JWT-валидации

# Auth Service /verify endpoint (упрощённо):
# app.get('/verify', (req, res) => {
#   const token = req.headers.authorization?.replace('Bearer ', '');
#   try {
#     const decoded = jwt.verify(token, JWT_SECRET);
#     res.set('X-User-Id', decoded.userId);
#     res.set('X-User-Role', decoded.role);
#     res.status(200).end();
#   } catch (err) {
#     res.status(401).json({ error: 'Invalid token' });
#   }
# });

# Преимущество: каждый микросервис не реализует JWT-проверку,
# а получает готовый X-User-Id — Single Responsibility

Canary Deployments и Load Balancing

Одна из ключевых просьб «АппЛаб» — возможность выкатывать новые версии сервисов постепенно, направляя сначала 10% трафика на новую версию. С nginx это делалось вручную через upstream weights.

Weighted Round Robin для Canary

Traefik поддерживает weighted load balancing через labels. Вот как реализован canary-деплой для catalog-сервиса:

# docker-compose.canary.yml — canary deployment
version: '3.8'

services:
  # Стабильная версия (90% трафика)
  catalog-stable:
    image: applab/catalog:v2.3.1
    restart: always
    networks:
      - traefik-public
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.catalog.rule=Host(`api.applab.dev`) && PathPrefix(`/catalog`)"
      - "traefik.http.routers.catalog.entrypoints=websecure"
      - "traefik.http.routers.catalog.tls.certresolver=letsencrypt"
      - "traefik.http.services.catalog.loadbalancer.server.port=3002"
      # Weighted routing
      - "traefik.http.services.catalog-weighted.weighted.services.0.name=catalog-stable"
      - "traefik.http.services.catalog-weighted.weighted.services.0.weight=9"
      - "traefik.http.services.catalog-weighted.weighted.services.1.name=catalog-canary"
      - "traefik.http.services.catalog-weighted.weighted.services.1.weight=1"
      - "traefik.http.routers.catalog.service=catalog-weighted"

  # Canary версия (10% трафика)
  catalog-canary:
    image: applab/catalog:v2.4.0-rc1
    restart: always
    networks:
      - traefik-public
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.catalog-canary.loadbalancer.server.port=3002"

networks:
  traefik-public:
    external: true
  internal:
    driver: bridge

Процесс canary-деплоя:

  1. Запускаем canary с весом 10% (weight=1 из 10)
  2. Мониторим error rate и latency через Grafana
  3. Если всё хорошо — меняем weight на 50/50, затем 90/10 в пользу canary
  4. Удаляем stable, canary становится новым stable
# Скрипт автоматизации canary deploy
#!/bin/bash
# deploy-canary.sh  

SERVICE=$1
NEW_VERSION=$2

echo "Starting canary deployment: $SERVICE → $NEW_VERSION"

# Шаг 1: Запускаем canary (10%)
TAG_CANARY=$NEW_VERSION docker compose -f docker-compose.canary.yml up -d
echo "Canary deployed. Waiting 5 minutes for metrics..."
sleep 300

# Шаг 2: Проверяем error rate через Prometheus
ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query?query=rate(traefik_service_requests_total{service=\"${SERVICE}-canary@docker\",code=~\"5..\"}[5m])" | jq '.data.result[0].value[1]')

if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
  echo "ERROR: Canary error rate $ERROR_RATE > 1%. Rolling back!"
  docker compose -f docker-compose.canary.yml down
  exit 1
fi

echo "Canary looks healthy. Promoting to 100%..."
# Шаг 3: Обновляем stable на новую версию
TAG=$NEW_VERSION docker compose up -d --no-deps $SERVICE
docker compose -f docker-compose.canary.yml down
echo "Deployment complete: $SERVICE → $NEW_VERSION"

Sticky Sessions для stateful-сервисов

Cart-сервис хранит состояние сессии, поэтому запросы одного пользователя должны попадать на один инстанс:

# Sticky sessions через cookie (в labels контейнера):
labels:
  - "traefik.http.services.cart.loadbalancer.sticky.cookie=true"
  - "traefik.http.services.cart.loadbalancer.sticky.cookie.name=cart_affinity"
  - "traefik.http.services.cart.loadbalancer.sticky.cookie.secure=true"
  - "traefik.http.services.cart.loadbalancer.sticky.cookie.httpOnly=true"
  - "traefik.http.services.cart.loadbalancer.sticky.cookie.sameSite=strict"

# Traefik устанавливает cookie cart_affinity при первом запросе,
# и все последующие запросы с этим cookie идут на тот же инстанс.
# Если инстанс умирает — cookie пересоздаётся на новый инстанс.

TCP/UDP Routing, File Provider и Dashboard

Traefik умеет маршрутизировать не только HTTP, но и TCP/UDP-трафик. А file provider позволяет подключить legacy-сервисы, работающие вне Docker.

TCP-маршрутизация для PostgreSQL

У «АппЛаб» внешние аналитики подключаются к read-only реплике PostgreSQL. Раньше порт PostgreSQL был проброшен напрямую. Теперь маршрутизация через Traefik с TLS:

# /opt/traefik/dynamic/tcp-routes.yml

tcp:
  routers:
    postgres-readonly:
      entryPoints:
        - "postgres"  # Добавляем в traefik.yml: entryPoints.postgres.address: ":5432"
      rule: "HostSNI(`db.applab.dev`)"
      service: postgres-ro
      tls:
        certResolver: letsencrypt

  services:
    postgres-ro:
      loadBalancer:
        servers:
          - address: "10.10.1.20:5432"  # PostgreSQL read-replica

# Клиент подключается:
# psql "host=db.applab.dev port=5432 sslmode=require dbname=analytics user=analyst"

File Provider для legacy-сервисов

Один из сервисов «АппЛаб» — старая admin-панель на PHP — работает на bare-metal сервере вне Docker. File provider позволяет включить её в маршрутизацию Traefik:

# /opt/traefik/dynamic/legacy.yml

http:
  routers:
    legacy-admin:
      rule: "Host(`legacy.applab.dev`)"
      entryPoints:
        - websecure
      service: legacy-admin-svc
      tls:
        certResolver: letsencrypt
      middlewares:
        - admin-ipwhitelist
        - security-headers

  services:
    legacy-admin-svc:
      loadBalancer:
        servers:
          - url: "http://10.10.1.30:8080"
        healthCheck:
          path: "/health.php"
          interval: "15s"
          timeout: "5s"

File provider следит за изменениями в файлах директории /opt/traefik/dynamic/ и автоматически применяет новые конфигурации без перезагрузки Traefik. Это удобно для legacy-сервисов, которые нельзя быстро мигрировать в Docker.

Traefik Dashboard и Prometheus-метрики

Dashboard доступен на traefik.applab.dev с basic-аутентификацией и IP-фильтрацией. Он показывает все роутеры, сервисы и middleware в реальном времени.

Для глубокой аналитики Traefik экспортирует метрики в Prometheus:

# Prometheus scrape config:
scrape_configs:
  - job_name: 'traefik'
    static_configs:
      - targets: ['traefik:8082']

# Ключевые метрики:
# traefik_entrypoint_requests_total — запросы по entrypoint
# traefik_router_requests_total — запросы по роутеру
# traefik_service_requests_total — запросы по сервису
# traefik_service_request_duration_seconds — latency
# traefik_service_open_connections — открытые соединения
# traefik_tls_certs_not_after — срок истечения сертификатов

# Grafana Dashboard — полезные панели:

# Panel: Request Rate per Service (Time Series)
# Query: sum(rate(traefik_service_requests_total[5m])) by (service)

# Panel: Error Rate per Service (Time Series)
# Query: sum(rate(traefik_service_requests_total{code=~"5.."}[5m])) by (service)
#        / sum(rate(traefik_service_requests_total[5m])) by (service)

# Panel: P99 Latency per Service (Time Series)
# Query: histogram_quantile(0.99,
#          sum(rate(traefik_service_request_duration_seconds_bucket[5m])) by (le, service))

# Panel: TLS Certificate Expiry (Stat)
# Query: (traefik_tls_certs_not_after - time()) / 86400
# Alert: < 7 days → Warning, < 3 days → Critical

# Panel: Active Connections (Gauge)
# Query: sum(traefik_service_open_connections) by (service)

Результаты внедрения и автоматизация CI/CD

Миграция с nginx на Traefik заняла 3 дня: 1 день на настройку Traefik и тестирование, 1 день на миграцию сервисов (переключение labels), 1 день на настройку мониторинга и документирование.

Интеграция с CI/CD pipeline

Теперь деплой в GitLab CI полностью автоматизирован — никаких ручных правок конфигов:

# .gitlab-ci.yml — деплой микросервиса
stages:
  - build
  - deploy

build:
  stage: build
  script:
    - docker build -t registry.applab.dev/$CI_PROJECT_NAME:$CI_COMMIT_SHA .
    - docker push registry.applab.dev/$CI_PROJECT_NAME:$CI_COMMIT_SHA

deploy-production:
  stage: deploy
  environment: production
  script:
    # Просто обновляем тег образа — Traefik подхватывает автоматически!
    - ssh deploy@prod "cd /opt/applab && TAG=$CI_COMMIT_SHA docker compose up -d --no-deps $CI_PROJECT_NAME"
  only:
    - main

deploy-canary:
  stage: deploy
  environment: canary
  script:
    - ssh deploy@prod "cd /opt/applab && TAG_CANARY=$CI_COMMIT_SHA docker compose -f docker-compose.canary.yml up -d"
  when: manual
  only:
    - main

Полный цикл: разработчик пушит код → GitLab CI собирает образ → деплоит новый контейнер → Traefik автоматически обнаруживает и начинает отправлять трафик. Ноль ручных правок конфигов.

Результаты в цифрах

Через месяц после миграции мы собрали метрики:

МетрикаДо (nginx)После (Traefik)
Время деплоя нового сервиса15-30 мин (правка nginx.conf)0 мин (Docker labels)
Даунтаймы из-за конфигурации2 за месяц0 за 3 месяца
Ручные правки конфигов/мес140
Управление TLS-сертификатамиCertbot cron (ненадёжно)ACME автоматически
Health checksНетКаждые 10 сек на каждый сервис
Canary-деплоиВручную (upstream weights)Автоматически (weighted labels)
Метрики по сервисамОбщий nginx access.logPer-service Prometheus метрики
Строк конфигурации nginx2470 (заменены на Docker labels)

Главный результат: DevOps-инженер «АппЛаб» перестал быть «человеком-nginx-конфигом» и занялся автоматизацией тестирования и мониторинга. Команда за месяц выполнила 38 деплоев — и ни один не потребовал ручного вмешательства в конфигурацию reverse proxy.

Дополнительный бонус — Let's Encrypt работает безупречно. Traefik автоматически получает и обновляет сертификаты для каждого домена. Панический пятничный инцидент с истёкшим сертификатом больше невозможен.

Часто задаваемые вопросы

В синтетических бенчмарках nginx действительно обрабатывает больше запросов в секунду: ~50 000 RPS у nginx vs ~30 000 RPS у Traefik на аналогичном оборудовании. Однако для большинства реальных приложений это не имеет значения — bottleneck находится в микросервисах, а не в reverse proxy. В нашем кейсе «АппЛаб» с 5 000 RPS Traefik использует менее 5% CPU. Разница в производительности заметна только при десятках тысяч RPS на одном сервере. При этом Traefik экономит часы работы инженеров за счёт автоматического service discovery.

Если контейнер Traefik остановится, все HTTP(S)-запросы к сервисам перестанут обрабатываться — это single point of failure, как и nginx. Для решения этой проблемы используют два подхода: (1) Docker restart policy restart: always — Docker автоматически перезапустит контейнер за секунды; (2) Кластерный режим с двумя инстансами Traefik за DNS round-robin или hardware load balancer. В нашем кейсе мы использовали restart: always плюс мониторинг через Prometheus — за 3 месяца Traefik ни разу не падал.

Да. Traefik имеет нативный Kubernetes provider и может работать как Ingress Controller. Он поддерживает стандартные Kubernetes Ingress ресурсы, а также собственные CRD (Custom Resource Definitions) — IngressRoute, Middleware, TLSOption. Если «АппЛаб» в будущем мигрирует с Docker Compose на Kubernetes, Traefik мигрирует вместе с ними с минимальными изменениями — концепции роутеров, middlewares и сервисов идентичны.

Да, это распространённый паттерн при поэтапной миграции. Traefik может стоять перед nginx (nginx как backend-сервис в Traefik) или nginx может стоять перед Traefik (nginx как edge-прокси для TLS termination, Traefik для service discovery). В нашем кейсе мы полностью заменили nginx, но для организаций с большим legacy вполне допустимо оставить nginx для статики и legacy-приложений, а Traefik использовать для Docker-микросервисов. File provider в Traefik позволяет маршрутизировать трафик на любые backend, включая nginx.

Traefik обновляет сертификаты Let's Encrypt автоматически в фоне, за 30 дней до истечения. Новый сертификат получается через ACME-протокол (HTTP challenge или DNS challenge), сохраняется в файл acme.json и подключается к TLS-конфигурации без перезагрузки — hot reload. Весь процесс проходит без даунтайма и без вмешательства администратора. В отличие от certbot + nginx, где нужен cron, reload и обработка ошибок, Traefik делает всё самостоятельно.

Нужна помощь с настройкой?

Специалисты АйТи Фреш помогут с внедрением и настройкой — 15+ лет опыта, обслуживание от 15 000 ₽/мес

📞 Связаться с нами
#Traefik reverse proxy настройка#Traefik Docker provider#Traefik Let's Encrypt ACME#Traefik middlewares rate-limit#Traefik canary deployment#Traefik Prometheus метрики#Traefik load balancing#Traefik dashboard настройка