Как стать автором
Обновить

Traefikация сервера

Уровень сложностиСредний
Время на прочтение17 мин
Количество просмотров26K

Статья предназначена для тех, кто подбирает себе reverse proxy или load balancer и хочет приглядеться к Traefik v2 в этом качестве. Рассмотрена установка в Docker и взаимодействие с его контейнерами, организация как собственного HTTPS шифрования, так и проброс TCP трафика на HTTPS сервер. Без Kubernetes, без SWARM.

Предположительно, у вас уже установлен Docker, вы знакомы с compose файлами, умеете выбирать хостинг план и создавать директории. Просто не хочется отнимать ваше время на это.

Установка Traefik.

Предлагаю пока выбрать порты 8080 и 8443, чтобы потренироваться на кошечках, не беспокоя основные сервисы. Начнём с эксперимента. Можно ли запустить Traefik вообще без настроек?

mkdir traefik && cd traefik
nano docker-compose.yml

Содержимое файла docker-compose.yml

version: '3'
services:
  traefik:
    image: traefik:v2.10.4

Предпочитаю прописывать версию, чтобы избегать неожиданного поведения и захламления образами. С уязвимостями не приходилось сталкиваться, а с неожиданным поведением новых версий разных проектов приходилось. На момент создания статьи версия была 2.10.4. Юра, поехали!

sudo docker compose -p traefik up -d

Что-то скачалось, создалось, запустилось. Вроде работает, ничто нигде не отвалилось. Эксперимент можно признать успешным. Пора прикручивать блэкджек. В смысле дашборд, порты, конфиги из меток, конфиги из статического файла. И сертификат с ключом. Ведь он уже есть на сервере, правда? Тут, кстати, у Traefik есть неприятная мозоль. Когда он сам запрашивает сертификат у ACME провайдера, то сохраняет его в JSON формате. И если нужно поделиться с каким-нибудь другим HTTPS сервисом, то придётся выдирать его из JSON и сохранять в привычном PEM формате. Благо, скрипты для этого в интернете есть. Можно и самостоятельно написать, если не лень. Там тот же PEM в виде одной длинной строки.

Файл docker-compose.yml сразу на максималках:

version: '3'
services:
  traefik-proxy:
    image: traefik:v2.10.4
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    env_file:
      - traefik.env
    ports:
      - 8080:8080
      - 8443:8443
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./logs/:/var/log/traefik/
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./config/:/etc/traefik/config/:ro
      - ./cert/:/etc/traefik/cert/:ro
      - ./secret:/etc/traefik/secret:ro
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-http-router.entrypoints=websecure"
      - "traefik.http.routers.traefik-http-router.rule=Host(`traefik.example.com`) &&
	     (PathPrefix(`/traefik`) ||
		  Headers(`Referer`, `https://traefik.example.com:8443/traefik/dashboard/`))"
      - "traefik.http.routers.traefik-http-router.tls=true"
      - "traefik.http.routers.traefik-http-router.service=api@internal"
      - "traefik.http.routers.traefik-http-router.middlewares=traefik-mw-auth,traefik-mw-strip"
      - "traefik.http.middlewares.traefik-mw-auth.basicauth.usersFile=/etc/traefik/secret"
      - "traefik.http.middlewares.traefik-mw-strip.stripprefix.prefixes=/traefik"
      # SANs
      #- "traefik.http.routers.traefik-http-router.tls.certresolver=myresolver"
      - "traefik.http.routers.traefik-http-router.tls.domains[0].main=traefik.example.com"
      - "traefik.http.routers.traefik-http-router.tls.domains[0].sans=traefik.example.com,kifeart.example.com"
    networks:
      traefik-bridge:
#        ipv4_address: 192.168.100.100
networks:
  traefik-bridge:
    name: traefik
#    ipam:
#      config:
#        - subnet: 192.168.100.0/24
#          gateway: 192.168.100.1

Прописаны привычные опции, порты, подсеть, маппинги чего-то полезного, включая файл статической конфигурации. Он должен называться traefik.toml или traefik.yml и находиться в одном из предопределённых мест. Хорошо бы, чтобы к следующему запуску этот файл уже был, пока добрый docker compose не создал папку с таким именем.

touch traefik.yml

Но самое главное! Прописаны конфиги для Traefik в виде labels.

Entrypoints, routers, middlewares, services.

Немного теории. Существуют entrypoints, в которые поступают запросы клиентов и services в контейнерах или локальной сети, которые ждут эти запросы. Между ними бесконечность, называемая Traefik. За организацию движения запросов и ответов между энтрипоинтами и сервисами отвечают routers. Если запрос нуждается в дополнительной обработке, на помощь приходят middlewares.

Labels

Давайте пробежимся лёгкой рысцой по имеющимся меткам.

traefik.enable=true
В файле статической конфигурации позже будет прописано, что Traefik должен смотреть только на те контейнеры, которые вот так себя обозначили.

traefik.http.routers.traefik-http-router.entrypoints=websecure
В Traefik роутеры делятся на http, tcp и udp. В данном случае http роутер с именем traefik-http-router должен смотреть только на энтрипоинт с именем websecure. Можно через запятую указать другие энтрипоинты. Можно вообще убрать этот конфиг. Тогда роутер будет смотреть на все имеющиеся энтрипоинты.

traefik.http.routers.traefik-http-router.rule=
Вот здесь можно проявить богатство фантазии и объединить и разъединить различные условия срабатывания роутера. В данном конкретном случае указано, что интересуют те запросы, которые пришли на хост с заданным именем traefik.example.com и заданным путём /traefik. Ну, а фишка с хедером была найдена в интернете, когда выяснилось, что дашборд плохо принимает запросы, основанные только на пути. Прошу заметить, порт потом надо будет переписать. И ещё болячка. Завершающий слэш dashboard/ обязателен, когда будете набирать в браузере. Подробнее про правила.

traefik.http.routers.traefik-http-router.tls=true
Схема запроса HTTPS, требуется найти сертификат для запрошенного имени хоста в собственном хранилище или PEM файлах и организовать TLS сессию.

traefik.http.routers.traefik-http-router.service=api@internal
Запрос, попавший в правила этого роутера передать себе любимому. В смысле сервису по имени api@internal.

traefik.http.routers.traefik-http-router.middlewares=traefik-mw-auth,traefik-mw-strip
Перед тем как попасть в сервис запрос будет обработан и преобразован миддлварями с указанными именами в порядке слева направо.

traefik.http.middlewares.traefik-mw-auth.basicauth.usersFile=/etc/traefik/secret
Эта миддлварь умеет делать больно авторизацию типа Basic. Если принять, что хранилищем паролей будет файл secret и сгенерировать в него хотя бы один пароль с помощью утилиты htpasswd, то доступ в дашборд Traefik будет ограничен тем, кто не знает пароль. Грустно это.

htpasswd -сb secret adm adm

Если утилита htpasswd отсутствует, придётся скачать её в составе apache2-utils. В Ubuntu это так.

sudo apt update && sudo apt install apache2-utils

traefik.http.middlewares.traefik-mw-strip.stripprefix.prefixes=/traefik
Следующая миддлварь с именем traefik-mw-strip отрезает от запроса префикс /traefik перед тем как передать в сервис. Ну, или какой другой придумаете. Хоть однобуквенный, хоть UUID. Только важно, чтобы префикс начинался со слэша. Конечно же разных миддлварей намного больше.

Всё это увлекательно, но как Traefik понимает на каком IP адресе запущен контейнер с сервисом, которому нужно передать попавший в правила запрос? В Докере же сети генерируются рандомно. Вот здесь работает магия использования провайдера Docker в описанном ниже файле статической конфигурации traefik.yml. Неважно, какой адрес у контейнера. Провайдер его предоставит. Собственно, давайте переходить к этому файлу.

Статическая конфигурация

Статическая конфигурация может быть описана в формате TOML или YAML. Я предпочитаю YAML и ниже буду придерживаться его. Фломастеры у всех разные.
Файл traefik.yml:

global:
  checkNewVersion: true
  sendAnonymousUsage: false
log:
  level: DEBUG # DEBUG, INFO, WARNING, ERROR, CRITICAL
  format: common # common, json, logfmt
  filePath: /var/log/traefik/traefik.log
accesslog:
  format: common # common, json, logfmt
  filePath: /var/log/traefik/access.log
api:
  dashboard: true
entryPoints:
  web:
    address: ":8080"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: false
  websecure:
    address: ":8443"
#  registry:
#    address: ":5000"
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    directory: /etc/traefik/config
    watch: true
certificatesResolvers:
  myresolver:
    acme:
      email: your-email@example.com
      storage: acme.json
      tlsChallenge: {}

Описаны такие статические элементы как логи, энтрипоинты, провайдеры, резолвер. Обратите внимание – статические, значит те, которые не хочется менять за всё время работы контейнера Traefik.

Раздел global просто как табуретка. Версию проверять, статистику не отправлять.
Раздел log указывает на путь внутри контейнера, куда писать логи. В файле docker-compose.yml он пока замаплен в папку ./logs, чтоб далеко не бегать. А когда кошечки закончатся, можно будет переписать на цивильный /var/log/traefik.

Кнопка включения дашборда находится в разделе api.

Раздел entryPoints. Имейте в виду, что энтрипоинты подразумеваются в сети контейнера, а снаружи к ним надо ещё открыть доступ через ports: файла docker-compose.yml. Описанная здесь схема перехода с HTTP на HTTPS на самом деле создаёт виртуальный роутер. Вы можете найти его в дашборде под именем web-to-websecure@internal. Адреса записаны в краткой форме, а полная форма – 0.0.0.0:8443.

Далее providers. Провайдер docker передаёт в Traefik информацию о контейнерах через сокет unix:. А поскольку указано, что exposedByDefault=false, то Traefik будет игнорировать те контейнеры, в которых не помечено traefik.enable=true. Вечеринка для избранных.
Провайдер file. Указана папка внутри контейнера, где неожиданно для Traefik могут появляться динамические конфиги. В docker-compose.yml эта папка замаплена в ./config на хосте. Поскольку опция watch включена, то Traefik будет периодически следить за изменениями в файлах. Можно указать провайдера в виде только одного файла. Тогда directory должно быть заменено на file и файл соответствующе назван и замаплен с хоста.
Конечно же, всего провайдеров не два, а намного больше. Возможно, какие-то из них уже применяются в вашем окружении.
И наконец certificatesResolvers. По умолчанию Traefik использует Letsencrypt. Можно переопределить. Указан tlsChallenge как TLS-ALPN-01 challenge на 443 порте. Но это потом.

Динамическая конфигурация

Раз выяснилось, что Traefik есть дело до содержимого /etc/traefik/config, давайте положим туда конфиг, например, с информацией о сертификатах. Если docker compose ещё не запускался с новым docker-compose.yml, то папки ./config на хосте пока не существует.

mkdir config
nano config/certificates.yml

Содержимое файла certificates.yml

tls:
  certificates:
    - certFile: /etc/traefik/cert/fullchain.pem
      keyFile: /etc/traefik/cert/privkey.pem
      stores:
        - default
#    - certFile: /etc/traefik/cert/selfsigned.pem
#      keyFile: /etc/traefik/cert/selfkey.pem
#      stores:
#        - default
  stores:
    default:
      defaultCertificate:
        certFile: /etc/traefik/cert/fullchain.pem
        keyFile: /etc/traefik/cert/privkey.pem
  options:
    default:
      sniStrict: true

Вы уже догадались по именам файлов, что они скопированы из Certbot. Кстати, действительно, скопируйте ключ и сертификат в папку ./cert. Возможно, её придётся сначала создать. При желании даже с помощью docker compose, там все папки прописаны. Пока давайте сделаем вид, что Traefik не обучен сам запрашивать ACME challendge и оставим в таком виде.

В разделе tls.certificates указаны пути к PEM файлам. Traefik проходит по этим путям и внимательно читает и извлекает все Certificate Subject Alternative Name (SAN) из файлов, чтобы потом сопоставлять нужные сертификаты клиентам в соединениях. Проверьте в лог-файле, чего он извлекает. Сертификатов может быть несколько разных. Их перечисление определяется правилами YAML или TOML.

В разделе tls.stores.default.defaultCertificate указан сертификат, который Traefik подставляет когда ни один не подошёл под запрос. Если информации о дефолтном сертификате нет или подходящего файла нет, то сгенерирует и выдаст самоподписанный. Можете понаблюдать за этим в браузере, зайдя на https://traefik.example.com:8443/traefik/dashboard/ и перемещая certificates.yml из папки и обратно. Конфигурация же динамическая. Надеюсь, не надо объяснять, что имя сервера здесь вымышленное для примера.

В разделе tls.options.default указана опция sniStrict. Если ни один сертификат не подошёл, то сразу 404. Зайдите теперь браузером по IP адресу, совсем другие ощущения. Вообще TLS опции есть разные. Если интересно, https://doc.traefik.io/traefik/https/tls/

Сами вы можете создать здесь конфигурацию с роутером и сервисом для какого-нибудь локального процесса в системе на примере файла для Registry ниже.

Конфигурация переменными

Здесь затронем максимально кратко. В docker-compose.yml для сервиса traefik-proxy через секцию env_file: указан файл traefik.env, куда можно прописать статические переменные и их значения для Traefik. Сохраните там, например, такое:
echo TRAEFIK_API_DISABLEDASHBOARDAD=true > traefik.env
Про остальные можно узнать на https://doc.traefik.io/traefik/reference/static-configuration/env/

Напомню путь к дашборду Traefik:
https://traefik.example.com:8443/traefik/dashboard/.
Надеюсь, у вас доступ к нему защищён не паролем adm.

Хотя нет. Посмотрите сначала, как он редиректит с HTTP:
http://traefik.example.com:8080/traefik/dashboard/

С пациентом всё ясно. А может он не только себя показывать, но и на другие контейнеры смотреть?

Проброс на HTTP сервер через свой HTTPS

Все любят котиков контейнеры. Давайте развернём систему управления контейнерами Portainer CE и пропустим её через :8443/portainer.

Создайте рядом с директорией traefik другую директорию и романтично назовите её portainer.

cd ..
mkdir portainer
nano portainer/docker-compose.yml

Содержимое файла docker-compose.yml

version: "3"
services:
  portainer:
    image: portainer/portainer-ce:2.18.4-alpine
    container_name: portainer
    command: -H unix:///var/run/docker.sock
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    expose:
      - 9000
      - 9443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data/:/data/
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=poportainer"
      - "traefik.http.routers.portainer-http-router.entrypoints=websecure"
      - "traefik.http.routers.portainer-http-router.rule=Host(`traefik.example.com`) && (PathPrefix(`/portainer`)"
      - "traefik.http.routers.portainer-http-router.service=portainer-http-service"
      - "traefik.http.routers.portainer-http-router.tls=true"
      - "traefik.http.routers.portainer-http-router.middlewares=portainer-mw-strip"
      - "traefik.http.middlewares.portainer-mw-strip.stripprefix.prefixes=/portainer"
      - "traefik.http.services.portainer-http-service.loadbalancer.server.port=9000"
      # SANs
      #- "traefik.http.routers.portainer-http-router.tls.certresolver=myresolver"
      - "traefik.http.routers.portainer-http-router.tls.domains[0].main=traefik.example.com"
      - "traefik.http.routers.portainer-http-router.tls.domains[0].sans=traefik.example.com,kifeart.example.com"
    networks:
      portainer-used:
      portainer-unused:
networks:
  portainer-used:
    name: poportainer
  portainer-unused:

Если пока не касаться меток, предназначенных для Traefik, обратите внимание на остальные детали. Информацию о контейнерах Portainer получает от Docker так же через unix: сокет. Порты объявлены не через ports:, а через expose:, то есть на хосте открывать не надо. Для примера объявлено несколько сетей, что иногда бывает в разных compose сборках. И указано конкретное значение метки traefik.docker.network, чтобы наш прокси ничего не перепутал и не соединил случайным образом.

С самими метками, надеюсь, уже понятно по предыдущему примеру, чего хотелось бы передать в Traefik. Давайте повторим на новом примере.

  • Обработка контейнера включена;

  • сеть контейнера явно задана;

  • роутер с именем portainer-http-router наблюдает только за энтрипоинтом websecure;

  • где собирает запросы на хост traefik.example.com с путём, начинающимся на /portainer;

  • после чего передаёт на миддлварь portainer-mw-strip для подстрижки этого '/portainer'.

  • Далее запрос покидает роутер через сервис, указанный в роутере. Совершенно случайно этим сервисом оказался portainer-http-service на 9000 порте, который на самом деле контейнер Portainer в вышеуказанной сети в доме, который построил Джек.

Запустите, наконец, оба контейнера. Если они расположены в домашней папке, можно сделать это быстро через явное указание файла docker-compose.yml примерно вот так:

sudo docker compose -f ~/portainer/docker-compose.yml -p portainer up -d
sudo docker compose -f ~/traefik/docker-compose.yml -p traefik up -d

Если в каком-то другом месте, поправьте пути как надо. Если душа просит переходить в нужную папку и запускать там docker compose up -d, то так и сделайте. По идее что-то открылось на https://traefik.example.com:8443/portainer.

Ну и? На что похож Portainer? На 404? Да. Это потому, что у контейнера traefik нет доступа в сеть poportainer. Поскольку вы явно указывали имя и контейнера и сети, то можно легко их использовать и дать руками доступ в консоли:

sudo docker network connect poportainer traefik

А так пришлось бы догадываться об автоматически назначенных именах portainer_portainer-used и traefik-traefik-proxy-1. Если связка Traefik-Portainer нужна не на пять минут, а как долговременный сервис, наверное, удобней прописать это дело в traefik/docker-compose.yml.

services:
  traefik-proxy:
  # ...
    networks:
      poportainer:
networks:
  poportainer:
    external: true

Только теперь запускать придётся после Portainer, поскольку сеть с таким именем создаётся там. Или можно насоздавать сетей руками и выдавать контейнерами через такой вот external.
Доступ получен, traefik перезапущен, на https://traefik.example.com:8443/portainer появилось симпатичное приглашение залогиниться. Логин admin, пароль admin. Поменяйте в разделе Settings / Users. Кто первый встал, того и тапки. После формальности загляните в Home / primary / Networks / poportainer. Вот он результат трудов - контейнер traefik в этой сети имеет свой адрес. И хоть в сети portainer_portainer-unused и есть одинокий контейнер, но Traefik он не интересует. В дашборде самого Traefik есть HTTP сервис portainer-http-service@docker с IP адресом, совпадающим с адресом контейнера portainer в сети poportainer. Провайдер Docker!

Кстати. Помните одинокую переменную TRAEFIK_API_DISABLEDASHBOARDAD для Traefik? Её тут видно в соответствующем контейнере.

Проброс TCP на HTTPS

Соединять нешифрованный трафик любой Squid умеет. А можно соединить с сервером, который сам организует свои TLS сессии? Можно через TCP, но поскольку снаружи сессии доступно очень мало информации, выбор топоров для этой каши небольшой. Можно применить только четыре правила: HostSNI(``), HostSNIRegexp(``), ClientIP(``), ALPN(``). Зато рассматривает Traefik TCP правила в приоритете перед HTTP, что позволяет смешивать роутеры на одном энтрипоинте с заранее известным результатом.

Надеюсь, вы обратили внимание, что в секции expose у Portainer указан также и 9443? Portainer запускает на этом порте HTTPS версию, даже если не открывать к нему доступ. Но мы воспользуемся. Если в (Portainer) Settings / SSL certificate предоставить файлы ключа и сертификата, то они будут использованы против вас в TLS соединении. Если не давать, то будет самоподписанный сертификат на localhost. Добавьте в portainer/docker-compose.yml в секцию labels собственно вот эти TCP labels, выровняйте их с остальными, как того требует YAML:

    labels:
    #...
      - "traefik.tcp.routers.portainer-tcp-router.entrypoints=websecure"
      - "traefik.tcp.routers.portainer-tcp-router.rule=!ClientIP(`8.8.8.8`)"
 #     - "traefik.tcp.routers.portainer-tcp-router.rule=ClientIP(`0.0.0.0/0`)"
 #     - "traefik.tcp.routers.portainer-tcp-router.rule=HostSNI(`megatest.example.com`)"
      - "traefik.tcp.routers.portainer-tcp-router.tls.passthrough=true"
      - "traefik.tcp.routers.portainer-tcp-router.service=portainer-tcp-service"
      - "traefik.tcp.services.portainer-tcp-service.loadbalancer.server.port=9443"

И перезапустите.

sudo docker compose -f ~/portainer/docker-compose.yml -p portainer up -d

Если хотите протестировать правило на SNI, то сначала придётся подшаманить в hosts на клиенте или на DNS сервере, отвечающем за ваш домен. Или оставить в правиле уже настроенный traefik.example.com. Да сейчас от HTTPS сервера Portainer ничего и не нужно. Просто убедиться, что он жив, Traefik с ним соединяет (TCP to HTTPS) и можно посмотреть на знаменитый сертификат localhost. Добавленные метки TCP конфига по смыслу такие же как HTTP.

  • Роутер portainer-tcp-router следит за энтрипоинтом websecure,

  • если там срабатывает правило "любой клиент кроме Гугла",

  • то передаёт уже не запросы, а пакеты на сервис portainer-tcp-service, которым так удачно оказался контейнер portainer. В доме, который построил Джек.

Когда закончите тестировать, закомментируйте это rule=!ClientIP(8.8.8.8). Оно же перекрыло HTTP доступ к дашборду и Portainer.

HTTPS over IP

Факультатив, можно не читать. Про Traefik тут ничего нового. Новое разве что про Docker, Portainer и Registry. Просто хочется показать путь между граблями, на котором Traefik может действительно помочь.

Вы уже наверняка облазили Portainer и нашли там секцию App Templates. А в ней шаблон контейнера Registry. Да, можно развернуть собственное хранилище Docker образов двумя кликами. Разверните, загляните в него, скопируйте IP адрес и переходите в раздел Settings / Registries. Там добавьте custom registry и укажите скопированный IP адрес и порт. Как удобно, что Portainer догадывается про 5000 порт в подсказке! Наверняка ребята разбираются в вопросе. Сохраните и возвращайтесь в раздел Containers. А что если сохранить Registry в Registry? Вот умора то! Откройте его контейнер (или любой другой), в секции Create image переключите Registry на свой новенький локальный, задайте имя образа и примените Create. Скорость исполнения желаний поражает. Образ готов, найдите его в разделе Images, откройте и попытайте отправить в реестр (кнопка Push в секции Image tags). Ага... "Failure, http: server gave HTTP response to HTTPS client". Да где я тебе HTTPS response возьму, собака? Может, в консерватории что-то не в порядке? Давайте вернёмся в Settings / Registries и чётко недвусмысленно обозначим URL как http. А там в редактировании при наведении на знак вопроса всплывает чёткое и недвусмысленное "Any protocol or trailing slash will be stripped if present." То есть нетъ. Победа была так близка. Ладно, придётся курить мануал.

Чтобы ускорить процесс, сразу скажу, что с /etc/docker/daemon.json способ не рабочий для Portainer. Может быть Docker что-то себе помечает, но Portainer своё поведение не меняет. А вот с самоподписанным сертификатом очень даже рабочий, и Traefik здесь может помочь. Мне поначалу казалось, что достаточно взять portainer:/etc/ssl/certs/ca-certificates.crt, дописать в него свой самоподписанный и положить на место. Но оказалось, что показалось. Способ тоже не рабочий. Давайте уже делать по официальному руководству. План работ такой:

  • пересоздать Registry без маппинга порта

  • в Traefik создать энтрипоинт :5000

  • в Traefik создать HTTP сервис registry

  • в Traefik создать между ними HTTP роутер :5000 <-> registry

  • создать самоподписанный сертификат

  • положить его на хосте в папки в traefic/cert и /etc/docker/certs.d/192.168.100.100:5000/ под именем ca.crt, причём именно с номером порта, всё серьёзно

  • направить Portainer по новому адресу

Пересоздавать Registry приходится потому, что Portainer мило мапит в него случайный порт хоста. А Docker услужливо открывает этот порт на файрволе для всего интернета. Можете проверить http://traefik.example.com:<random port>/v2/_catalog - где вместо <random port> то, что присвоено хосту в 0.0.0.0:<random port> у вашего Registry.

Поскольку отредактировать контейнер возможности нет, придётся начать с удаления. В Portainer в списке контейнеров найдите свой Registry, отметьте чекбокс для него и нажмите Remove. Затем снова App Templates -> Registry, впишите имя registry, потому что оно позже будет использоваться и выберите сеть traefik. Далее нажмите Show advanced options, в открывшемся удалите порт маппинг и нажмите Deploy the container. Половина дела сделана.

Вы заметили, что в файле traefik/docker-compose.yml есть возможность прибить гвоздями ipv4_address и subnet? Это не случайно. В сертификате магия провайдера Docker не работает, нужно записать IP адрес раз и навсегда. Заодно это поможет соседу Registry всегда получать 192.168.100.2 после перезагрузки. Раскомментируйте ipv4_address и всё в секции networks от ipam до gateway. Возможно, потребуется пересоздать контейнер.

sudo docker stop traefik
sudo docker rm traefik

Энтрипоинт не может быть добавлен динамически. Поэтому отредактируйте также файл статической конфигурации traefik/traefik.yml, раскомментировав энтрипоинт registry и соответствующий адрес (0.0.0.0:5000).

Остался роутер, сервис и сертификат. Как всё сложно.

nano traefic/config/registry.yml

Содержимое файла registry.yml

http:
  routers:
    registry-http-router:
      rule: Host(`192.168.100.100`)
      entrypoints:
        - registry
      service: registry-http-service
      tls:
        domains:
          - main: "IP:192.168.100.100"
            sans: "IP:192.168.100.100"
#        certResolver: do-not-use-it
  services:
    registry-http-service:
      loadBalancer:
        servers:
          - url: http://192.168.100.2:5000
tls:
  certificates:
    - certFile: /etc/traefik/cert/192.168.100.100-cer.pem
      keyFile: /etc/traefik/cert/192.168.100.100-key.pem

Прошу обратить внимание на url сервиса. И http, и конкретные адрес и порт.

Дальше генерация сертификата. Скопируйте пример из документации Docker, подправьте имена файлов, срок валидности и вуаля. Можно даже создать сразу в traefic/cert, всё равно нигде кроме Portainer он не нужен.

cd traefic/cert
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 \
  -nodes -keyout 192.168.100.100-key.pem -out 192.168.100.100-cer.pem -subj "/CN=192.168.100.100" \
  -addext "subjectAltName=IP:192.168.100.100"

Проверьте, есть ли в получившемся сертификате CA:TRUE?

openssl x509 -in 192.168.100.100-cer.pem -noout -text

Да куда денется, конечно есть. Осталось перезапустить Traefik, чтобы он открыл новый энтрипоинт и переехал на 192.168.100.100. Так-то роутер, сервис и сертификат уже подтянулись в настройки. И следом перезапустите Registry. Скорее всего у него пока ещё 192.168.100.3 после первого запуска. Можно сделать это через Portainer. На всякий случай если что-то пойдёт не так:

sudo docker compose -f ~/traefik/docker-compose.yml -p traefik up -d

В Portainer перепишите URL своего локального custom registry туда, где ждёт traefik, на 192.168.100.100:5000. Напомню, это в Settings / Registries. Снова найдите и откройте ранее созданный образ в Images, переключите Registry на локальный, добавьте тег и попытайтесь запушить под этим новым тегом. Что-то вроде 192.168.100.100:5000/reg:latest. Теперь пишет "certificate signed by unknown authority". То есть кто-то нашёл сертификат для IP 192.168.100.100 и подставил его в TLS сессию. Кто бы это мог быть?

Осталось заставить Portainer уважать наш знаменитый в узких кругах CA. Давайте сделаем это через Docker.

sudo mkdir /etc/docker/certs.d/192.168.100.100:5000
sudo ln -s $PWD/192.168.100.100-cer.pem /etc/docker/certs.d/192.168.100.100:5000/ca.crt

Уже работает. Можете проверить, запушив наконец образ. Вот так Traefik оказался HTTPS представителем HTTP сервера.

На этом всё. Когда закончите и отполируете настройку своего Traefik, не забудьте log level поменять на WARNING или INFO, энтрипоинты на :80 и :443 или может какие другие, маппинг логов на хосте подальше в /var/log/traefik, подпишитесь на мой телеграм канал с курсами успешного успеха. Его пока нет, но это, надеюсь, не должно помешать...

– Постой, а как же сертификаты Letsencrypt?
– А уже всё настроено. Почти. Актуальный email впишите резолверу myresolver в файле traefik.yml и будет полностью настроено. Те роутеры с tls=true, которые будут использовать этот резолвер, должны указать в tls.domains.main, какое доменное имя их интересует. При желании и все альтернативные имена в tls.domains.sans. И тогда для всех страждущих, обратившихся к myresolver, будут запрошены сертификаты. В пределах лимита запросов к acme-v02.api.letsencrypt.org. Пример label конфигурации в файле docker-compose.yml ниже коммента # SANs, пример динамической конфигурации в файле registry.yml как единственный коммент. Официальная документация по сертификатам: https://doc.traefik.io/traefik/https/acme/

Теги:
Хабы:
Всего голосов 3: ↑3 и ↓0+3
Комментарии14

Публикации

Истории

Работа

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань