Русский
Русский
English
Статистика
Реклама

Docker registry

Проблема умной очистки образов контейнеров и её решение в werf

06.10.2020 10:05:04 | Автор: admin


В статье рассмотрена проблематика очистки образов, которые накапливаются в реестрах контейнеров (Docker Registry и его аналогах) в реалиях современных CI/CD-пайплайнов для cloud native-приложений, доставляемых в Kubernetes. Приведены основные критерии актуальности образов и вытекающие из них сложности при автоматизации очистки, сохранения места и удовлетворения потребностям команд. Наконец, на примере конкретного Open Source-проекта мы расскажем, как эти сложности можно преодолеть.

Введение


Количество образов в реестре контейнеров может стремительно расти, занимая больше места в хранилище и, соответственно, значительно увеличивая его стоимость. Для контроля, ограничения либо поддержания приемлемого роста места, занимаемого в registry, принято:

  1. использовать фиксированное количество тегов для образов;
  2. каким-либо образом очищать образы.

Первое ограничение иногда допустимо для небольших команд. Если разработчикам хватает постоянных тегов (latest, main, test, boris и т.п.), реестр не будет раздуваться в размерах и долгое время можно вообще не думать об очистке. Ведь все неактуальные образы перетираются, а для очистки просто не остаётся работы (всё делается штатным сборщиком мусора).

Тем не менее, такой подход сильно ограничивает разработку и редко применим к CI/CD современных проектов. Неотъемлемой частью разработки стала автоматизация, которая позволяет гораздо быстрее тестировать, развертывать и доставлять новый функционал пользователям. Например, у нас во всех проектах при каждом коммите автоматически создается CI-пайплайн. В нём собирается образ, тестируется, выкатывается в различные Kubernetes-контуры для отладки и оставшихся проверок, а если всё хорошо изменения доходят до конечного пользователя. И это давно не rocket science, а обыденность для многих скорее всего и для вас, раз вы читаете данную статью.

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

Но как вообще определить, актуален ли образ?

Критерии актуальности образа


В подавляющем большинстве случаев основные критерии будут таковы:

1. Первый (самый очевидный и самый критичный из всех) это образы, которые в настоящий момент используются в Kubernetes. Удаление этих образов может привести к серьезным издержкам в связи с простоем production (например, образы могут потребоваться при репликации) или свести на нет усилия команды, которая занимается отладкой на каком-либо из контуров. (По этой причине мы даже сделали специальный Prometheus exporter, отслеживающий отсутствие таких образов в любом Kubernetes-кластере.)

2. Второй (менее очевиден, но тоже очень важен и снова относится к эксплуатации) образы, которые требуются для отката в случае выявления серьёзных проблем в текущей версии. Например, в случае с Helm это образы, которые используются в сохраненных версиях релиза. (К слову, по умолчанию в Helm лимит в 256 ревизий, но вряд ли у кого-то реально есть потребность в сохранении такого большого количества версий?..) Ведь мы, в частности, для того и храним версии, чтобы можно было их потом использовать, т.е. откатываться на них в случае необходимости.

3. Третий потребности разработчиков: все образы, которые связаны с их текущими работами. К примеру, если мы рассматриваем PR, то имеет смысл оставлять образ, соответствующий последнему коммиту и, скажем, предыдущему коммиту: так разработчик сможет оперативно возвращаться к любой задаче и работать с последними изменениями.

4. Четвертый образы, которые соответствуют версиям нашего приложения, т.е. являются конечным продуктом: v1.0.0, 20.04.01, sierra и т.д.

NB: Определенные здесь критерии были сформулированы на основе опыта взаимодействия с десятками команд разработчиков из разных компаний. Однако, конечно, в зависимости от особенностей в процессах разработки и используемой инфраструктуры (например, не используется Kubernetes), эти критерии могут отличаться.

Соответствие критериям и существующие решения


Популярные сервисы с container registry, как правило, предлагают свои политики очистки образов: в них вы можете определять условия, при которых тег удаляется из registry. Однако возможности этих условий ограничиваются такими параметрами, как имена, время создания и количество тегов*.

