Привет всем! В данной статье мы осветим наш опыт внедрения в платформу 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".

Выбор "Client type"
Выбор "Client type"

Cоздайте Client для приватного реестра Docker-registry и задайте "docker-v2" в поле "Client Type". Введите имя клиента Docker-registry в поле "Client ID" такое же, как задали в переменной окружения REGISTRY_AUTH_TOKEN_SERVICE при настройке Docker-registry в предыдущем пункте выше, и нажмите кнопку Next.

Параметры клиента Docker-registry
Параметры клиента Docker-registry

В следующем окне вводить ничего не нужно, просто нажмите кнопку Next и затем кнопку Save.

Далее перейдите к настройкам только что созданного клиента docker-registry и в правом верхнем углу получите параметры настройки Docker-registry, нажав кнопку "Action" и выбрав пункт "Download adapter config".

Настройки адаптера Docker-registry
Настройки адаптера Docker-registry

Выберите пункт "Docker Compose YAML" и нажмите кнопку "Download".

Скачайте настройки адаптера Docker-registry
Скачайте настройки адаптера Docker-registry

В 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 с предложением смены пароля
Окно Keycloak с предложением смены пароля

Если администратор 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.

Окно аутентификации пользователя в Web-интерфейсе Docker-registry
Окно аутентификации пользователя в Web-интерфейсе Docker-registry

Пользователь вводит свой логин и пароль из Keycloak и получает доступ к Web-интерфейсу Docker-registry. На стартовой странице появится список всех репозиториев в приватном реестре Docker-образов.

Список репозиториев в приватном реестре Docker-образов
Список репозиториев в приватном реестре Docker-образов

Можно раскрыть один из репозиториев и получить список Tag-ов в нем и дополнительную информацию. Если потребуется, удалить ненужные Docker-образы.

Список Tag-ов и дополнительная информация
Список Tag-ов и дополнительная информация

Заключение

В данной статье мы осветили наш опыт внедрения собственного частного реестра Docker-образов на базе Docker-registry c аутентификацией в Keycloak и Web-интерфейсов от Joxit. Будем рады фидбеку, замечаниям и конструктивной критике. Не сочтите за труд и проголосуйте, пожалуйста, ниже. Спасибо за внимание!

Комментарии (6)


  1. GoooodBoy
    05.06.2024 06:13
    +1

    А где будут данные хранится? На дисках очень дорого.

    А как происходит очистка старых образов? Все хранить опять же дорого, да и не нужно

    А как на счет HA? Сервис максимально критичный, поскольку если он встанет, то колом встанет все

    Выглядит как лабораторная работа студента. Даже для небольшого проекта в прод я бы это не пустил.


    1. gitorion Автор
      05.06.2024 06:13

      Это платформа для разработки и тестирования. В production из нее деплоится только production контур и в нем можно сделать HA. Колом встанет не все, а только разработка. Удалить старые образы можно нажав пиктограмму Корзины в Web-интерфейсе. Платформа развернута к Kubernetes и там простор для типов подключаемых Persistent Volume. Можно подключить хоть NAS на Ceph


      1. GoooodBoy
        05.06.2024 06:13

        То есть, вы предлагаете для каждого стейджа свой регистри делать? А потом еще их синхронизировать между собой? Удаление руками, в WebUI! вы точно про продакшен рейди продукт говорите?


        1. gitorion Автор
          05.06.2024 06:13

          Регистри один, контуров 3 - development, staging и production. При необходимости для автоматизации очистки реестра можно добавить еще один пайплайн в Jenkins. Мы обязательно доработаем этот нюанс. Спасибо за ваш фидбек


          1. GoooodBoy
            05.06.2024 06:13

            Тогда почему регистри не HA? Ваш регистри упал, сломался, не справился с нагрузкой и все стейджы встали, ни сборку сделать, ни выкатить новую версию. Да еще и в проде под решил переехать на другую ноду, а там нет образа и ваш прибыльный сервис начал терять деньги.

            Пока вы предлагает откровенно непригодное для прода решение.


            1. gitorion Автор
              05.06.2024 06:13

              Это резонно, но это тема как минимум для отдельной статьи, чтобы материал относящийся к Docker-registry не утонул в материале про HA) Ровно так же все встанет, если упадет Git-сервер или Jenkins или Keycloak. При подготовке следующей статьи как раз о горизонтальном масштабировании мы подумаем о HA.