Comments 14
Вы будете смеяться, но то, что было описано в labels, можно повторить в формате TOML или YAML. Я предпочитаю YAML и ниже буду придерживаться его. Фломастеры у всех разные.
Файл traefik.yml […]
А ведь так хорошо начиналось-то...
Нет, то что было описано в labels, повторить в traefik.yml невозможно. Потому что traefik.yml — это статическая конфигурация, а labels — динамическая, и с точки зрения traefik это принципиально разные вещи.
Описаны такие статические элементы как логи, энтрипоинты, провайдеры, резолвер. Обратите внимание – статические, значит те, которые не хочется менять за всё время работы контейнера Traefik. Так-то можно и пару роутеров сюда засунуть.
Нет, нельзя туда засунуть роутеры.
Раз выяснилось, что Traefik есть дело до содержимого /etc/traefik/config, давайте положим туда конфиг, например, с информацией о сертификатах.
А это вообще работает?
Для примера объявлено несколько сетей, что иногда бывает в разных compose сборках. И указано конкретное значение метки traefik.docker.network, чтобы наш прокси ничего не перепутал и не соединил случайным образом.
А в динамической конфигурации это вообще работает? Это ж настройка провайдера...
Нельзя? Жаль. Просто видел там роутеры у других людей. Спасибо, сейчас поправлю.
Роутеры там вы могли видеть либо в traefik v1 (тут точно не знаю), либо в файловой динамической конфигурации, за которую отвечает настроенный вами file provider. Однако файловая динамическая конфигурация и статическая конфигурация — совершенно не одно и то же, и в traefik v2 они не могут заменять друг друга.
Так, с критикой автора закончил, теперь расскажу как делать правильно.
Самое первое — направление подключения. Добавлять все сети в docker-compose.yaml траефика — дело нудное и сложное в автоматизации, поступать следует строго наоборот.
Нужно создать сеть traefik и заводить в неё все контейнеры которым нужен доступ наружу:
services:
traefik:
image: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /etc/traefik:/etc/traefik
networks:
default:
name: traefik
Почему внутреннее имя сети default? Потому что от неё вы не избавитесь, она всё равно будет создана, а пространство адресов следует экономить.
Ну и в статическом конфиге неплохо бы указать эту самую сеть, чтобы traefik знал какой адрес у контейнера использовать:
[providers.docker]
watch = true
exposedbydefault = false
network = "traefik"
Кстати, рекомендую по возможности использовать конфигурацию формата toml, а не yaml. Она гораздо компактнее.
Теперь в эту настроенную сеть traefik можно добавлять контейнеры. Вот пример такого добавления:
services:
api:
image: ...
restart: unless-stopped
expose:
- 80
networks:
default:
aliases:
- api_internal
traefik:
labels:
- traefik.enable=true
- traefik.http.routers.api-$COMPOSE_PROJECT_NAME.rule=Host(`api.example.org`)
- traefik.http.services.api-$COMPOSE_PROJECT_NAME.loadbalancer.server.port=80
networks:
traefik:
name: traefik
external: true
На что тут следует обязательно обратить внимание — это на то, что хотя динамическая конфигурация пишется индивидуально для контейнеров, пространства имён всех роутеров, сервисов и мидлварей глобальны. И если забыть использовать для них уникальный суффикс (например $COMPOSE_PROJECT_NAME) — можно "напороться" на удивительные плавающие баги из-за конфликтов настроек.
Также обратите внимание, что два сервиса находящиеся в сети traefik могут "решить" связаться по этой сети если попытаются использовать для этого имена сервисов (а эти имена могут оказаться неуникальны для разных проектов docker compose). Именно потому я назначил сервису псевдоним api_internal в сети default — чтобы другие сервисы могли ссылаться на него как api_internal. Тут требуется некоторое соглашение в команде, чтобы в соседнем проекте не оказалось сервиса api_internal. В идеале бы вообще выключить разрешение имён докера в сети traefik, но кажется докер так не умеет.
А можете поделиться рецептом, как настроить доступ к MySQL через traefik (если такое вообще возможно)? Допустим у меня есть два сайта, у них отдельные docker-compose.yml в которых поднято всё нужное, https работает через traefik, но вот к MySQL приходится обращаться напрямую, по ip и открывая порт наружу.
Делаете контейнер с ssh сервером, даёте ему порт наружу, и точно так же выделяете ему сетку. Тот, кому нужен доступ к MySQL, может установить туннель до этого контейнера, а точку назначения указать через доменное имя. Поскольку доменное имя точки назначения резолвится сервером, а сервер в докере — автоматически появляется возможность указывать имена контейнеров.
Иными словами, работает это как-то так. Служебный контейнер:
services:
ssh-gateway:
image: …
restart: unless-stopped
ports:
- "2222:22"
volumes:
- ssh-gateway:/etc/ssh
networks:
default:
name: ssh-gateway
volumes:
ssh-gateway:
И сервис которому нужен доступ через туннель:
services:
db:
image: postgres
restart: unless-stopped
expose:
- 5432
networks:
default:
aliases:
- db-internal
ssh-gateway:
aliases:
- db.dev.my-project.example.org
networks:
ssh-gateway:
name: ssh-gateway
external: true
Теперь, когда нужен доступ к контейнеру, достаточно поднять туннель и можно подключаться к локальному концу туннеля:
ssh -N -L 5432:db.dev.my-project.example.org:5432 -p 2222 example.org
Так, вариант с ssh — универсален, однако я сейчас понял что конкретно к MySQL можно попробовать и через traefik подключиться.
План действий тут будет такой. Во-первых, для контейнера нужно настроить TCP роутер, а не HTTP, указав правило на HostSNI:
labels:
- traefik.enable=true
- traefik.tcp.routers.mysql-$COMPOSE_PROJECT_NAME.rule=HostSNI(`mysql.example.org`)
- traefik.tcp.services.mysql-$COMPOSE_PROJECT_NAME.loadbalancer.server.port=3306
Во-вторых, надо уговорить клиента использовать TLS соединение с сервером.
Если протокол не подставит подводных камней — должно в такой конфигурации сработать. Но я не уверен, потому что ни разу так не настраивал.
Все хорошо, но это все работает для *.example.com, а какая настройка нужна на сам example.com?
Э-э-э, а разница-то в чём?
Не имеет значения. Это такой же хост с именем и адресом. Если ДНС привёл за ним к серверу с Traefik, то всё что нужно — это роутер Traefik с правилом, принимающим это имя и сертификат в файле или резолвере. Можно напрячь HostRegexp(), чтоб в него влезло и это имя. Можно вообще не использовать HostRegexp() или Host(), тогда будут приниматься любые имена. Можно, например, вот так:
.rule=Host(`example.com`) || HostRegexp(`{subdomain:[az0-9]+}.example.com`)
разница в том что с субдоменами все отлично работает, но как только делашь роутер наexample.com то на основном домене не работае, указываешь, сублрмен + example.com все снова замечательно
Traefikация сервера