Apps: "GitLab CE/EE", "GitLab Container Registry".
Задача: активировать встроенный в комбайн "GitLab" репозиторий docker-образов "GitLab Container Registry" с последующей проверкой функциональности. Примем за данность, что "GitLab" уже установлен по вышестоящей инструкции.
Дальнейшие действия:
1. Активация и настройка "GitLab Container Registry";
2. Проверка функциональности "GitLab Container Registry";
3. Автоматизация очистки от ненужных данных в "Container Registry".
2. Проверка функциональности "GitLab Container Registry";
3. Автоматизация очистки от ненужных данных в "Container Registry".
Активация и настройка "GitLab Container Registry".
По умолчанию подсистема "GitLab Container Registry" (базирующаяся на "Docker Registry") отключена. Она активируется, полностью автоматически настраиваясь оркестратором "Omnibus", при обнаружении в конфигурационном файле параметра "registry_external_url":
# vi /etc/gitlab/gitlab.rb
....
# URL, на котором обслуживаются запросы к "GitLab" (настроен ранее)
external_url 'https://gitlab.example.net'
# Указываем URL, на котором обслуживаются запросы к "GitLab Container Registry"
registry_external_url 'https://gitlab.example.net:5000'
# Переводим встроенный сервер "Docker Registry" на свободный порт "локальной петли"
registry['registry_http_addr'] = "localhost:5001"
....
# URL, на котором обслуживаются запросы к "GitLab" (настроен ранее)
external_url 'https://gitlab.example.net'
# Указываем URL, на котором обслуживаются запросы к "GitLab Container Registry"
registry_external_url 'https://gitlab.example.net:5000'
# Переводим встроенный сервер "Docker Registry" на свободный порт "локальной петли"
registry['registry_http_addr'] = "localhost:5001"
....
По умолчанию интегрированный в "GitLab" сервер "Docker Registry" запускается на локальной сетевой петле, принимая проксируемые от "GitLab" запросы на выделенном порту TCP:5000, что вынуждает для приёма подключений извне выбирать другой порт. Это весьма неудобно, так как заставляет пользователей вносить изменения к конфигурации своих приложений, по умолчанию работающих через порт TCP:5000, выделенный для сервиса "Docker Registry". В конфигурации выше мы переводим встроенный сервер "Docker Registry" на произвольный свободный порт, а высвободившийся TCP:5000 задействуем для приёма клиентских подключений извне.
В параметре "registry_external_url" три ключевых части. Указание протокола "https" по сути обязательно, так как это требование подкапотного "Docker Registry" - таковой не осуществляет аутентификацию по незащищённому каналу (чтобы не допустить утечки паролей). FQDN может быть произвольным - блок обслуживания адресованных ему запросов автоматически будет добавлен в конфигурацию встроенного в "GitLab" web-сервера "Nginx". TCP-порт может быть произвольным, за исключением уже используемых.
Важно иметь в виду, что "external_url" (адрес "GitLab") и "registry_external_url" (адрес "GitLab Container Registry") должны отличаться, иначе внутренний маршрутизатор межкомпонентных запросов не поймёт, куда направлять потоки данных. Если использовать одинаковые FQDN, то в запросах клиентов к "Docker Registry" придётся явно указывать TCP-порт.
На практике в "GitLab" приходится пользоваться единым FQDN для всех его компонентов потому, что автоматически запрашиваются только SSL-сертификаты "Let`s Encrypt" для FQDN основного сервиса "GitLab" (так в моих инсталляциях, в основном 12.x). При желании можно указать использовать для "GitLab Container Registry" имеющийся выделенный сертификат, но впоследствии предстоит ежегодная возня с его обновлением, что может быть неудобным:
....
# (опционально) Явно указываем используемые для FQDN "реестра" SSL-сертификаты
registry_nginx['ssl_certificate'] = "/etc/ssl/gitlab/registry.example.net.crt"
registry_nginx['ssl_certificate_key'] = "/etc/ssl/gitlab/registry.example.net.key"
....
# (опционально) Явно указываем используемые для FQDN "реестра" SSL-сертификаты
registry_nginx['ssl_certificate'] = "/etc/ssl/gitlab/registry.example.net.crt"
registry_nginx['ssl_certificate_key'] = "/etc/ssl/gitlab/registry.example.net.key"
....
Применяем изменения:
# gitlab-ctl reconfigure
Проверяем, правильным ли сертификатом отвечает "GitLab Container Registry":
$ echo QUIT | openssl s_client -connect gitlab.example.net:5000 | openssl x509 -noout -text | less
Проверка функциональности "GitLab Container Registry".
Для использования реестра docker-образов в "GitLab" требуется предварительная аутентификация. Учитывая то, что клиенты "Docker Registry" не обеспечивают безопасное хранение паролей, для этого сервиса выделяется отдельный набор логинов и паролей, названых здесь "токенами". У каждого пользователя может быть несколько "токенов", предъявителям которых он разрешает ограниченный доступ к тем или иным подсистемам. Каждый "токен" по отдельности можно легко отозвать (так же и автоматически, по истечению срока действия), не блокируя при этом основную учётную запись.
Итак, создаём "токен" и выделяем его для доступа к "GitLab Container Registry":
URL: https://gitlab.example.net/profile/personal_access_tokens
User Settings -> Access Tokens:
Add a personal access token:
Name: gitlab-token-username
Scopes: api, read_registry
User Settings -> Access Tokens:
Add a personal access token:
Name: gitlab-token-username
Scopes: api, read_registry
Подключаясь к "Docker Registry" можно указывать URL как полностью детально, так и подразумевая дополнение его "по умолчанию". Аккуратность призывает к первому, но практика подсказывает, что лучше покороче, так как указанный URL в дальнейшем будет фигурировать в качестве "имени репозитория":
$ docker login -u gitlab-token-username gitlab.example.net:5000
Как я упоминал выше, логин и пароль для подключения к "Docker Registry" по умолчанию сохраняются в открытом виде (закодированы "Base64") в домашней директории docker-клиента - поэтому мы используем "токены" вместо учётных записей пользователей "GitLab":
$ cat ~/.docker/config.json
{
"auths": {
"gitlab.example.net": {
"auth": "cm9...eg=="
}
....
}
"auths": {
"gitlab.example.net": {
"auth": "cm9...eg=="
}
....
}
После того, как мы убедились, что подключение к "Docker Registry" осуществимо, подготовим docker-образы для загрузки в реестр.
Принцип определения, какие конкретно "образы" выгружать в реестр, построен на тегировании. Если в "теге" docker-образа указан репозиторий, к которому налажено подключение, то по команде выгрузки он будет туда отправлен.
Таким образом, при создании "образа" мы просто указываем, в каком репозитории он должен быть размещён, и в процессе сборки он будет туда залит:
$ docker build --tag gitlab.example.net:5000/group/project:app-1.234 --file ./Dockerfile .
Как вариант, можно добавить "тег" к уже имеющемуся образу, указывая тем самым, что он должен быть размещён ещё и в нужном нам репозитории.
Запрашиваем перечень локальных "образов":
$ docker images
REPOSITORY TAG IMAGE ID
selfmade app-1.234 8dfd976a2acf
selfmade app-1.234 8dfd976a2acf
Добавляем "тег" с указанием целевого репозитория к имеющемуся docker-образу:
$ docker tag 8dfd976a2acf gitlab.example.net:5000/group/project:app-1.234
Теперь один и тот же "образ" отмечен, как размещаемый в локальном и удалённом хранилищах:
$ docker images
REPOSITORY TAG IMAGE ID
selfmade app-1.234 8dfd976a2acf
gitlab.example.net:5000/group/project app-1.234 8dfd976a2acf
selfmade app-1.234 8dfd976a2acf
gitlab.example.net:5000/group/project app-1.234 8dfd976a2acf
Даём команду отправить в "Docker Registry" изменения "образов", имеющие отношение к целевому репозиторию:
$ docker push gitlab.example.net:5000/group/project
The push refers to repository [gitlab.example.net/group/project]
690...27: Pushed
627...4c: Pushed
app-1.234: digest: sha256:879...1c size: 741
690...27: Pushed
627...4c: Pushed
app-1.234: digest: sha256:879...1c size: 741
Обратимся непосредственно к репозиторию, запросим перечень его docker-образов - наш должен быть там:
$ docker images -a gitlab.example.net:5000/group/project
REPOSITORY TAG IMAGE ID
gitlab.example.net:5000/group/project app-1.234 8dfd976a2acf
gitlab.example.net:5000/group/project app-1.234 8dfd976a2acf
Ненужный или неверный "тег" при docker-образе в локальном хранилище легко удалить:
$ docker rmi selfmade:app-1.234
Бесплатные версии docker-клиентов не поддерживают простое прямое удаление docker-образов из удалённого репозитория - это приходится делать через специальное REST-API, специфичное для каждого продукта, будь то "GitLab", "Nexus" или оригинальный "Docker Registry" - мы рассмотрим эту методику позже.
Проще всего неподготовленному пользователю удалить ненужный ему образ через web-интерфейс "GitLab":
GitLab -> Project -> Packages -> Container Registry.
После завершения работы с "Docker Registry" желательно явно отключиться от него (с автоматическим удалением логина и пароля из файла "~/.docker/config.json" в профиле пользователя):
$ docker logout
Автоматизация очистки от ненужных данных в "GitLab Container Registry".
Важно иметь в виду, что удаление docker-образов из репозитория "GitLab Container Registry" на самом деле является не в полном смысле удалением данных, а представляет собой лишь удаление описаний docker-образов и ссылок на слои данных, при этом последние остаются в файловой системе. Не уверен, что понимаю, зачем так сделано - возможно ради скорости работы или для надёжности. Как бы то ни было, для удаления осиротевших слоёв данных (без мета-описаний) приходится регулярно запускать уборщика мусора:
# vi /etc/crontab
....
# Weekly garbage collection in "GitLab Container Registry"
1 4 * * 0 root gitlab-ctl registry-garbage-collect -m
# Weekly garbage collection in "GitLab Container Registry"
1 4 * * 0 root gitlab-ctl registry-garbage-collect -m
Проблемы: подключение к серверу с "самоподписанным" SSL-сертификатом.
В случае, если "GitLab Container Registry" работает через "сампоподписанный (self-signed)" SSL-сертификат, подключающийся docker-клиент не сможет проверить его подлинность и откажется работать со следующим сообщением:
Error response from daemon: Get https://gitlab.example.net:5000/v2/: x509: certificate signed by unknown authority
Для обхода проблемы придётся явно подать "Docker"-у публичную часть сертификата целевого сервера как доверенную, разместив его в предназначенном для этого месте файловой системы (последняя директория в иерархии должна точно соответствовать FQDN целевого сервера, включая порт, если таковой указывается - например: "gitlab.example.net:5000"):
# mkdir -p /etc/docker/certs.d/gitlab.example.net:5000
# cp ./gitlab.example.net-ss.crt /etc/docker/certs.d/gitlab.example.net:5000/
# cp ./gitlab.example.net-ss.crt /etc/docker/certs.d/gitlab.example.net:5000/