metalnikovg.ru
MetalnikovG.ru

Traefik - обратный прокси для Docker контейнеров

Banner.png
Опубликовано
//
10 мин. чтения

Traefik - это обратный прокси сервер. Его основными преимуществами является наличие собственной панели мониторинга, а так же поддержка Docker “из коробки”. Если вам нужно опубликовать ваши докеризированные сервисы во внешнюю сеть, то Traefik будет идеальным для этого решением. Он будет осуществлять ретрансляцию запросов из внешней сети, что позволит скрыть структуру внутренней.

traefik-architecture

Рассмотрим два способа конфигурирования: при помощи меток и при помощи динамических файлов конфигурации.

1. Конфигурирование Traefik с помощью меток

Запускать Traefik будем в докере, поэтому он должен быть заранее установлен

Обновляем пакеты

apt update && apt upgrade -y

Устанавливаем docker

curl -sSL https://get.docker.com | sh
sudo groupadd docker
sudo usermod -aG docker ${USER}
sudo apt install docker-compose
sudo systemctl enable docker
sudo reboot

Для запуска будем использовать официальный Docker образ, но прежде чем запустить наш контейнер, необходимо создать конфигурационный файл, создать docker-сеть, выпустить API-токен Cloudflare для выпуска и обновления сертификатов а так же настроить парольный доступ к панели мониторинга.

И так создаем папку traefik и переходим в нее:

mkdir traefik && cd traefik

Создаем файл docker-compose:

nano docker-compose.yml

со следующим содержимым:

docker-compose.yaml
version: "3.8"
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      proxy: null
    ports:
      - 80:80
      - 443:443
    environment:
      - CF_API_EMAIL=youremail@gmail.com
      - CF_DNS_API_TOKEN=cf_api_token
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
      - ./acme.json:/acme.json
      - ./config.yml:/config.yml:ro
      - ./logs:/var/log/traefik
    labels:
      - traefik.enable=true
      - traefik.http.routers.traefik.entrypoints=http
      - traefik.http.routers.traefik.rule=Host(`traefik.domain.ru`)
      - traefik.http.middlewares.traefik-auth.basicauth.users=user:password
      - traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https
      - traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https
      - traefik.http.routers.traefik.middlewares=traefik-https-redirect
      - traefik.http.routers.traefik-secure.entrypoints=https
      - traefik.http.routers.traefik-secure.rule=Host(`traefik.domain.ru`)
      - traefik.http.routers.traefik-secure.middlewares=traefik-auth
      - traefik.http.routers.traefik-secure.tls=true
      - traefik.http.routers.traefik-secure.tls.certresolver=cloudflare
      - traefik.http.routers.traefik-secure.tls.domains[0].main=domain.ru
      - traefik.http.routers.traefik-secure.tls.domains[0].sans=*.domain.ru
      - traefik.http.routers.traefik-secure.service=api@internal
networks:
  proxy:
    name: proxy
    external: true

1.1 Получения API-токена Cloudflare

Приступаем к выпуске API-токена Cloudflare, как это сделать, можно прочитать в заметке по развертыванию Nginx Proxy Manager. Для этого переходим в наш профиль, в раздел API нажимаем Create Token -> Edit zone DNS

В Zone Resources можете выбрать нужную DNS-зону если у вас из несколько. А в Client IP Filtering по желанию можете указать ваш белый статический IP с оператором ls in, делается это на случай того, если ваш токен будет скомпрометирован, то злоумышленник не смог им воспользоваться.

Копируем полученный токен и сохраняем его в надежном месте, т.к. посмотреть вы его больше не сможете. Если потеряете его то придется перевыпускать.

Возвращаемся в наш файл docker compose и вставляем полученный токен в переменную CF_DNS_API_TOKEN, а в CF_API_EMAIL вписываем ваш email от аккаунта Cloudflare.

1.2 Создаем пароль для панели мониторинга

Для создания хэшированного пароля для basicauth авторизации, нам понадобится установить утилиту apache2-utils

sudo apt install apache2-utils

Затем выполняем следующую команду предварительно заменив user на любое желаемое имя пользователя

