MEAN-stack и Docker. Часть 3 - Traefik reverse proxy
Третья часть цикла, посвященного контейнеризации MEAN-stack приложений, в которой мы настроим обратный прокси-сервер (reverse proxy) и балансировщик нагрузки (load balancer) Traefik.
Содержание цикла
- Часть 1 - Dockerfile, образы, контейнеры
 - Часть 2 - Docker Compose
 - Часть 3 - Traefik reverse proxy
 - Часть 4 - Docker Swarm
 - Часть 5 - Mongo Replica Set в Docker Swarm
 
Введение
В предыдущей части мы написали docker-compose файл, в котором настроили запуск сервисов с серверной частью приложения, клиентской частью и базой данных. Теперь же нам необходимо настроить приложение следующим образом:
- Angular приложение должно быть доступно только по адресу 
docker-example.local; - серверное api должно принимать запросы по адресу 
api.docker-example.local; - должен быть открыт только порт 80.
 
Для этого нужно поднять прокси-сервер, который будет принимать все запросы на порту 80 и по хосту ретранслировать запросы на сервис client для docker-example.local и на server для api.docker-example.local.
Самое популярное решение для этих целей - использовать прокси-сервер nginx. Однако в этом проекте я попробую использовать Traefik - молодой реверс-прокси сервер и балансировщик нагрузки, который подает большие надежды.
Такой выбор был сделан по следующим причинам:
- хорошая интеграция с Docker, Docker Swarm, Kubernetes
 - более интуитивная конфигурация и настройка
 - HTTPS от Let's Encrypt из коробки
 - еще ряд некоторых плюшек из разряда Websocket, HTTP/2, метрики Prometheus
 - интересно попробовать
 
Hosts
Сперва добавим наши фейковые домены в hosts:
# windows:      c:/windows/system32/drivers/etc/hosts
# ubuntu/mac:   /etc/hosts
127.0.0.1       docker-example.local
127.0.0.1       api.docker-example.local 
# Traefik Dashboard
127.0.0.1       traefik.docker-example.local
Конфигурация Traefik
Вся настройка Traefik производится в том же docker-compose файле.
docker-compose.yml
services:
  # ...
  # Добавляем сервис реверс-прокси Traefik
  reverse-proxy:
    image: traefik
    # В командах производим основную настройку
    command:
      # Включаем Traefik Dashboard (порт 8080)
      - "--api"
      # Точка входа http на порту 80 используется по умолчанию
      - "--entrypoints=Name:http Address::80"
      - "--defaultentrypoints=http"
      # Включаем режим совместимости с Docker
      - "--docker"
      # Домен по умолчанию
      - "--docker.domain=docker-example.local"
      # Следить за изменениями докера
      - "--docker.watch"
    restart: always
    # Открываем только 80 порт
    ports:
      - 80:80
    
    # Даем Traefik доступ к docker.sock 
    # для отслеживания событий Docker
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    # Подключаемся к сети для прокси
    networks:
      traefik-net:
        
        # Альтернативное имя хотса для доступа по сети traefik-net
        aliases:
          - api.docker-example.local
    # В метках сервисов прописываем правила для Traefik
    labels:
      # Запросы на хост `traefik.docker-example.local`
      - traefik.frontend.rule=Host:traefik.docker-example.local
      # перенаправлять на порт 8080 (Traefik Dashboard) этого сервиса
      - traefik.port=8080
Настройка сервисов
Сами сервисы подключим к сети traefik-net, избавляемся от проброшенных портов. В labels зададим правила проксирования.
server service:
  server:
    image: myusername/public:mean-docker-server
    restart: on-failure
    depends_on:
      - db
    environment:
      - "db:uri=mongodb://db/docker"
    networks:
      # Подключаемся к сети 
      - traefik-net
      - db-net
    # В метках прописываем правила для Traefik
    labels:
      # Для реверс-прокси использовать сеть traefik-net
      - traefik.docker.network=traefik-net
      # Запросы на хост `api.docker-example.local`
      - traefik.frontend.rule=Host:api.docker-example.local
      # перенаправлять на порт 3000 этого сервиса
      - traefik.port=3000
client service:
  client:
    image: myusername/public:mean-docker-client
    restart: on-failure
    depends_on:
      - server
    networks:
      # Подключаемся к сети 
      - traefik-net
    labels:
      # Запросы на хост `docker-example.local`
      - traefik.frontend.rule=Host:docker-example.local
      # перенаправлять на порт 4000 этого сервиса
      - traefik.port=4000
db service:
  db:
    image: mongo
    restart: on-failure
    # К сети proxy-net не подключаемся
    # с сервером общаемся по db-net
    networks:
      - db-net
    labels:
      # В проксировании не нуждаемся
      - traefik.enable=false
Сети:
networks:
  traefik-net:
    external: true
  db-net:
В работе с сетями у траефика есть небольшая проблема.  Дело в том, что docker compose при старте создает сети и к их именам добавляет префикс с названием проекта. Т.е. в compose файле мы указываем, что необходимо создать сеть traefik-net. В метке сервиса server мы указываем traefik, что для прокси из двух сетей необходимо использовать traefik.docker.network=traefik-net. Но после старта эта сеть не будет найдена, ибо докер назвал ее mean-docker-stack_traefik-net.
Есть два выхода:
- Предугадывать переименование сети и указывать 
traefik.docker.network=mean-docker-stack_traefik-net; - Предварительно перед запуском создать external сеть 
traefik-netи использовать ее, указав, что она являетсяexternalв docker-compose файле. 
В данном случае я воспользовался вариантом №2.
Настройка HTTPS от Let's Encrypt в Traefik
Для настройки https в командах сервиса reverse-proxy добавим/заменим следующие строки:
  reverse-proxy:
    
    # ...
    command:
      - "--api"
      - "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
      - "--entrypoints=Name:https Address::443 TLS"
      - "--defaultentrypoints=http,https"
      - "--acme"
      - "--acme.storage=/etc/traefik/acme/acme.json"
      - "--acme.entryPoint=https"
      - "--acme.httpChallenge.entryPoint=http"
      - "--acme.onHostRule=true"
      - "--acme.onDemand=false"
      - "--acme.email=contact@mydomain.com"
Во время тестирования рекомендую использовать Let's Encrypt's staging server:
      - "--acme.caServer=https://acme-staging.api.letsencrypt.org/directory"
Подробнее читать в официальной документации Traefik.
Запуск
Предварительно создадим сеть traefik-net:
docker network create traefik-net
Запускаем уже известной нам командой:
docker-compose up
Рабочее приложение наблюдаем в браузере по адресу http://docker-example.local/
В следующей части попробуем развернуть наше приложение в кластере Docker Swarm.