* Зависит от конкретных реализаций container registry. Мы рассматривали возможности следующих решений: Azure CR, Docker Hub, ECR, GCR, GitHub Packages, GitLab Container Registry, Harbor Registry, JFrog Artifactory, Quay.io по состоянию на сентябрь'2020.

Такого набора параметров вполне достаточно, чтобы удовлетворить четвёртому критерию то есть отбирать образы, соответствующие версиям. Однако для всех остальных критериев приходится выбирать какое-то компромиссное решение (более жесткую или, наоборот, щадящую политику) в зависимости от ожиданий и финансовых возможностей.

Например, третий критерий связанный с потребностями разработчиков может решаться за счет организации процессов внутри команд: специфичного именования образов, ведения специальных allow lists и внутренних договоренностей. Но в конечном счете его всё равно необходимо автоматизировать. А если возможностей готовых решений не хватает, приходится делать что-то своё.

Аналогична и ситуация с двумя первыми критериями: их невозможно удовлетворить без получения данных от внешней системы той самой, где происходит развёртывание приложений (в нашем случае это Kubernetes).

Иллюстрация workflow в Git


Предположим, вы работаете примерно по такой схеме в Git:



Иконкой с головой на схеме отмечены образы контейнеров, которые в настоящий момент развёрнуты в Kubernetes для каких-либо пользователей (конечных пользователей, тестировщиков, менеджеров и т.д.) или используются разработчиками для отладки и подобных целей.

Что произойдёт, если политики очистки позволяют оставлять (не удалять) образы только по заданным названиям тегов?



Очевидно, такой сценарий никого не обрадует.

Что изменится, если политики позволяют не удалять образы по заданному временному интервалу / числу последних коммитов?



Результат стал значительно лучше, однако всё ещё далёк от идеала. Ведь у нас по-прежнему есть разработчики, которым нужны образы в реестре (или даже развёрнутые в K8s) для отладки багов

Резюмируя сложившуюся на рынке ситуацию: доступные в реестрах контейнеров функции не предлагают достаточной гибкости при очистке, а главная тому причина нет возможности взаимодействовать с внешним миром. Получается, что команды, которым требуется такая гибкость, вынуждены самостоятельно реализовывать удаление образов снаружи, используя Docker Registry API (или нативный API соответствующей реализации).

Однако мы искали универсальное решение, которое автоматизировало бы очистку образов для разных команд, использующих разные реестры

Наш путь к универсальной очистке образов


Откуда такая потребность? Дело в том, что мы не отдельно взятая группа разработчиков, а команда, которая обслуживает сразу множество таковых, помогая комплексно решать вопросы CI/CD. И главный технический инструмент для этого Open Source-утилита werf. Её особенность в том, что она не выполняет единственную функцию, а сопровождает процессы непрерывной доставки на всех этапах: от сборки до деплоя.

Публикация в реестре* образов (сразу после их сборки) очевидная функция такой утилиты. А раз образы туда помещаются на хранение, то если ваше хранилище не безгранично нужно отвечать и за их последующую очистку. О том, как мы добились успеха в этом, удовлетворяя всем заданным критериям, и будет рассказано далее.

* Хоть сами реестры могут быть различными (Docker Registry, GitLab Container Registry, Harbor и т.д.), их пользователи сталкиваются с одними и теми же проблемами. Универсальное решение в нашем случае не зависит от реализации реестра, т.к. выполняется вне самих реестров и предлагает одинаковое поведение для всех.

Несмотря на то, что мы используем werf как пример реализации, надеемся, что использованные подходы будут полезны и другим командам, столкнувшимся с аналогичными сложностями.

Итак, мы занялись внешней реализацией механизма для очистки образов вместо тех возможностей, что уже встроены в реестры для контейнеров. Первым шагом стало использование Docker Registry API для создания всё тех же примитивных политик по количеству тегов и времени их создания (упомянутых выше). К ним был добавлен allow list на основе образов, используемых в развёрнутой инфраструктуре, т.е. Kubernetes. Для последнего было достаточно через Kubernetes API перебирать все задеплоенные ресурсы и получать список из значений image.

Такое тривиальное решение закрыло самую критичную проблему (критерий 1), но стало только началом нашего пути по улучшению механизма очистки. Следующим и куда более интересным шагом стало решение связать публикуемые образы с историей Git.