echo $(htpasswd -nB user | sed -e s/\\$/\\$\\$/g

Вам нужно будет ввести и подтвердить пароль, после чего вы получите ваш хешированный пароль:

Копируем полностью полученную строку и вставляем в метку traefik.http.middlewares.traefik-auth.basicauth.users

1.3 Создаем docker сеть

Создаем docker сеть proxy:

docker network create proxy

1.4 Создаем файлы конфигурации

Создаем файл статической конфигурации:

nano traefik.yml

со следующим содержимым:

traefik.yml
api:
  dashboard: true
  debug: true
entryPoints:
  http:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: https
          scheme: https
  https:
    address: ":443"
serversTransport:
  insecureSkipVerify: true
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    filename: /config.yml
certificatesResolvers:
  cloudflare:
    acme:
      email: youremail@gmail.com #add your email
      storage: acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"

log:
  level: "INFO"
  filePath: "/var/log/traefik/traefik.log"
accessLog:
  filePath: "/var/log/traefik/access.log"

Создаем файл динамической конфигурации, но пока оставим его пустым:

nano config.yml

И создаем пустой файл acme.json в нем будут храниться сведения о сертификатах

nano acme.json

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

ERR The ACME resolve is skipped from the resolvers list error="unable to get ACME account: permissions 644 for acme.json are too open, please use 600" resolver=cloudflare

Проверяем и выставляем ему права:

ll
sudo chmod 600 acme.json
docker comopose up -d

После запуска, переходим по ранее указанному адресу traefik.example.com и попадаем в дашборд traefik'a:

1.5 Подключение сервисов

Чтобы опубликовать ваше docker-приложение во внешнюю сеть, необходимо добавить метки в его docker-compose файл и подключить к docker-сети traefik'а. Рассмотрим в качестве примера Homarr:

docker-compose.yaml
version: "3"
services:
  homarr:
    container_name: homarr
    image: ghcr.io/ajnart/homarr:latest
    restart: unless-stopped
    volumes:
      # - /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration
      - ./homarr/configs:/app/data/configs
      - ./homarr/icons:/app/public/icons
      - ./homarr/data:/data
    ports:
      - 7575:7575
    networks:
      proxy: null
    labels:
      - traefik.enable=true
      - traefik.http.routers.homarr.entrypoints=http
      - traefik.http.routers.homarr.rule=Host(`home.domain.ru`)
      - traefik.http.middlewares.homarr-https-redirect.redirectscheme.scheme=https
      - traefik.http.routers.homarr-secure.entrypoints=https
      - traefik.http.routers.homarr-secure.rule=Host(`home.domain.ru`)
      - traefik.http.routers.homarr-secure.tls=true
      - traefik.http.routers.homarr-secure.service=homarr
      - traefik.http.services.homarr.loadbalancer.server.port=7575
      - traefik.docker.network=proxy
networks:
  proxy:
    external: true

И в качестве еще одного примера Vaultwarden:

docker-compose.yaml
version: "3"
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - SIGNUPS_ALLOWED=false
    volumes:
      - ./vw-data:/data
    networks:
      proxy: null
    labels:
      - traefik.enable=true
      - traefik.http.routers.vaultwarden.entrypoints=http
      - traefik.http.routers.vaultwarden.rule=Host(`vaultwarden.domain.ru`)
      - traefik.http.middlewares.vaultwarden-https-redirect.redirectscheme.scheme=https
      - traefik.http.routers.vaultwarden.middlewares=vaultwarden-https-redirect
      - traefik.http.routers.vaultwarden-secure.entrypoints=https
      - traefik.http.routers.vaultwarden-secure.rule=Host(`vaultwarden.domain.ru`)
      - traefik.http.routers.vaultwarden-secure.tls=true
      - traefik.http.routers.vaultwarden-secure.service=vaultwarden
      - traefik.http.services.vaultwarden.loadbalancer.server.port=80
      - traefik.docker.network=proxy
    security_opt:
      - no-new-privileges:true
networks:
  proxy:
    external: true

Если сервис уже запущен, то измените файл docker-compose и перезапустите контейнер, после этого приложение будет доступно по заданному адресу.

2. Конфигурация Traefik с помощью файлов динамической конфигураций

Этот подход является более простым, надежным и понятным. Вам практически не нужно будет редактировать каждый docker-compose файл. Вместо этого вы будете создавать динамический файл конфигурации для каждого сервиса и все его изменения будут автоматически подхватываться Traefik.

Создаем файл docker-compose без меток и в volume вместо конкретного файла config.yml монтируем директорию config в которой будут храниться файлы динамической конфигурации:

docker-compose.yaml
version: "3.8"
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy
    ports:
      - 80:80
      - 443:443
    environment:
      - CF_API_EMAIL=youremail@gmail.com
      - CF_DNS_API_TOKEN=cf_api_token
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
      - ./acme.json:/acme.json
      - ./config:/config:ro
      - ./logs:/var/log/traefik
networks:
  proxy:
    name: proxy
    external: true

Создаем файл статической конфигурации traefik.yml и добавляем директорию /config в провайдеры:

traefik.yml
api:
  dashboard: true
  debug: true
entryPoints:
  http:
    address: ":80"
    http:
      middlewares:
      redirections:
        entryPoint:
          to: https
          scheme: https
  https:
    address: ":443"
    http:
      middlewares:

serversTransport:
  insecureSkipVerify: true
providers:
  file:
    directory: /config
    watch: true
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
certificatesResolvers:
  cloudflare:
    acme:
      email: youremail@gmail.com #add your email
      storage: acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"

log:
  level: "INFO"
  filePath: "/var/log/traefik/traefik.log"
accessLog:
  filePath: "/var/log/traefik/access.log"

Создаем папку config:

mkdir config

Создаем пустой файл acme.json в нем будут храниться сведения о сертификатах:

nano acme.json

Выставляем нужные права:

sudo chmod 600 acme.json

2.1 Создание файлов динамической конфигурации

В директории /config будут размещаться файлы динамической конфигурации в которую вносятся все настройки проксирования.

TIP

Для удобной навигации в конфигурации применяются следующие правила именования:

  • <name>.yml - служебный файл содержащий настройки промежуточного слоя middlewares
  • ext_<name>.yml - конфигурация приложения которое размещается в общем доступе из интернета. В нем отсутствует настройка ip-allow-list
  • int_<name>.yml - обычное приложение для внутренней сети

Создаем файл ip-allow-list он будет служить для ограничения доступа к сервисам во внутренней сети, в нем необходимо указать адрес локальной подсети или подсетей для которых сервис будет доступен:

ip-allow-list.yml
http:
  middlewares:
    ip-allow-list:
      ipAllowList:
        sourceRange:
          - "192.168.0.0/24"
          - "192.168.1.0/24"
          - "192.168.2.0/24"
          - "192.168.4.0/24"

Т.к. мы убрали метки с контейнера самого Traefik создадим для него динамическую конфигурацию с Basic Auth авторизацией. Генерируем пароль на сайте либо с помощью следующей команды:

htpasswd -nbm user password

Полученный ответ копируем в последнюю сторку нашего конфиг файла:

ext_traefik.yml
http:
  routers:
    traefik:
      rule: "Host(`traefik.domain.ru`)"
      entrypoints:
        - http
      middlewares:
        - traefik-auth
      service: api@internal

    traefik-secure:
      rule: "Host(`traefik.domain.ru`)"
      entrypoints:
        - https
      middlewares:
        - traefik-auth
      tls: true
      service: api@internal

  middlewares:
    traefik-https-redirect:
      redirectScheme:
        scheme: https
    traefik-auth:
      basicauth:
        removeHeader: true
        users:
          - "admin:$apr1$qe69dnj0$eaQ1.qGb/HschwCrLtx2N/"

Шаблон динамической конфигурации будет выглядеть следующим образом:

template
http:
  routers:
    <name>:
      rule: "Host(`<name>.<domain>.ru`)"
      entrypoints:
        - http
      middlewares:
        - <name>-https-redirect
        - authentik #if use authentik
        - ip-allow-list #dont use for external service
      service: <name>

    <name>-secure:
      rule: "Host(`<name>.<domain>.ru`)"
      entrypoints:
        - https
      middlewares:
        - authentik #if use authentik
        - ip-allow-list #dont use for external service
      tls: true
      service: <name>

  middlewares:
    <name>-https-redirect:
      redirectScheme:
        scheme: https

  services:
    <name>:
      loadBalancer:
        servers:
          - url: "http://192.168.78.110:1234"

Этот шаблон можно будет использовать практически для всех внутренних и внешних сервисов, кроме например таких как Nextcloud AIO.

2.2 Конфигурация для локального сервиса

Зачем настраивать обратный прокси для локального сервиса? Ответ очень простой, если вы не хотите каждый раз вводить IP адрес и порт чтобы получить к нему доступ. а хотите просто в адресной строке набрать adguard.domain.ru и попасть в свой локальный Adguard Home, то вам это необходимо. Сделать это просто если вы используете Cloudflare для управления вашей DNS зоной, то просто создайте A запись для субдомена adguard c указанием IP адреса хоста на котором развернут Traefik без проксирования. При использовании другого DNS провайдера вы можете использовать функцию Перезапись DNS-запросов в том же Adguard Home.

Создадим файл конфигурации например для нашего локального DNS сервиса Adguard Home, админ панель которого доступна на порту 5000

http:
  routers:
    adguard:
      rule: "Host(`adguard.domain.ru`)"
      entrypoints:
        - http
      middlewares:
        - adguard-https-redirect
        - ip-allow-list
      service: adguard

    adguard-secure:
      rule: "Host(`adguard.domain.ru`)"
      entrypoints:
        - https
      middlewares:
        - ip-allow-list
      tls: true
      service: adguard

  middlewares:
    adguard-https-redirect:
      redirectScheme:
        scheme: https

  services:
    adguard:
      loadBalancer:
        servers:
          - url: "http://192.168.78.110:5000"

Если ваши docker-сервисы развернуты на одном хосте с Traefik вы можете подключить их к его docker-сети:

    networks:
      proxy: null
networks:
  proxy:
    external: true

После этого вы сможете обращаться к контейнеру по его имени, например:

services:
  adguard:
    loadBalancer:
      servers:
        - url: 'http://adguard:5000'

2.2 Конфигурация для внешнего сервиса

Шаблон для внешнего сервиса будет такой же как и для внутреннего, но в нем не будет использоваться промежуточный слой ip-allow-list.

3. Балансировка нагрузки

Одной из ключевых возможностей Traefik, является осуществление балансировки нагрузки между экземплярами одного и того же сервиса. Чтобы продемонстрировать данный функционал, создадим тестовый nginx-контейнер, положим в папку src простой файл index.html

docker-compose.yaml
version: "3"

services:
    test:
        image: nginx
        ports:
            - 80
        volumes:
            - ./src:/usr/share/nginx/html
        restart: unless-stopped
        networks:
            proxy: null
        labels:
            - traefik.enable=true
            - traefik.http.routers.test.entrypoints=http
            - traefik.http.routers.test.rule=Host(`test.domain.ru`)
            - traefik.http.middlewares.test-https-redirect.redirectscheme.scheme=https
            - traefik.http.routers.test.middlewares=test-https-redirect
            - traefik.http.routers.test-secure.entrypoints=https
            - traefik.http.routers.test-secure.rule=Host(`test.domain.ru`)
            - traefik.http.routers.test-secure.tls=true
            - traefik.http.routers.test-secure.service=test
            - traefik.http.services.test.loadbalancer.server.port=80
            - traefik.docker.network=proxy
        security_opt:
            - no-new-privileges:true
networks:
  proxy:
    external: true

Запускаем контейнер:

docker compose up -d

Масштабируем запущенный контейнер:

docker compose up --scale test=2 -d

После этого переходим в дашборд Traefik, в раздел HTTP Services и убеждаемся, что балансировщик видит оба экземпляра тестового сервиса. Как видите в разделе Servers их два:

Если же посмотреть логи docker-compose, то можно увидеть, что Traefik балансирует входящие запросы на оба экземпляра:

141.101.105.14 - - [16/Oct/2024:20:52:36 +0000] "GET / HTTP/2.0" 304 0 "-" "-" 76149 "test-secure@docker" "http://192.168.32.9:80" 3ms
141.101.105.14 - - [16/Oct/2024:20:52:37 +0000] "GET / HTTP/2.0" 304 0 "-" "-" 76150 "test-secure@docker" "http://192.168.32.4:80" 3ms

WARNING

К сожалению с динамической конфигурацией балансировка работает не корректно. При остановке одного из контейнеров Traefik не видит, что один из контейнеров не запущен и продолжает балансировку. В итоге при использовании сервиса мы периодически будем получать ошибку 502.

4. Настройка ротации логов Trefik

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

sudo nano /etc/logrotate.d/traefik
/home/user/traefik/logs/*.log {
  size 10M
  rotate 5
  missingok
  notifempty
  postrotate
    docker kill --signal="USR1" traefik
  endscript
}

Где

  • /home/user/docker/traefik/logs - путь к файлам логов
  • traefik - имя контейнера

Запустить:

sudo logrotate /etc/logrotate.conf --debug
sudo logrotate /etc/logrotate.conf

Заключение

Мы развернули Traefik в качестве нашего обратного-прокси сервера а так же попробовали два способа его использования с метками и динамической конфигурацией. Подключили Cloudflare DNS Challenge для автоматического выпуска и обновления сертификатов. Протестировали работу балансировщика нагрузки.