Обо мне Блог Контакты

Обратный прокси Traefik: динамическая конфигурация

В предыдущей статье мы рассмотрели статическую конфигурацию Traefik. Теперь перейдём к динамической.

Динамическая конфигурация — это набор правил маршрутизации запросов к конечным сервисам. Эти правила могут быть как общими для нескольких сервисов, так и специфичными для конкретного маршрута.

Технически всё можно описать в одном файле, однако при росте количества сервисов такая конфигурация быстро становится громоздкой и трудночитаемой. Поэтому я разделяю её на несколько файлов:

  • один общий файл с базовыми настройками;
  • отдельные файлы для каждого сервиса.

Например:

  • config.yaml — содержит общие middleware, TLS-настройки и базовые маршруты;
  • immich.yaml — описывает маршрутизацию и правила только для сервиса Immich.

Общая динамическая конфигурация

Рассмотрим файл config.yaml:

http:
  routers:
    catch-all:
      rule: 'HostRegexp(`.+`) || PathPrefix(`/`)'
      service: noop@internal
      middlewares:
        - block-all
      tls: {}
      priority: 1

  middlewares:
    headers:
      headers:
        sslRedirect: true
        contentTypeNosniff: true
        stsPreload: true
        customRequestHeaders:
          X-Forwarded-Proto: https
        browserXssFilter: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsSeconds: 15552000
        referrerPolicy: strict-origin-when-cross-origin
        frameDeny: true
        customFrameOptionsValue: SAMEORIGIN

    block-all:
      ipAllowList:
        sourceRange:
          - 127.0.0.1/32

    local:
      ipAllowList:
        sourceRange:
          - 192.168.1.0/24

    rate-limit:
      rateLimit:
        average: 50
        burst: 20
        period: 1s

    rate-limit-strict:
      rateLimit:
        average: 10
        burst: 5
        period: 1s

    internal-chain:
      chain:
        middlewares:
          - local
          - headers

    external-chain:
      chain:
        middlewares:
          - rate-limit
          - headers

    external-chain-strict:
      chain:
        middlewares:
          - rate-limit-strict
          - headers

    external-chain-no-rate-limit:
      chain:
        middlewares:
          - headers

tls:
  options:
    default:
      minVersion: VersionTLS12
      curvePreferences:
        - X25519
        - CurveP256
        - CurveP384
        - CurveP521
      sniStrict: false
      cipherSuites:
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

  stores:
    default:
      defaultCertificate:
        certFile: /etc/traefik/certs/default.crt
        keyFile: /etc/traefik/certs/default.key

Рассмотрим немного подробней эту конфигурацию.

Router catch-all

В начале определяется маршрут catch-all.

Правило rule: HostRegexp(.+) || PathPrefix(/) означает, что этот маршрут сработает для любого входящего запроса. Однако благодаря минимальному приоритету (priority: 1) он активируется только в том случае, если ни один другой маршрут не подошёл. Фактически это маршрут по умолчанию. К нему применяется middleware block-all, который блокирует доступ и возвращает ошибку 403 Forbidden. В качестве сервиса используется noop@internal — пустой встроенный сервис Traefik. До него запрос не дойдёт, так как middleware block-all заблокирует его раньше. В общем это маршрут заглушка, чтобы блокировать все запросы, для которых нет маршрута.


Middlewares

Middleware — это компоненты, которые обрабатывают запрос на пути между точкой входа и конечным сервисом. Каждый middleware может модифицировать запрос, добавить к нему заголовки, ограничить доступ по IP-адресу, применить rate limiting или выполнить другие действия до того, как запрос будет передан на бэкенд. К одному маршруту можно подключить несколько middleware — они будут применяться последовательно в порядке их указания.

1. headers

В целом это набор базовых HTTP-заголовков, которые Traefik добавляет к ответам приложения. Они усиливают защиту браузера от распространённых атак (XSS, clickjacking, downgrade-атак и т.д.).

Этот middleware применяется практически ко всем публичным сервисам.

2. block-all

Блокирует все входящие запросы и возвращает ошибку 403 Forbidden. Используется только в маршруте catch-all. Механизм работы основан на ipAllowList: в список разрешённых адресов включён только 127.0.0.1/32 — loopback-адрес самого LXC-контейнера, в котором работает Traefik. Реальные клиентские запросы никогда не придут с этого адреса, поэтому доступ будет заблокирован для всех.

3. local

Работает по тому же принципу, что и block-all, но в качестве разрешённого диапазона указана подсеть 192.168.1.0/24. Это означает, что доступ получат только устройства из локальной сети. Разумеется вы должны указать свою локальную сеть.

Используется для сервисов, которые не должны быть доступны из интернета (например, PGAdmin). Запросы извне получат ошибку 403 Forbidden.

4. rate-limit

Ограничение частоты запросов с одного IP-адреса:

  • average: 50 — среднее число запросов;
  • burst: 20 — допустимый кратковременный всплеск;
  • period: 1s — интервал расчёта.

Подходит для большинства обычных сервисов.

4. rate-limit-strict

То же самое, что и предыдущий, но с более жёстким лимитом. Используется для чувствительных сервисов (например, Vaultwarden), чтобы снизить риск brute-force атак.


Следующие middleware представляют собой цепочки (chains). Цепочки позволяют группировать несколько middleware в одну именованную сущность и подключать её к сервису одной строкой.

5. internal-chain

Применяется к внутренним сервисам, доступным только из локальной сети. Одновременно ограничивает доступ по IP (разрешает только локальный диапазон адресов) и добавляет защитные заголовки.

6. external-chain

Применяется к публичным сервисам. Включает защитные заголовки и ограничение частоты запросов.

7. external-chain-strict

То же самое, что и external-chain, но с более строгим ограничением частоты запросов.

8. external-chain-no-rate-limit

То же самое, что и external-chain, но без ограничения частоты запросов. Применяется для сервисов, где rate limiting может мешать нормальной работе (например, при потоковой передаче данных).


TLS

В завершение указаны глобальные настройки TLS. Подробно разбирать их в рамках этой статьи не буду — при необходимости можно обратиться к официальной документации.

Отмечу лишь раздел stores, в котором задан SSL-сертификат по умолчанию. На практике он не играет ключевой роли, поскольку для рабочих доменов через протокол ACME выпускаются wildcard-сертификаты (*.example.com). Сертификат по умолчанию используется для всех прочих запросов, не попадающих под wildcard, — скорее как запасной вариант. Этот раздел не является обязательным.

Таким образом, мы описали общую динамическую конфигурацию обратного прокси Traefik.


Динамическая конфигурация отдельного сервиса

Теперь разберём пример конфигурации для конкретного сервиса — например Immich.

http:
  routers:
    immich:
      rule: 'Host(`immich.kvasok.xyz`)'
      service: immich
      middlewares:
        - external-chain-no-rate-limit

  services:
    immich:
      loadBalancer:
        servers:
          - url: http://192.168.1.20:2283

Выглядит довольно просто, не правда ли?

Разберём по порядку. Конфигурация состоит из двух основных частей: routers и services.

Router

В секции routers описываем маршрут immich. Точку входа мы определили в статической конфигурации по умолчанию — websecure, поэтому тут явно ее можно не укзывать. Правило Host(immich.kvasok.xyz) означает, что маршрут будет применяться только к запросам с доменным именем immich.kvasok.xyz. Далее указываем сервис immich, к которому Traefik будет проксировать запросы (его определяем чуть ниже).

В качестве middleware выбран external-chain-no-rate-limit — цепочка без ограничения частоты запросов. Это сделано намеренно: при прокрутке галереи в Immich клиент генерирует большое количество запросов для подгрузки превью, и стандартный rate limit будет быстро исчерпан, что приведёт к ошибкам загрузки.

Также, мы явно не указываем тут certResolver: timewebcloud — тот самый резолвер ACME, который мы настроили в статической конфигурации, потому что мы указали его по умолчанию для нашей точки входа — websecure. Так что Traefik автоматически запросит и будет обновлять SSL-сертификат для домена immich.kvasok.xyz через этого провайдера.

Service

В секции services описываем бэкенд, на который Traefik будет проксировать запросы. Для сервиса immich используется loadBalancer — стандартный способ определения upstream-серверов в Traefik.

В массиве servers перечисляются адреса бэкенд-серверов. В нашем случае сервер один: http://192.168.1.20:2283 — это внутренний IP-адрес хоста, на котором запущен Immich, и порт, на котором он принимает HTTP-запросы. Если бы требовалась балансировка нагрузки между несколькими экземплярами, здесь можно было бы указать дополнительные серверы.

Каждый новый сервис описывается аналогичным образом. Некоторые из них требуют дополнительных настроек, но в большинстве случаев достаточно конфигурации, аналогичной приведённому примеру с Immich. Главное — подобрать подходящий middleware: ограничить доступ там, где он не нужен, или, наоборот, ослабить ограничения для сервисов, которым это необходимо.

Если какому-то сервису нужно определить какой-то специфичный для этого сервиса middleware, то его можно сделать прям в этом файле. И мы будем точно понимать, что этот middleware нужен только в этом сервисе.


На этом мы закончили базово настраивать наш Traefik. Теперь мы можем пользоваться нашими сервисами как дома, так и удаленно через интернет, используя доменное имя, как будто бы используем сервисы Google или Yandex. Но, так как теперь все наши сервисы находятся непосредственно у нас дома на нашем сервере, то заботиться о безопасности всего этого добра нужно нам самим. Об этом в следующих статьях.