Схемы тегирования


Для начала мы выбрали подход, при котором конечный образ должен хранить необходимую информацию для очистки, и выстроили процесс на схемах тегирования. При публикации образа пользователь выбирал определённую опцию тегирования (git-branch, git-commit или git-tag) и использовал соответствующее значение. В CI-системах установка этих значений выполнялась автоматически на основании переменных окружения. По сути конечный образ связывался с определённым Git-примитивом, храня необходимые данные для очистки в лейблах.

В рамках такого подхода получился набор политик, который позволял использовать Git как единственный источник правды:

  • При удалении ветки/тега в Git автоматически удалялись и связанные образы в registry.
  • Количество образов, связанное с Git-тегами и коммитами, можно было регулировать количеством тегов, использованных в выбранной схеме, и временем создания связанного коммита.

В целом, получившаяся реализация удовлетворяла нашим потребностям, но вскоре нас ожидал новый вызов. Дело в том, что за время использования схем тегирования по Git-примитивам мы столкнулись с рядом недостатков. (Поскольку их описание выходит за рамки темы этой статьи, все желающие могут ознакомиться с подробностями здесь.) Поэтому, приняв решение о переходе на более эффективный подход к тегированию (content-based tagging), нам пришлось пересмотреть и реализацию очистки образов.

Новый алгоритм


Почему? При тегировании в рамках content-based каждый тег может удовлетворять множеству коммитов в Git. При очистке образов больше нельзя исходить только из коммита, на котором новый тег был добавлен в реестр.

Для нового алгоритма очистки было решено уйти от схем тегирования и выстроить процесс на мета-образах, каждый из которых хранит связку из:

  • коммита, на котором выполнялась публикация (при этом не имеет значения, добавился, изменился или остался прежним образ в реестре контейнеров);
  • и нашего внутреннего идентификатора, соответствующего собранному образу.

Другими словами, была обеспечена связь публикуемых тегов с коммитами в Git.

Итоговая конфигурация и общий алгоритм


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

  • множеством references, т.е. Git-тегами или Git-ветками, которые используются при сканировании;
  • илимитом искомых образов для каждого reference из множества.

Для иллюстрации вот как стала выглядеть конфигурация политик по умолчанию:

cleanup:  keepPolicies:  - references:      tag: /.*/      limit:        last: 10  - references:      branch: /.*/      limit:        last: 10        in: 168h        operator: And    imagesPerReference:      last: 2      in: 168h      operator: And  - references:        branch: /^(main|staging|production)$/    imagesPerReference:      last: 10

Такая конфигурация содержит три политики, которые соответствуют следующим правилам:

  1. Сохранять образ для 10 последних Git-тегов (по дате создания тега).
  2. Сохранять по не более 2 образов, опубликованных за последнюю неделю, для не более 10 веток с активностью за последнюю неделю.
  3. Сохранять по 10 образов для веток main, staging и production.

Итоговый же алгоритм сводится к следующим шагам:

  • Получение манифестов из container registry.
  • Исключение образов, используемых в Kubernetes, т.к. их мы уже предварительно отобрали, опросив K8s API.
  • Сканирование Git-истории и исключение образов по заданным политикам.
  • Удаление оставшихся образов.

Возвращаясь к нашей иллюстрации, вот что получается с werf:



Однако, даже если вы не используете werf, подобный подход к продвинутой очистке образов в той или иной реализации (в соответствии с предпочтительным подходом к тегированию образов) может быть применен и в других системах/утилитах. Для этого достаточно помнить о тех проблемах, которые возникают, и найти те возможности в вашем стеке, что позволяют встроить их решение наиболее гладко. Надеемся, что пройденный нами путь поможет посмотреть и на ваш частный случай с новыми деталями и мыслями.

Заключение


  • Рано или поздно с проблемой переполнения registry сталкивается большинство команд.
  • При поиске решений в первую очередь необходимо определить критерии актуальности образа.
  • Инструменты, предлагаемые популярными сервисами container registry, позволяют организовать очень простую очистку, которая не учитывает внешний мир: образы, используемые в Kubernetes, и особенности рабочих процессов в команде.
  • Гибкий и эффективный алгоритм должен иметь представление о CI/CD-процессах, оперировать не только данными Docker-образов.

P.S.


Читайте также в нашем блоге:

Подробнее..

Traefik, docker и docker registry

11.04.2021 16:04:19 | Автор: admin

Под катом вы увидите:

  1. Использования Traefik в качестве обратного прокси для маршрутизации трафика внутрь docker контейнеров.

  2. Использование Traefik для автоматического получения Lets Encrypt сертификатов

  3. Использование Traefik для разграничения доступа к docker registry при помощи basic auth

  4. Все перечисленное выше будет настраиваться исключительно внутри docker-compose.yml и не потребует передачи отдельных конфигурационных файлов внутрь контейнеров.

Актуальность вопроса

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

Помимо этого в интернете мало информации на тему использования traefik для контроля доступа к docker registry. Описанную ниже технику можно использовать для контроля доступа к любому приложению, реализующему Rest API.

Поиск решения

Вот ссылка на официальную статью по развертыванию docker registry. Крутим страницу вниз и видим пример развертывания через docker-compose. Я перепечатаю пример ниже:

registry:  restart: always  image: registry:2  ports:    - 5000:5000  environment:    REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt    REGISTRY_HTTP_TLS_KEY: /certs/domain.key    REGISTRY_AUTH: htpasswd    REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd    REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm  volumes:    - /path/data:/var/lib/registry    - /path/certs:/certs    - /path/auth:/auth

Нам предлагают терминировать https трафик прямо внутри сервиса registry, чего мы делать не будем. Мы не станем усложнять себе жизнь и копировать сертификаты внутрь сервиса. Кроме того у нас есть другие https сервисы, которым также нужны сертификаты, так что у нас уже есть единая точка входа, где происходит автоматическая генерация сертификатов для новых сервисов с помощью let's encrypt - это контейнер с traefik.

Также нам предлагают использовать базовую авторизацию для доступа к registry, однако не предлагают способа разделения прав доступа. То есть авторизованный пользователь будет иметь полный доступ к сервису, сможет читать и писать в любом репозитории. На сайте есть пример использования nginx, но и там мы видим единственного пользователя с полными правами.

В интернете мы находим идеальный пример использования nginx для авторизации и разделения прав доступа. Мы сделаем то же самое, но через traefik.

Итак, приступаем.

Базовая конфигурация Registry

Сперва запустим сервис registry через отдельный compose файл. Создадим новую папку registry", в которой создадим compose файл:

mkdir registrycd registrynano docker-compose.yml

Вставим в файл следующее содержимое и сохраним:

version: '2.4'services:  registry:    restart: always    image: registry:2    ports:      - 5000:5000

Запустим сервис

docker-compose up -d

Откроем в браузере страницу http://<IP>:5000/v2/_catalog , где <IP> - это ip адрес докер машины.
В ответ увидим страницу с текстом:

{"repositories":[]}

Значит, все работает. Если это не так - проверьте firewall.

Базовая конфигурация Traefik

Знакомство с traefik начнем с базовой конфигурации.
Позже мы добавим SSL, сжатие трафика и авторизацию с аутентификацией.

Сперва запустим сервис registry через отдельный compose файл. Создадим новую папку registry", в которой создадим compose файл:

mkdir traefikcd traefiknano docker-compose.yml

Вставим в файл следующее содержимое и сохраним:

version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"    ports:      - "8080:8080"
Разберем каждую новую строку (нажать)
command:- "--api.insecure=true"

Через command передаются параметры запуска нашего приложения.
Включаем доступ к dashboard в insecure режиме. Это означает, что dashboard будет доступен напрямую в точке входа с названием traefik. Если указанная точка входа traefik не настроена, она будет автоматически создана на порту 8080.

- "8080:8080"

Перенаправление с порта 8080 на docker машине в аналогичный порт контейнера traefik. Эта настройка также как и предыдущая необходима, чтобы попасть в dashboard traefik

Запускаем наш сервис:

docker-compose up -d

Открываем страницу с IP адресом докер машины и портом 8080:

Подключение Registry к Traefik (настройка домена)

