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

Прозрачно кешируем несколько Container Registry в CRI-O и Podman

Время на прочтение13 мин
Количество просмотров20K

Возможно, вы уже активно используете CRI-O и Podman, а может только смотрите на альтернативы Docker с осторожностью. Но, как бы там ни было, альтернативные решения создают конкуренцию монополисту Docker и предлагают новые и востребованные улучшения. Одна из таких особенностей, это исключение Docker Hub как корневого и основного источника образов контейнеров. Таким образом, снимается привязка к поставщику и появляются новые возможности, а одной из таких мы и поговорим.

Это инструкция по выбору решения и настройке прозрачного кеширования множества реестров контейнеров для CRI-O, Podman, Buildah, Skopeo и прочих инструментов, работающих с образами контейнеров OCI и использующих общую конфигурацию containers/common.

Для чего нужен прокси?

Если у вас есть несколько экземпляров CRI-O или Podman, работающих в вашей инфраструктуре, например: кластер Kubernetes, сборочные агенты, физические или виртуальные машины на которых работают контейнеризированные приложения. Каждый такой узел при запуске контейнера обращается к реестру контейнеров и пытается скачать образ контейнера, если он остутствует локально в кеше или найдена новая версия. Мы развернем и настроим кеширующий прокси сервер, для получения образов контейнеров из быстрого локального источника, тем самым:

  • Обойдем ограничения на скачивание;

  • Увеличим скорость запуска контейнеров, единожды запрошеный контейнер осядет в кеш и все последующие запросы будут отработаны гораздо быстрее, особенно актуально для узких интернет каналов;

  • Увеличим доступность и стабильность. Аптайма 100% не существует, и в самый ответственный момент публичный реестр контейнеров может оказаться недоступным, по разным причинам, имя кеш мы с большей вероятностью, по-прежнему сможем запускать контейнеры, а в случае недоступности кеша контейнер будет получен на прямую;

  • Сэкономим трафик (если это еще кому-то актуально);

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

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

Проксирование будем настраивать для семи популярных реестров контейнеров:

  • docker.io

  • quay.io

  • gcr.io

  • k8s.gcr.io

  • ghcr.io

  • mcr.microsoft.com

  • registry.gitlab.com

Этот список может быть расширен любыми другими реестрами контейнеров, к примеру вы можете добавить ваш личный реестр или запроксировать только определенную группу (namespace, организацию).

Отличие использования зеркал

Docker - позволяет указать список registry-mirrors только для основного зеркала (library), это означает, что только Docker Hub будет проксироваться прозрачно, для прочих реестров контейнеров будет необходимо использовать измененный адрес контейнера proxy.host.tld/registry.host.tld/namespace/image:tag. Есть конечно обходной путь с использованием HTTPS_PROXY, но этот метод также не лишен недостатков.

CRI-O и Podman - предоставляет возможность настроить перечень реестров контейнеров и зеркал для каждого реестра персонально, чем мы непременно воспользуемся.

Конфигурация registries.conf

‎Все настройки мы будем выполнять в конфигурационных файлах из проекта containers/image. Здесь вы можете найти документацию к файлу registries.conf.

А еще, для большего удобства, вы можете хранить конфигурацию в отдельных файлах в каталоге /etc/containers/registries.conf.d/ или использовать персональные настройки в пользовательском каталоге $HOME/.config/containers/registries.conf

А это пример конфигурации реестров и их зеркал:

unqualified-search-registries = ["my-registry.tld"]

[[registry]]
prefix = "my-registry.tld/namespace"
location = "internal-registry.tld/project-one"

[[registry.mirror]]
location = "container-mirror.local/namespace"

[[registry.mirror]]
location = "container-mirror-2.local/mirrors/namespace"
insecure = true

Пример того как будет обработан запрос скачивания образа  namespace/image:tag

  1. Сначала будет попытка найти алиас [aliases] из registries.conf для образа namespace/image, пример имеющихся алиасов вы найдете в файле
    /etc/containers/registries.conf.d/000-shortnames.conf

  2. Далее будет попытка найти образ на предложенном из списка unqualified-search-registries узле, так как указан всего один узел, поиск произойдет автоматически на my-registry.tld

  3. Первая попытка получения образа произойдет по адресу
    container-mirror.local/namespace/image:tag

  4. Иначе, будет запрошен адрес
    container-mirror-2.local/mirrors/namespace/image:tag
    игнорируя проверку сертификата

  5. Иначе, обратимся в оригинальный источник
    internal-registry.tld/project-one/image:tag

