Содержание

Docker Compose + Traefik: L7-маршрутизация для множества проектов

Цель данной статьи: демонстрация запуска нескольких инстансов одного проекта в docker compose, доступ к которым будет осуществляться через Traefik (прокси). Приведу пример, зачем всё это нужно и почему тут Traefik, а не Nginx.

Есть задача запускать множество окружений-песочниц, на которых QA-инженеры будут тестировать код. Песочницы должны динамически создаваться и удаляться при отрабатывании пайплайна в CI-системе (например Gitlab). Стэк – Nginx + httpd. Как правильно организовать данную схему?

Разумеется, приложение распилено на микросервисы, и можно написать плейбук или скрипты для запуска приложения в docker-compose и с нужным количеством реплик. Но как проксировать трафик извне до поднятых приложений?

Основная проблема заключается в следующем:

Как будет реализовано: каждый проект будет запускаться в своём отдельном сетевом namespace, т.е. через docker compose up -d (будет создана своя отдельная сеть под проект). Nginx в каждом проекте будет запущен на 80 порту, никаких конфликтов по портам на хосте не возникает. Остается открытым вопрос доступа к приложению. Здесь-то в игру и вступает Traefik, который решит описанную выше проблему.

Traefik – относительно новый продукт, который является L7-балансировщиком. Он написан для применения как раз в схемах с использованием микросервисов. А потому может использоваться как Ingress Controller в Kubernetes или же как прокси для standalone докер-контейнеров, что как раз и необходимо. Traefik запускается также в контейнере, а взаимодействие с другими контейнерами осуществляется через API-докера (с указанием пути до unix-сокета) и labels.

Как будет работать вся схема:

Схематично вышеописанное представлено на изображении ниже:

Структура тестового “проекта” следующая:

.
├── app
│  └── index.html
├── default.conf
└── docker-compose.yaml

Запуск и настройка Traefik

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

<blockquote>Терминация TLS в данном случае должна выполняться на стороне Traefik, но в рамках статьи этот момент сознательно опущен, чтобы не усложнять материал. Например, для реального рабочего проекта на основе Traefik, настройки TLS были выполнены на вышестоящем прокси-сервере с Nginx, т.к. именно он являлся точкой входа для других несвязанных сервисов, а потому все TLS-сертификаты выполнялись на ином сервере. Но это частный случай, и всё зависит от задачи. В любом случае, Traefik позволяет терминировать TLS при необходимости.</blockquote>

mkdir /opt/traefik && cd /opt/traefik
api:
  dashboard: true
  insecure: true

accessLog: {}

log:
  level: INFO

entryPoints:
  http:
    address: ":80"
  https:
    address: ":443"

http:
  routers:
    host:
   entryPoints:
      - http
      rule: Host(`domain.ru`)

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

В конфиге выше указывается минимальный набор:

Данный конфиг монтируется внутрь контейнера. Для примера ниже представлен docker-compose.yaml:

version: '3.8'

services:
  traefik:
    image: traefik:v2.2
    volumes:
   - ./traefik.yml:/traefik.yml:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    ports:
      - 80:80
      - 8080:8080
    restart: always
    networks:
      - default

networks:
  default:
    external:
      name: gateway

docker-compose.yaml – типовой файл, при запуске которого будет запущен Traefik в отдельной внутренней сети с именем default, которая создается автоматически. Но есть один примечательный и очень важный момент, связанный с этой сетью. Помимо сети по умолчанию (default), вручную создается отдельная сеть, в которую приходят запросы извне. В рамках данной статьи сеть названа gateway. И внутренняя сеть default линкуется с внешней сетью gateway.

docker network create \
  --driver=bridge \
  --attachable \
  --internal=false \
  gateway

docker-compose up -d