Мы могли бы упростить себе задачу, объединив оба сервиса в одном compose файле, но мы не будем этого делать, чтобы показать более сложный сценарий использования.
В случае объединения нам не пришлось бы прописывать общие сети.

Новый compose для Traefik:

Version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"      - "--providers.docker=true"      - "--providers.docker.exposedbydefault=false"    ports:      - "80:80"      - "8080:8080"    networks:      - registry_default    volumes:      - "/var/run/docker.sock:/var/run/docker.sock:ro"networks:  registry_default:    external: true
Разберем каждую новую строку (нажать)
- "--providers.docker=true"

Включаем докер провайдер. После этого traefik будет следить за появлением специальных меток на других контейнерах и перенастраивать все согласно этим меткам.

- "--providers.docker.exposedbydefault=false"

Запрещаем автоматическое добавление HTTP сервисов и HTTP маршрутов в traefik. Если этого не сделать, то traefik опубликует все docker контейнеры, в которых есть expose порта наружу автоматически. В качестве доменного имени он будет использовать имя контейнера.

Таким образом, без этой строки мы неявно открываем публичный доступ ко всем своим контейнерам! Все, что нужно для атаки - угадать имя контейнера! Я проверил эту теорию на практике: после добавления в файл hosts строки IP_докер_машины имя_контейнера, страница http://имя_контейнера" открылась в браузере.

- "80:80"

Перенаправление стандартного веб порта 80 (http) на docker машине в аналогичный порт контейнера traefik. Это нужно для обработки и маршрутизации полезного трафика.

    networks:      - registry_defaultnetworks:  registry_default:    external: true

Подключение к коммутатору, обслуживающему контейнеры из другого compose файла. Дело в том, что для каждого compose файла создается виртуальный коммутатор с именем имя_родительской_папки_default, таким образом сервисы внутри одного compose файла могут легко друг с другом взаимодействовать.

volumes:  - "/var/run/docker.sock:/var/run/docker.sock:ro"

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

Новый compose для Registry:

version: '2.4'services:  registry:    restart: always    image: registry:2    ports:      - 5000:5000    labels:      - "traefik.enable=true"      - "traefik.http.routers.registry.rule=Host(`<REGISTRY.FQDN>`)"
Разберем каждую новую строку (нажать)
- "traefik.enable=true"

Эта метка оповещает traefik, что данный сервис нужно опубликовать

- "traefik.http.routers.registry.rule=Host(`<REGISTRY.FQDN>`)"

Эта метка указывает traefik, что доменное имя <REGISTRY.FQDN> необходимо связать с данным сервисом. Запросы, в заголовках которых будет указано данное имя, будут перенаправлены в текущий контейнер.

- "traefik.http.services.registry.loadbalancer.server.port=5000"

Мы также можем указать порт, на который будет перенаправлен трафик, но в данном случае это делать не обязательно. Если docker файл нашего сервиса открывает наружу всего лишь 1 порт, этот порт будет выбран автоматически.

Перезапустим оба наших сервиса:

docker-compose up -d

Откроем в браузере страницу http://<REGISTRY.FQDN>:5000/v2/_catalog , где <REGISTRY.FQDN> - это полное доменное имя нашего сервиса, описанное в метке compose файла.

В ответ увидим страницу в текстом:

{"repositories":[]}

Значит все работает.

Добавление SSL (настройка https)

Мы будем автоматически получать и продлять SSL сертификаты через Let's Encrypt.

Новый compose для Traefik:

version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"      - "--providers.docker=true"      - "--providers.docker.exposedbydefault=false"      - "--entrypoints.web.address=:80"      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"      - "--entrypoints.websecure.address=:443"      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"      - "--certificatesresolvers.myresolver.acme.email=<EMAIL>"      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"    ports:      - "80:80"      - "443:443"      - "8080:8080"    networks:      - registry_default    volumes:      - "letsencrypt:/letsencrypt"      - "/var/run/docker.sock:/var/run/docker.sock:ro"volumes:  letsencrypt:networks:  registry_default:    external: true
Разберем каждую новую строку (нажать)
- "--entrypoints.web.address=:80"

Меняем имя стандартного entrypoint с http на web для удобства.

- "--entrypoints.web.http.redirections.entryPoint.to=websecure"