Выбор решения

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

  • Собственная реализация - можно реализовать что-то своё взяв библиотеку distribution или сконфигурировать Nginx, Squid или другой подходящий прокси. Но мы не первопроходцы в этом деле, и можно обратить внимание на проект Docker Registry Proxy реализованный на Nginx или Docker Registry Cache основанный на Squid. Это самое гибкое и легковесное решение позволяющее проксировать любое количество репозиториев, а при помощи Lua или njs возможно реализовать почти любые идеи, но это потребует дополнительных компетенций и добавит сложности в обслуживании. Врят ли это то, что вы искали.

  • Distribution - стандартное решение для запуска реестра контейнеров с открытым исходным кодом, совместимо со спецификацией OCI, библиотека используется в реализации таких проектов как: Docker Hub, GitHub Container Registry, GitLab Container Registry, VMware Harbor Registry и прочих. Помимо хранения ваших контейнеров также поддерживает проксирование одного репозитория, для проксирования нескольких репозиториев, придется запустить по экземпляру приложения на каждый репозиторий.

  • Harbor - зрелое решение для запуска реестра контейнеров от VMware с открытым исходным кодом. Имеет web-интерфейс, ролевой контроль доступа, интеграцию c AD и OIDC, аудит, сканирование уязвимостей и прочие возможности. Позволяет создать прокси-репозиторий на каждый реестр контейнеров. GitHub.

  • Quay и Project Quay - коммерческий и свободно распространяемый реестр контейнеров от Red Hat. По функциональности схож с Harbor но имеет более гибкие возможности по разграничению прав доступа, основным отличием для нас является зеркалирование удаленного реестра контейнеров. Здесь это именно зеркалирование в отличии от прозрачного кеширования, что может оказаться избыточным и не подходящим при решении нашей задачи. ProjectQuay GitHub.

  • Sonatype Nexus - коммерческий и свободно распространяемый реестр артефактов от Sonatype. Это настоящий комбайн для хранения и проксирования артефактов для огромного множества поддерживаемых репозиториев, таких как: Nuget, NPM, Maven, APT, YUM, Conan, Docker и прочих. Как рестр образов контейнеров, Nexus уступает Quay и Harbor по обилию возможностей, но вполне успешно справляется с проксированием нескольких реестров и позволяет объединить их в группы. Прекрасно подойдет вам, если в вашем окружении выполняются сборочные пайплайны, вы сомжете собрать весь необходимый кеш артефактов через единый сервис. Nexus OSS GitHub.

  • JFrog Artifactory - коммерческое решение схожее по функциональности с Sonatype Nexus. Решения довольно похожи, можно посмотреть сравнение решений от Sonatype, и от JFrog. Также имеется бесплатная версия предназначеная только для хранения образов контейнеров и helm чартов.

Реализация класса "Стяжки и синяя изолента"

Изобретать свой велосипед мы с вами не будем, а пожалуй воспользуемся уже готовым Docker Registry Proxy, для демонстрации возможностей этого решения будет достаточно. Тем не менее, изучив образ контейнера Docker Registry Proxy, вы сможете реализовать своё решение под ваши нужды.

Запустим контейнер:

mkdir -p ./containers-registry-proxy/cache
mkdir -p ./containers-registry-proxy/certs

podman run --rm --detach --name containers-registry-proxy \
  --publish 0.0.0.0:3128:3128 \
  --env ENABLE_MANIFEST_CACHE=true \
  --env REGISTRIES="quay.io gcr.io k8s.gcr.io ghcr.io mcr.microsoft.com registry.gitlab.com" \
  --volume "$(pwd)/containers-registry-proxy/cache":/docker_mirror_cache \
  --volume "$(pwd)/containers-registry-proxy/certs":/ca \
  rpardini/docker-registry-proxy:0.6.4

Вот и всё, прокси запущен, в отличии от прочих решений, данный пример работает как HTTP прокси, и требует указания адреса прокси сервера в переменных HTTP_PROXY и HTTPS_PROXY. Для более тонкой настройки обратитесь к документации проекта.

⚠️ Будьте готовы встретить проблемы и необходимость искать ошибки и дорабатывать решение под свои потребности.

Оценка решения:

  • ? Минимальное потребление ресурсов;

  • ? Решение подходит для прозрачного проксирования образов для Docker;

  • ? Это только кешируйщий прокси, вы не можете публиковать здесь свои образы контейнеров;

  • ? Возможность кешировать другие типы репозиториев, но потребует доработки, дополнительных знаний и времени;

  • ? Отсутствует веб-интерфейс для администрирования;

  • ? Работает как HTTP прокси, при других реализациях требует подмены DNS или публикации нескольких экземпляров на разных портах;

  • ? Сложная отладка и кастомизация.

Реализация класса "Нативный инструмент"

Для реализации будем использовать немного сложный и при этом гибкий вариант, мы запустим несколько экземпляров Distribution. Воспользуемся официальным образом контейнера.

Давайте запустим наш первый прокси для Docker Hub:

mkdir -p ./containers-registry-proxy/docker.io

podman run --rm --detach --name registry \
  --publish 5000:5000 \
  --env REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/cache \
  --env REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
  --volume "$(pwd)/containers-registry-proxy/docker.io":/cache \
  registry:2

Добавим наше зеркало Docker Hub в конфигурационный файл registries.conf

[[registry]]
prefix = "docker.io"

[[registry.mirror]]
prefix = "docker.io"
location = "0.0.0.0:5000"
insecure = true

И проверим, что все работает:

podman --log-level debug pull docker.io/alpine

В отладочных сообщениях мы увидим, что все запросы ушли на http://0.0.0.0:5000, а в ниже приведенных каталогах, появились данные:

  • ./containers-registry-proxy/docker.io/docker/registry/v2/repositories/library/

  • ./containers-registry-proxy/docker.io/docker/registry/v2/blobs/sha256/

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

Для реализации нашей задачи по прозрачному проксированию нескольких реестров контейнеров, всю рутину по созданию и запуску множества экземпляров Distribution и созданию конфигурационных файлов, мы можем автоматизировать, к примеру при помощи bash скрипта.

Скрипт для запуска нескольких копий Distribution

В скрипте также используется запуск Redis для кеширования метаданных и более быстрого доступа к ним.

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

Пример конфигурации registries.conf полученный из скрипта
unqualified-search-registries = [
  "docker.io",
  "quay.io",
  "gcr.io",
  "k8s.gcr.io",
  "ghcr.io",
  "mcr.microsoft.com",
  "registry.gitlab.com",
]

[[registry]]
prefix = "docker.io"
[[registry.mirror]]
prefix = "docker.io"
location = "0.0.0.0:5000"
insecure = true

[[registry]]
prefix = "quay.io"
[[registry.mirror]]
prefix = "quay.io"
location = "0.0.0.0:5001"
insecure = true

[[registry]]
prefix = "gcr.io"
[[registry.mirror]]
prefix = "gcr.io"
location = "0.0.0.0:5002"
insecure = true

[[registry]]
prefix = "k8s.gcr.io"
[[registry.mirror]]
prefix = "k8s.gcr.io"
location = "0.0.0.0:5003"
insecure = true

[[registry]]
prefix = "ghcr.io"
[[registry.mirror]]
prefix = "ghcr.io"
location = "0.0.0.0:5004"
insecure = true

[[registry]]
prefix = "mcr.microsoft.com"
[[registry.mirror]]
prefix = "mcr.microsoft.com"
location = "0.0.0.0:5005"
insecure = true

[[registry]]
prefix = "registry.gitlab.com"
[[registry.mirror]]
prefix = "registry.gitlab.com"
location = "0.0.0.0:5006"
insecure = true

Оценка решения:

  • ? Нативное решение;

  • ? Минимальное потребление ресурсов;

  • ? Вы можете публиковать сюда свои образы контейнеров запустив еще один экземпляр Distribution;

  • ? Горизонтально и вертикально масштабируется;

  • ? Сравнительно более сложное в обслуживании решение;

  • ? Отсутствует веб-интерфейс для администрирования (возможное решение).

Реализация класса "Коробочное решение"

Для реализации будем использовать Harbor. Минимально для запуска потребуется выделить 2 CPU и 4 Gb ОЗУ, для нормальной работы необходимо вдвое больше ресурсов. Установку выполним при помощи официального инсталятора и выполним настройку согласно документации.

Выполнив базовую настройку, перейдем в интерфейс и на каждый проксируемй репозиторий создадим по одноименному проекту в соответствии с примером из документации.

Теперь можем добавить конфигурацию в файл registries.conf, где каждому репозиторию указан в качестве зеркала отдельный проект в рамках Harbor.

Пример конфигурации registries.conf
[[registry]]
prefix = "docker.io"
[[registry.mirror]]
prefix = "docker.io"
location = "harbor.host.tld/docker"

[[registry]]
prefix = "quay.io"
[[registry.mirror]]
prefix = "quay.io"
location = "harbor.host.tld/quay"

[[registry]]
prefix = "gcr.io"
[[registry.mirror]]
prefix = "gcr.io"
location = "harbor.host.tld/gcr"

[[registry]]
prefix = "k8s.gcr.io"
[[registry.mirror]]
prefix = "k8s.gcr.io"
location = "harbor.host.tld/k8s-gcr"

[[registry]]
prefix = "ghcr.io"
[[registry.mirror]]
prefix = "ghcr.io"
location = "harbor.host.tld/ghcr"

[[registry]]
prefix = "mcr.microsoft.com"
[[registry.mirror]]
prefix = "mcr.microsoft.com"
location = "harbor.host.tld/mcr"

[[registry]]
prefix = "registry.gitlab.com"
[[registry.mirror]]
prefix = "registry.gitlab.com"
location = "harbor.host.tld/gitlab"

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

Оценка примера:

  • ? Вы можете публиковать сюда свои образы контейнеров;

  • ? Наличие веб-интерфейса для администрирования и управления;

  • ? Имеет сканирование безопасности и ролевые политики;

  • ? Умеренное потребление ресурсов;

  • ? Избыточность для решения задачи кеширующего прокси.

Реализация класса "Звёздный разрушитель типа Венатор"

Рассмотрим пример запуска Sonatype Nexus OSS, бесплатной версии решения. Для коммерческой версии настройки будут такими же, а для JFrog Artifactory настройка будет похожей и описана в официальной документации.

Установим Nexus согласно официальной документации, для запуска нам понадобится выделить 4 CPU и 8 Gb ОЗУ, но при активном использовании готовтесь отдать около 32 Gb ОЗУ.

Завершив установку, перейдем в интерфейс администратора и выполним настройку проксирования репозиториев, в отличии от Harbor, где мы публиковали зеркала на разных маршрутах, здесь как в Distribution, каждый репозиторий может быть опубликован на собственном порту. Но для удобства существует возможность объединения нескольких репозиториев в группы.

Для каждого реестра контейнеров создадим по новому репозиторию формата docker (proxy) в соответствии с документацией.

Пример создания docker (proxy) репозитория в Nexus для ghcr.io
Пример создания docker (proxy) репозитория в Nexus для ghcr.io

Для разнообразия конфигурирования, создадим новый репозиторий с типом docker (group) и объеденим все наши прокси в общую группу:

Пример групировки docker (proxy) репозиториев в общую групповой репозиторий docker (group)
Пример групировки docker (proxy) репозиториев в общую групповой репозиторий docker (group)

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

Пример конфигурации registries.conf
[[registry]]
prefix = "docker.io"
[[registry.mirror]]
prefix = "docker.io"
location = "nexus.host.tld:8082"

[[registry]]
prefix = "quay.io"
[[registry.mirror]]
prefix = "quay.io"
location = "nexus.host.tld:8082"

[[registry]]
prefix = "gcr.io"
[[registry.mirror]]
prefix = "gcr.io"
location = "nexus.host.tld:8082"

[[registry]]
prefix = "k8s.gcr.io"
[[registry.mirror]]
prefix = "k8s.gcr.io"
location = "nexus.host.tld:8082"

[[registry]]
prefix = "ghcr.io"
[[registry.mirror]]
prefix = "ghcr.io"
location = "nexus.host.tld:8082"

[[registry]]
prefix = "mcr.microsoft.com"
[[registry.mirror]]
prefix = "mcr.microsoft.com"
location = "nexus.host.tld:8082"

[[registry]]
prefix = "registry.gitlab.com"
[[registry.mirror]]
prefix = "registry.gitlab.com"
location = "nexus.host.tld:8082"

Оценка примера:

  • ? Вы можете публиковать сюда свои образы контейнеров

  • ? Наличие веб-интерфейса для администрирования и управления

  • ? Возможность проксировать другие типы репозиториев

  • ? Имеет сканирование безопасности и ролевые политики

  • ? Избыточность для решения задачи кеширующего прокси образов контейнеров

  • ? Возможно потребует денег

  • ? Сравнительно большое потребление ресурсов

  • ? По неопытности можно вызвать коллизи с контейнерами для групповых репозиториев.

Отладка

Для отладки на стороне клиента запросим образ с уровнем логирования debug:

podman --log-level debug pull docker.io/alpine:3.12

А вот и пример проблемы отсутсвующего TLS:

DEBU[0000] Trying to access "0.0.0.0:3128/library/alpine:3.12" 
DEBU[0000] No credentials for 0.0.0.0:3128 found        
DEBU[0000] Using registries.d directory /etc/containers/registries.d for sigstore configuration 
DEBU[0000]  Using "default-docker" configuration        
DEBU[0000]  No signature storage configuration found for 0.0.0.0:3128/library/alpine:3.12, using built-in default file:///home/woozymasta/.local/share/containers/sigstore 
DEBU[0000] Looking for TLS certificates and private keys in /etc/docker/certs.d/0.0.0.0:3128 
DEBU[0000] GET https://0.0.0.0:3128/v2/                 
DEBU[0000] Ping https://0.0.0.0:3128/v2/ err Get "https://0.0.0.0:3128/v2/": http: server gave HTTP response to HTTPS client (&url.Error{Op:"Get", URL:"https://0.0.0.0:3128/v2/", Err:(*errors.errorString)(0xc000422e60)}) 
DEBU[0000] GET https://0.0.0.0:3128/v1/_ping            
DEBU[0000] Ping https://0.0.0.0:3128/v1/_ping err Get "https://0.0.0.0:3128/v1/_ping": http: server gave HTTP response to HTTPS client (&url.Error{Op:"Get", URL:"https://0.0.0.0:3128/v1/_ping", Err:(*errors.errorString)(0xc000423050)}) 
DEBU[0000] Accessing "0.0.0.0:3128/library/alpine:3.12" failed: error pinging docker registry 0.0.0.0:3128: Get "https://0.0.0.0:3128/v2/": http: server gave HTTP response to HTTPS client 
DEBU[0000] Trying to access "docker.io/library/alpine:3.12" 

Рекомендации

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

  • ghcr.io/woozymasta/archimate-ci:4.9.1-1.0.2

  • quay.io/woozymasta/archimate-ci:4.9.1-1.0.2

  • docker.io/woozymasta/archimate-ci:4.9.1-1.0.2

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

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

Старайтесь выполнять анализ уязвимостей для хранимых у вас образов в ваших реестрах контейнеров, будь это прокси или собственные репозитории. Рекомендую обратить внимание на утилиту Clair которая без проблем интегрируется в Harbor и Quay, и при небольшом усили в Distribution.

Выводы

Какое из решений использовать?

На практике для проксирования образов контейнеров, я использую все из перечисленных решений кроме самоделок на nginx, всё зависит от контекста. К примеру:

  • При развертывании On-Premise серверов предназначенных для запуска контейнеризированных приложений и кластеров Kubernetes, я использую Distribution размещенный на специально отведенный под это узел или в кластере Kubernetes, за частую используя Minio как хранилище.

  • Harbor или Project Quay подходит для небольших команд, где планируется разработка и построение CI пайплайнов. Также это может быть встроенный реестр контейнеров GitLab который также позволяет проксировать образы. Тут всё зависит от конкретной ситуации.

  • Для больших команд разработки при построении процессов CI и CD, зачастую использую Sonatype Nexus OSS как прокси для всего, что только можно кешировать. Но стараюсь не использовать как целевое хранилище контейнеров, вопреки: довольно слабой ролевой модели, отсутсвии OIDC в OSS версии и уступающему удобству и функцоналу в сравнении с Harbor или Project Quay.


На этом всё

Благодарю за ваше время и внимание! Стабильных и высокодоступных окружений вам.

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

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

Публикации

Истории

Работа

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

19 сентября
CDI Conf 2024
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн