Привет всем! В данной статье мы осветим наш опыт внедрения в платформу Gitorion собственного приватного реестра Docker-образов на базе CNCF Distribution Registry. Рассмотрим настройку аутентификации Docker-registry в Keycloak и подключение Web-интерфейса к Docker-registry.
Назначение и основные функции приватного реестра Docker-образов
Приватный реестр Docker-образов является подсистемой непрерывной доставки Continuous Delivery платформы Gitorion.
Пайплайны Jenkins билдят Docker-образы микросервисов и push-ат их в приватный реестр Docker-registry. Ниже для наглядности приведем команду сборки микросервиса backend:
docker buildx build --push -t registry.gitorion.ru/owneruser/backend/main:5c83f123556419654beb22eda68d1478c7d13825 --cache-to 'type=registry,ref=registry.gitorion.ru/owneruser/backend/main:latest,mode=min' --cache-from 'type=registry,ref=registry.gitorion.ru/owneruser/backend/main:latest' --cache-from 'type=registry,ref=registry.gitorion.ru/php:8.3.1-fpm-alpine3.19' .
после сборки, Docker-образ микросервиса бэкенда:
registry.gitorion.ru/owneruser/backend/main:5c83f123556419654beb22eda68d1478c7d13825
push-ится в частный приватный реестр Docker-образов https://registry.gitorion.ru
Чтобы ускорить процесс сборки, в качестве Docker-кэша используются Docker-образы, которые так же pull-ятся из частного приватного репозитория Docker-образов:
--cache-from 'type=registry,ref=registry.gitorion.ru/owneruser/backend/main:latest'
--cache-from 'type=registry,ref=registry.gitorion.ru/php:8.3.1-fpm-alpine3.19'
На этапе деплоя Kubernetes pull-ит Docker-образы микросервисов из приватного реестра Docker-образов и использует для запуска контейнеров микросервисов в кластере Kubernetes:
apiVersion: v1
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
meta.helm.sh/release-name: staging-owneruser-backend
meta.helm.sh/release-namespace: staging
labels:
app: staging-owneruser-backend
app.kubernetes.io/managed-by: Helm
name: staging-owneruser-backend
namespace: staging
spec:
replicas: 1
selector:
matchLabels:
app: staging-owneruser-backend
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: staging-owneruser-backend
spec:
containers:
- image: registry.gitorion.ru/owneruser/backend/main:5c83f123556419654beb22eda68d1478c7d13825
imagePullPolicy: IfNotPresent
name: backend
Также собственный приватный реестр Docker-образов позволяет обойти проблему с лимитом на количество скачиваний Docker-образов из публичного реестра. Публичные реестры имеют лимит на количество скачиваний с одного IP. При интенсивной работе большой команды программистов, часто запускающих пайплайны, скачивающие базовые образы из публичного реестра, можно вскоре получить ошибку, которая остановит всю работу:
ERROR: failed to solve: alpine:3.19.1: failed to resolve source metadata for docker.io/library/alpine:3.19.1: failed to copy: httpReadSeeker: failed open: unexpected status code https://registry-1.docker.io/v2/library/alpine/manifests/sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b: 429 Too Many Requests - Server message: toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit
Поэтому, мы скачиваем один раз базовые образы микросервисов из публичного реестра и помещаем их в собственный приватный реестр Docker-образов:
docker login registry.gitorion.ru
docker pull php:8.3.1-fpm-alpine3.19
docker tag php:8.3.1-fpm-alpine3.19 registry.gitorion.ru/php:8.3.1-fpm-alpine3.19
docker push registry.gitorion.ru/php:8.3.1-fpm-alpine3.19
И в дальнейшем пайплайны сборки Docker-образов используют базовые образы из частного приватного реестра Docker-образов. Пример базового слоя в Dockerfile микросервиса бэкенда:
FROM registry.gitorion.ru/php:8.3.1-fpm-alpine3.19
Кроме того, Docker-образы из приватного репозитория используются как Docker-кэш для ускорения сборки в команде билдинга Docker-образа docker buildx build
--cache-from 'type=registry,ref=registry.gitorion.ru/php:8.3.1-fpm-alpine3.19'
Агент Jenkins при запуске каждого пайплайна скачивает Docker-образ buildkit, что тоже может привести к блокировке из-за лимита на скачивание из публичного репозитория. Поэтому мы скачали Docker-образ buildkit из публичного реестра, поместили его в приватный реестр платформы, и пайплайны используют Docker-образ buildkit из приватного реестра:
docker buildx create --name container '--driver=docker-container' --config /etс/buildkitd.toml --driver-opt 'image=registry.gitorion.ru/buildkit:buildx-stable-1' --use container
Указанный выше подход позволяет вести разработку на платформе автономно без регулярного скачивания Docker-образов из публичного реестра при запуске каждого пайплайна.
Установка и настройка Docker-registry
Ниже мы приведем спецификацию контейнера в yaml манифесте и поясним ключевые настройки аутентификации Docker-registry в Keycloak. Как получить значения параметров из Keycloak мы поясним в следующем пункте чуть ниже.
containers:
- image: registry:2
name: docker-registry
env:
- name: REGISTRY_AUTH
value: "token"
- name: REGISTRY_AUTH_TOKEN_REALM
value: "https://auth.gitorion.ru/realms/gitorion/protocol/docker-v2/auth"
- name: REGISTRY_AUTH_TOKEN_SERVICE
value: "docker-registry"
- name: REGISTRY_AUTH_TOKEN_ISSUER
value: "https://auth.gitorion.ru/realms/gitorion"
- name: REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE
value: "/opt/certs/localhost_trust_chain.pem"
- name: REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin
value: "[https://registry.gitorion.ru]"
- name: REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods
value: "[HEAD,GET,OPTIONS,DELETE]"
- name: REGISTRY_HTTP_HEADERS_Access-Control-Allow-Credentials
value: "[true]"
- name: REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers
value: "[Authorization,Accept,Cache-Control]"
- name: REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers
value: "[Docker-Content-Digest]"
- name: REGISTRY_STORAGE_DELETE_ENABLED
value: "true"
https://auth.gitorion.ru - URL, по которому доступен Keycloak в платформе;
image: registry:2 - используйте официальный образ Docker-registry;
REGISTRY_AUTH - выберите тип аутентификации в Docker-registry по токену из Keycloak;
REGISTRY_AUTH_TOKEN_REALM - задайте путь к области Keycloak, в которой находится Client для Docker-registry;
REGISTRY_AUTH_TOKEN_SERVICE - задайте имя клиента Docker-registry;
REGISTRY_AUTH_TOKEN_ISSUER - задайте источник, выдающий токены;
REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE - подключите SSL-сертификат доступа к области Keycloak, в которой находится Client для Docker-registry. Как получить сертификат области из Keycloak объяснено в следующем пункте ниже;
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin — при подключении аутентификации типа token, Docker-registry активирует механизм безопасности единого источника CORS и разрешает доступ к частному реестру только из своего же домена.
Настройка аутентификации Docker-registry в Keycloak
Чтобы не дублироваться, мы не будем в данной статье останавливаться на создании пользователей, групп и ролей, которые освещены в статье о внедрении в платформу Gitorion единой системы аутентификации SSO на базе Keycloak. Здесь мы рассмотрим только нюансы подключения Docker-registry к Keycloak.
По умолчанию Keycloak запускается без поддержки "docker-v2", и ее нужно включить, передав параметр KC_FEATURES в команде запуска Keycloak, либо через переменную окружения:
containers:
- env:
- name: KC_FEATURES
value: docker
image: docker.io/bitnami/keycloak:24.0.4-debian-12-r2
imagePullPolicy: IfNotPresent
В списке "Client Type" на вкладке создания клиента наряду с "OpenID Connect" и "SAML" появится "docker-v2".
Cоздайте Client для приватного реестра Docker-registry и задайте "docker-v2" в поле "Client Type". Введите имя клиента Docker-registry в поле "Client ID" такое же, как задали в переменной окружения REGISTRY_AUTH_TOKEN_SERVICE при настройке Docker-registry в предыдущем пункте выше, и нажмите кнопку Next.
В следующем окне вводить ничего не нужно, просто нажмите кнопку Next и затем кнопку Save.
Далее перейдите к настройкам только что созданного клиента docker-registry и в правом верхнем углу получите параметры настройки Docker-registry, нажав кнопку "Action" и выбрав пункт "Download adapter config".
Выберите пункт "Docker Compose YAML" и нажмите кнопку "Download".
В zip архиве в файле docker-compose.yaml вы найдете значения всех переменных окружения, которые нужно задать в предыдущем пункте настройки Docker-registry выше. В этом же архиве располагается файл certs/localhost_trust_chain.pem с SSL-сертификатом области, который нужно подключить к контейнеру docker-registry и задать в переменной окружения REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE.
Теперь можно залогиниться в наш частный репозиторий Docker-registry пользователем из Keycloak:
docker login registry.gitorion.ru
Authenticating with existing credentials...
Username (user): user
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
Еще в данном пункте хотим рассказать о неочевидной ошибке, сбивающей с толку, на диагностику и устранение которой может уйти много времени и сил. Даже при правильных настройках команда docker login может получить ошибку:
Cookie not found. Please make sure cookies are enabled in your browser.
а в логах Keycloak появится сообщение:
2024-05-18 18:27:25,916 WARN [org.keycloak.events] (executor-thread-1) type="CUSTOM_REQUIRED_ACTION_ERROR", realmId="1b5c5f6d-630a-4e5f-ad2e-a442eb1ca4d6", clientId="null", userId="null", ipAddress="10.10.7.143", error="cookie_not_found"
Такую ошибку может вызывать окно Keycloak, требующее от пользователя при первом входе сменить логин и пароль. Команда docker login ждет от Keycloak проверки логина и пароля и токен в ответ, чтобы поместить его в cookie. Вместо этого получает в ответ окно с предложением смены пароля и выдает данную ошибку.
Если администратор Keycloak отключит принудительную смену пароля при первом входе, то ошибку все еще может вызвать окно, предлагающее пользователю заполнить его персональные данные в профиле.
Общая рекомендация: перед первой попыткой залогиниться командой docker login, предварительно залогиньтесь в любой другой сервис платформы, имеющий Web-интерфейс, и убедитесь, что пользователь успешно аутентифицируется в Keycloak.
Подключение Web-интерфейса к Docker-registry
Docker-registry в стоке не имеет графического интерфейса пользователя. Можно конечно обращатьca к Docker-registry curl-ом, но это неудобно. Поэтому мы решили подключить Web-интерфейс от Joxit к Docker-registry.
Автор проекта приводит пример подключения своего Web-интерфейса к Docker-registry с аутентификацией по токену из Keycloak. Тестовый пример разработан для Docker-compose, но его можно использовать как отправную точку для подключения Web-интерфейса к Docker-registry в Kubernetes. Позади Ingress-nginx устанавливается промежуточный прокси на базе Nginx, который запрашивает у пользователя логин и пароль при доступе к Web-интерфейсу Docker-registry, передает их в Keycloak, получает токен доступа из Keycloak, и дальше Web-интерфейс использует токен при подключении к Docker-registry.
Пользователь вводит свой логин и пароль из Keycloak и получает доступ к Web-интерфейсу Docker-registry. На стартовой странице появится список всех репозиториев в приватном реестре Docker-образов.
Можно раскрыть один из репозиториев и получить список Tag-ов в нем и дополнительную информацию. Если потребуется, удалить ненужные Docker-образы.
Заключение
В данной статье мы осветили наш опыт внедрения собственного частного реестра Docker-образов на базе Docker-registry c аутентификацией в Keycloak и Web-интерфейсов от Joxit. Будем рады фидбеку, замечаниям и конструктивной критике. Не сочтите за труд и проголосуйте, пожалуйста, ниже. Спасибо за внимание!
GoooodBoy
А где будут данные хранится? На дисках очень дорого.
А как происходит очистка старых образов? Все хранить опять же дорого, да и не нужно
А как на счет HA? Сервис максимально критичный, поскольку если он встанет, то колом встанет все
Выглядит как лабораторная работа студента. Даже для небольшого проекта в прод я бы это не пустил.
gitorion Автор
Это платформа для разработки и тестирования. В production из нее деплоится только production контур и в нем можно сделать HA. Колом встанет не все, а только разработка. Удалить старые образы можно нажав пиктограмму Корзины в Web-интерфейсе. Платформа развернута к Kubernetes и там простор для типов подключаемых Persistent Volume. Можно подключить хоть NAS на Ceph
GoooodBoy
То есть, вы предлагаете для каждого стейджа свой регистри делать? А потом еще их синхронизировать между собой? Удаление руками, в WebUI! вы точно про продакшен рейди продукт говорите?
gitorion Автор
Регистри один, контуров 3 - development, staging и production. При необходимости для автоматизации очистки реестра можно добавить еще один пайплайн в Jenkins. Мы обязательно доработаем этот нюанс. Спасибо за ваш фидбек
GoooodBoy
Тогда почему регистри не HA? Ваш регистри упал, сломался, не справился с нагрузкой и все стейджы встали, ни сборку сделать, ни выкатить новую версию. Да еще и в проде под решил переехать на другую ноду, а там нет образа и ваш прибыльный сервис начал терять деньги.
Пока вы предлагает откровенно непригодное для прода решение.
gitorion Автор
Это резонно, но это тема как минимум для отдельной статьи, чтобы материал относящийся к Docker-registry не утонул в материале про HA) Ровно так же все встанет, если упадет Git-сервер или Jenkins или Keycloak. При подготовке следующей статьи как раз о горизонтальном масштабировании мы подумаем о HA.