Цель данной статьи: демонстрация запуска нескольких инстансов одного проекта в 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 можно выполнять как через отдельный файл конфигурации, подключаемый в контейнер, так и частично через метки контейнеров – их не обязательно прописывать в конфиг, достаточно указать в нужном сервисе. Пример будет ниже.
<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.
internal=false
:docker network create \ --driver=bridge \ --attachable \ --internal=false \ gateway
docker-compose up -d
После запуска нужно убедиться, что ошибок в логах нет, и проверить дашборад (http://127.0.0.1:8080/dashboard/). Он не обязателен в рамках данной статьи, но просто наглядно демонстрирует, что Traefik запущен. Также в дашборде будут видны правила маршрутизации. По умолчанию доступ к дашборду по http.
После того, как 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
Запуск второго инстанса выполняется аналогично, например, в /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 докера.
Из явных плюсов данного подхода можно отметить следующее:
Минусов, как таковых, в данном случае нет, разве что дополнительная сложность, но это плата за удобство