Добавляем автоматическое перенаправление трафика с entrypoint web на websecure. Другими словами перенаправление с HTTP на HTTPS

- "--entrypoints.websecure.address=:443"

Создаем новый entrypoint на 443 порту с именем websecure

- "--certificatesresolvers.myresolver.acme.httpchallenge=true"

Настраиваем режим выдачи сертификатов Lets Encrypt через http challenge

- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"

Настраиваем entrypoint для http challenge

- "--certificatesresolvers.myresolver.acme.email=<EMAIL>"

Настраиваем <email> адрес для регистрации в центре сертификации

- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"

Меняем стандартное расположение файла acme.json. В этот файл будут записываться выданные сертификаты. Дело в том, что стандартное расположение файла /acme.json" в корне не позволяет хранить этот файл на подключенном томе.

- "443:443"

Перенаправление стандартного веб порта 443 (https) на docker машине в аналогичный порт контейнера traefik. Это нужно для обработки и маршрутизации полезного трафика.

volumes:- "letsencrypt:/letsencrypt"volumes:letsencrypt:

Подключаем именованный том для постоянного хранения SSL сертификатов. Теперь даже после пересоздания контейнера нам не придется заново получать все сертификаты.
Именованный том будет храниться здесь: /var/lib/docker/volumes/<имя тома>

Новый compose для Registry:

version: '2.4'services:  registry:    restart: always    image: registry:2    ports:      - 5000:5000    labels:      - "traefik.enable=true"      - "traefik.http.routers.registry.rule=Host(`<REGISTRY.FQDN>`)"      - "traefik.http.routers.registry.entrypoints=websecure"      - "traefik.http.routers.registry.tls.certresolver=myresolver"
Разберем каждую новую строку (нажать)
- "traefik.http.routers.registry.entrypoints=websecure"

Меняем entrypoint со стандартного http (web) на websecure

- "traefik.http.routers.registry.tls.certresolver=myresolver"

Задаем имя резолвера для работы SSL сертификатов

Перезапустим оба наших сервиса:

docker-compose up -d

Откроем в браузере страницу http://<REGISTRY.FQDN>:5000/v2/_catalog , где <REGISTRY.FQDN> - это полное доменное имя нашего сервиса, описанное в метке compose файла.

Мы увидим, что:

  • схема сменилась с http на https автоматически

  • соединение защищено сертификатом выданным Let's Encrypt

В случае проблем с получением сертификата, traefik будет использовать само-подписанный сертификат. Если это произошло, следует использовать команду docker logs traefik для просмотра логов.

Настройка домена и SSL для dashboard

Как ни странно, маршрутизация трафика во встроенный dashboard отличается от маршрутизации трафика во внешние сервисы, запущенные в докер контейнерах. Я потратил не мало времени, пытаясь использовать те же правила, что и раньше.

Новый compose для Traefik:

version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"      - "--providers.docker=true"      - "--providers.docker.exposedbydefault=false"      - "--entrypoints.web.address=:80"      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"      - "--entrypoints.websecure.address=:443"      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"      - "--certificatesresolvers.myresolver.acme.email=<EMAIL>"      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"    ports:      - "80:80"      - "443:443"      - "8080:8080"    networks:      - registry_default    volumes:      - "letsencrypt:/letsencrypt"      - "/var/run/docker.sock:/var/run/docker.sock:ro"    labels:      - "traefik.enable=true"      - "traefik.http.routers.traefik.rule=Host(`<TRAEFIK.FQDN>`)"      - "traefik.http.routers.traefik.entrypoints=websecure"      - "traefik.http.routers.traefik.tls.certresolver=myresolver"      - "traefik.http.routers.traefik.service=api@internal"volumes:  letsencrypt:networks:  registry_default:    external: true
Разберем каждую новую строку (нажать)
labels:  - "traefik.enable=true"  - "traefik.http.routers.traefik.rule=Host(`<TRAEFIK.FQDN>`)"  - "traefik.http.routers.traefik.entrypoints=websecure"  - "traefik.http.routers.traefik.tls.certresolver=myresolver"

Регистрируем роутер для направления трафика домена <traefik.fqdn> во внутренний dashboard.

- "traefik.http.routers.traefik.service=api@internal"

Явно указываем имя сервиса в который мы будем перенаправлять трафик.

api@internal - это зарезервированное имя сервиса. Перенаправление в dashboard не будет работать без этой строки.

Мы не указывали подобную строку в контейнере registry, так как регистрация имени сервиса и привязка роутера к сервису выполнялась автоматически.

Перезапустим traefik:

docker-compose up -d

Откроем в браузере страницу http://<TRAEFIK.FQDN>, где <TRAEFIK.FQDN> - это полное доменное имя нашего для доступа к traefik dashboard, описанное в метке compose файла.

Добавление сжатия трафика

Сжатие сильно ускоряет загрузку сайтов на клиенте. Обязательно нужно включать.

Новый compose для Traefik:

version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"      - "--providers.docker=true"      - "--providers.docker.exposedbydefault=false"      - "--entrypoints.web.address=:80"      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"      - "--entrypoints.websecure.address=:443"      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"      - "--certificatesresolvers.myresolver.acme.email=<email>"      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"    ports:      - "80:80"      - "443:443"      - "8080:8080"    networks:      - registry_default    volumes:      - "letsencrypt:/letsencrypt"      - "/var/run/docker.sock:/var/run/docker.sock:ro"    labels:      - "traefik.http.middlewares.traefik-compress.compress=true"      - "traefik.enable=true"      - "traefik.http.routers.traefik.rule=Host(`<TRAEFIK.FQDN>`)"      - "traefik.http.routers.traefik.entrypoints=websecure"      - "traefik.http.routers.traefik.tls.certresolver=myresolver"      - "traefik.http.routers.traefik.service=api@internal"      - "traefik.http.routers.traefik.middlewares=traefik-compress"volumes:  letsencrypt:networks:  registry_default:    external: true
Разберем каждую новую строку (нажать)
- "traefik.http.middlewares.traefik-compress.compress=true"

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

- "traefik.http.routers.traefik.middlewares=traefik-compress"

Добавляем middleware с именем traefik-compress в цепочку обработки трафика для сервиса traefik

Новый compose для Registry:

version: '2.4'services:  registry:    restart: always    image: registry:2    ports:      - 5000:5000    labels:      - "traefik.enable=true"      - "traefik.http.routers.registry.rule=Host(`<REGISTRY.FQDN>`)"      - "traefik.http.routers.registry.entrypoints=websecure"      - "traefik.http.routers.registry.tls.certresolver=myresolver"      - "traefik.http.routers.registry.middlewares=traefik-compress"
Разберем каждую новую строку (нажать)
- "traefik.http.routers.registry.middlewares=traefik-compress"

Добавляем middleware с именем traefik-compress в цепочку обработки трафика для сервиса registry

Добавление basic авторизации для доступа к Dashboard

Сначала мы собираемся сгенерировать комбинацию пользователя и пароля для базовой аутентификации с использованием htpasswd. Если он у вас не установлен, вам нужно сначала сделать это (например, для сервера Ubuntu):

apt-get install apache2-utils

Мы должны экранировать каждый символ $ в нашем зашифрованном пароле (заменить $ на $$), если мы используем пароль напрямую в docker-compose.yml

echo $(htpasswd -nbB USER "PASS") | sed -e s/\\$/\\$\\$/g

Пример вывода команды (результат будет разный при каждом запуске команды):

USER:$$2y$$05$$iPGcI0PwxkDoOZUlGPkIFe31e47F5vewcjlhzhgf0EHo45H.dFyKW

Вывод команды нужно поместить в наш docker-compose.yml внутрь traefik метки, заменив <USER-PASSWORD-OUTPUT> в примере ниже.

Новый compose для Registry:

version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"      - "--providers.docker=true"      - "--providers.docker.exposedbydefault=false"      - "--entrypoints.web.address=:80"      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"      - "--entrypoints.websecure.address=:443"      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"      - "--certificatesresolvers.myresolver.acme.email=<EMAIL>"      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"    ports:      - "80:80"      - "443:443"      - "8080:8080"    networks:      - registry_default    volumes:      - "letsencrypt:/letsencrypt"      - "/var/run/docker.sock:/var/run/docker.sock:ro"    labels:      - "traefik.http.middlewares.traefik-compress.compress=true"      - "traefik.http.middlewares.auth.basicauth.users=<USER-PASSWORD-OUTPUT>"      - "traefik.enable=true"      - "traefik.http.routers.traefik.rule=Host(`<TRAEFIK.FQDN>`)"      - "traefik.http.routers.traefik.entrypoints=websecure"      - "traefik.http.routers.traefik.tls.certresolver=myresolver"      - "traefik.http.routers.traefik.service=api@internal"      - "traefik.http.routers.traefik.middlewares=traefik-compress,auth"volumes:  letsencrypt:networks:  registry_default:    external: true
Разберем каждую новую строку (нажать)
- "traefik.http.middlewares.auth.basicauth.users=<USER-PASSWORD-OUTPUT>"

Регистрируем новый middleware с именем auth и функцией авторизации. Этот middleware мы затем сможем использовать в любом стороннем докер контейнере.

- "traefik.http.routers.traefik.middlewares=traefik-compress,auth"

Добавляем middleware с именем auth в цепочку обработки трафика для сервиса traefik

Внимание: Если вы используете переменные среды (например .env файл) в вашем docker-compose.yml вместо прямого указания <USER-PASSWORD-OUTPUT>, то вы не должны экранировать $. Генерация пароля в этом случае будет выглядеть так:

echo $(htpasswd -nbB <USER> "<PASS>")

После перезапуска docker контейнера (docker-compose up -d) мы увидим окно базовой авторизации, когда откроем dashboard traefik в браузере.

Разделение прав доступа пользователей Registry

Новый compose для Registry:

version: '2.4'services:  registry:    restart: always    image: registry:2    ports:      - 5000:5000    labels:      - "traefik.enable=true"      - "traefik.http.routers.registry.rule=Host(`REGISTRY.FQDN`) && Method(`POST`, `PUT`, `DELETE`, `PATCH`)"      - "traefik.http.routers.registry.entrypoints=websecure"      - "traefik.http.routers.registry.tls.certresolver=myresolver"      - "traefik.http.routers.registry.service=registry"      - "traefik.http.services.registry.loadbalancer.server.port=5000"      - "traefik.http.routers.registry.middlewares=auth-registry,traefik-compress"      - "traefik.http.middlewares.auth-registry.basicauth.users=<ADMIN-PASSWORD-OUTPUT>"      - "traefik.http.routers.guest-registry.rule=Host(`REGISTRY.FQDN`) && Method(`GET`, `HEAD`)"      - "traefik.http.routers.guest-registry.entrypoints=websecure"      - "traefik.http.routers.guest-registry.tls.certresolver=myresolver"      - "traefik.http.routers.guest-registry.service=guest-registry"      - "traefik.http.services.guest-registry.loadbalancer.server.port=5000"      - "traefik.http.routers.guest-registry.middlewares=aguest-registry,traefik-compress"      - "traefik.http.middlewares.aguest-registry.basicauth.users=<USER-PASSWORD-OUTPUT>"

Зарегистрируем на этом контейнере 2 набора роутеров и сервисов:

  • registry - роутер и сервис с таким именем будут предоставлять полный доступ (чтение\запись)

  • guest-registry - роутер и сервис с таким именем будут предоставлять гостевой доступ (чтение)

Так же мы создаем одноименные middleware для basic авторизации и добавляем их в роутеры.

Вы можете заметить, что мы добавили основного пользователя в оба набора правил. Это сделано потому, что наборы методов не пересекаются. Если сделать их пересекающимися - роутинг не будет работать как задумано.

Перезапустим registry:

docker-compose up -d
Проведем простую проверку с помощью Postman

Авторизовываемся пользователем с ограниченными правами.

Делаем Get запрос - работает.

Делаем Post запрос - 401.

Авторизовываемся пользователем с полными правами.

Делаем Get запрос - работает.

Делаем Post запрос - работает. Авторизация пройдена, но сам запрос отклоняется registry, так как не является допустимым. Мы не стали подбирать правильный запрос для экономии времени.

Заключение

На мой взгляд traefik гораздо удобнее классического nginx, если мы живем внутри docker контейнеров.

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru