Обратный прокси 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. Но, так как теперь все наши сервисы находятся непосредственно у нас дома на нашем сервере, то заботиться о безопасности всего этого добра нужно нам самим. Об этом в следующих статьях.