После запуска нужно убедиться, что ошибок в логах нет, и проверить дашборад (http://127.0.0.1:8080/dashboard/). Он не обязателен в рамках данной статьи, но просто наглядно демонстрирует, что Traefik запущен. Также в дашборде будут видны правила маршрутизации. По умолчанию доступ к дашборду по http.

Запуск проекта dev1

После того, как Traefik запущен, подготавливается первый инстанс проекта. Напомню, что для примера в качестве демонстрации используется максимально простой вариант: Nginx с проксированием до apache.

Для первого инстанса используется следующий docker-compose.yaml:

version: "3"

services:
  nginx:
    image: nginx:latest
    volumes:
    - ./app/index.html:/app/index.html
    - ./default.conf:/etc/nginx/conf.d/default.conf
    labels:
   - "traefik.enable=true"
      - "traefik.http.routers.nginx-dev1.rule=Host(`dev1.domain.ru`)"
      - "traefik.http.services.nginx-dev1.loadbalancer.server.port=8080"
      - "traefik.docker.network=gateway"
    networks:
    - default
    - dev1

  httpd:
    image: httpd:latest
    volumes:
    - ./app/index.html:/usr/local/apache2/htdocs/index.html
    networks:
      - dev1

networks:
  default:
    external: true
    name: gateway
  dev1:
    internal: true

Пояснения по конфигу:

Таким образом, все внешние запросы при обращении на dev1.domain.ru из Traefik будут приходить в контейнер с Nginx, где указаны метки для dev1 соответственно – сеть gateway будет обеспечивать эту связанность. И при этом не рушится межсервисное взаимодействие в рамках одного проекта – это обеспечивает внутренняя сеть dev1.

Конфигурационный файл default.conf:

server {
    listen 8080;
    server_name _;

    root /app;
    index index.php index.html;

    location / {
   proxy_pass http://httpd:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Пояснения по конфигу:

В файле app/index.html просто содержится статическая строчка для наглядного примера, в какой контейнер какого проекта пришёл запрос.

docker-compose up -d

Для проверки, что проект успешно запустился, можно зайти внутрь контейнера с Nginx и курлом проверить ответ:

docker exec -it project1_nginx_1 bash
root@6139a191e911:/# curl localhost:8080
It works!

PS. dev1 proj

Запуск проекта dev2

Запуск второго инстанса выполняется аналогично, например, в /opt/dev2. Меняются лишь метки и наименование внутренней сети. Ниже представлен пример docker-compose.yaml для наглядной демонстрации разницы между первыми и вторым инстансом:

version: "3"

services:
  nginx:
    image: nginx:latest
    volumes:
    - ./app/index.html:/app/index.html
    - ./default.conf:/etc/nginx/conf.d/default.conf
    labels:
   - "traefik.enable=true"
      - "traefik.http.routers.nginx-dev2.rule=Host(`dev2.domain.ru`)"
      - "traefik.http.services.nginx-dev2.loadbalancer.server.port=8080"
      - "traefik.docker.network=gateway"
    networks:
    - default
    - dev2

  httpd:
    image: httpd:latest
    volumes:
    - ./app/index.html:/usr/local/apache2/htdocs/index.html
    networks:
      - dev2

networks:
  default:
    external: true
    name: gateway
  dev2:
    internal: true

После запуска второго проекта, если используется тестовый домен для проверки, можно вписать в /etc/hosts своего рабочего компьютера 127.0.0.1, на котором слушает Traefik, и выполнять проверку:

127.0.0.1 dev1.domain.ru
127.0.0.1 dev2.domain.ru

При обращении на первый или второй инстанс запросы будут соотвественно распределяться в нужный контейнер.

Заключение

Изучив выше демонстрационный пример, можно без проблем настраивать сколько угодно копий одного проекта, при этом нет необходимости каждый раз вносить правки в Traefik – он динамически проверяет конфиграцию через API докера.

Из явных плюсов данного подхода можно отметить следующее:

Минусов, как таковых, в данном случае нет, разве что дополнительная сложность, но это плата за удобство