Найти в Дзене
реFUCKторинг

На злобу дня: разворачиваем приватный Docker Registry

Буквально на днях внезапно Docker Hub заблокировали на территории РФ. Если для кого-то это не критично, то многим пользователям нужно где-то хранить свои образы. Поэтому я решил рассказать, как развернуть и использовать свой приватный Docker Registry, и какие есть неочевидные подводные камни. Первым делом нам нужен установленный Docker и, например, NGINX. Про установку Docker и NGINX уже имеется куча статей, поэтому не будем тратить на это время. И так, начнем творить! Запуск приватного Docker Registry Запуск приватного Docker Registry задача довольно тривиальная: нужно стартануть контейнер с registry и настроить аутентификацию (мы ведь не хотим, чтобы наш registry использовался всеми подряд). Для начала необходимо прикрутить аутентификацию (безопасность все-таки). Для этого используем стандартный htpasswd. Единственное, что нужно учитывать при запуске контейнеров - нужно использовать какое-либо зеркало, потому что Docker Hub все еще не работает. Для примера я использую mirror.gcr.io.
Оглавление

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

Первым делом нам нужен установленный Docker и, например, NGINX.

Про установку Docker и NGINX уже имеется куча статей, поэтому не будем тратить на это время.

И так, начнем творить!

Запуск приватного Docker Registry

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

Для начала необходимо прикрутить аутентификацию (безопасность все-таки). Для этого используем стандартный htpasswd.

Единственное, что нужно учитывать при запуске контейнеров - нужно использовать какое-либо зеркало, потому что Docker Hub все еще не работает. Для примера я использую mirror.gcr.io.

Настройка аутентификации

Создадим директорию, где будем хранить volume's и auth информацию:

mkdir -p docker_registry/auth

Далее переходим в новую директорию docker_registry и запускаем контейнер httpd для создания логина и пароля для аутентификации:

cd docker_registry && docker run --entrypoint htpasswd mirror.gcr.io/httpd:latest -Bbn username password > auth/htpasswd

username - имя пользователя для аутентификации в нашем registry,
password - пароль пользователя.

В файле docker_registry/auth/htpasswd будут лежать данные для аутентификации в формате (фактически это auth_token как в Docker Hub):

username:$2y$05$HVFccVGkg6ai/DcnDajc1OQMWv8FdrIy8DN2sWdJz/3Kv7PmBWFTO

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

Настройка аутентификации закончена, переходим к запуску контейнера Docker Registry:

docker run -itd -p 5000:5000 --name docker-registry -v ~/docker_registry/auth:/auth -e REGISTRY_AUTH=htpasswd -e REGISTRY_AUTH_HTPASSWD_REALM=Registry_realm -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd mirror.gcr.io/registry

Собственно, этой командой мы запускаем наш контейнер с необходимыми параметрами для аутентификации.

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

docker login -u username -p password localhost:5000

Note: возможно localhost:5000 не сработает и нужно будет вместо localhost подставить IP-адрес сервера.

Насройка NGINX

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

Сама конфигурация nginx довольно проста.

Первым делом создаем конфиг и проксируем запросы в контейнер с registry:

upstream registry {
server localhost:5000;
}

server {
server_name docker.registry.my;

location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://registry;
}

listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/docker.registry.my/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/docker.registry.my/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}


server {
if ($host = docker.registry.my) {
return 301 https://$host$request_uri;
} # managed by Certbot

server_name docker.registry.my;
listen 80;
return 404; # managed by Certbot
}

docker.registry.my - ваш домен для доступа к реджистри.

Важно понимать, что докер работает по протоколу https, поэтому я использую certbot, который сам создает и обновляет сертификаты для NGINX.

После создания конфига необходимо рестартануть NGINX.

Note: помним про localhost - возможно лучше указать IP-адрес вашего сервера.

Подводные камни

На первый взгляд все готово и даже получится авторизоваться:

docker login -u username -p password docker.registry.my

Но радоваться еще рано!

Во-первых, наш конфиг должен обрабатывать путь с /v2, поскольку docker push и docker pull будут работать именно по пути https://docker.registry.my/v2/... для эскпорта/импорта манифестов, blobs и т.д.

Поэтому добавим обработку /v2 в конфиг NGINX. Для этого достаточно обработать еще один location:

location /v2 {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://registry;
}

И даже сейчас радоваться рано. Да, запросы docker push теперь не будут отваливаться с ошибкой HTTP 502, но и необходимого результата мы не достигнем, потому что команда будет падать с ошибкой:

failed to push docker.registry.my/repo/my-image:f1b8322: failed commit on ref "config-sha256:c88b95160c6dea133553bfb9397259a454d519a258aa232e0095107716568e4f": unexpected status from GET request to https://docker.registry.my/v2/repo/blobs/uploads/1ef07737-b45a-409b-b21a-82ea36231e48?_state=-60-p1INn5h8KG380yPr2k0uxa9JpT6S2FcNplM7nch7Ik5hbWUiOiJza2F6a2EiLCJVVUlEIjoiMWVmMDc3MzctYjQ1YS00MDliLWIyMWEtODJlYTM2MjMxZTQ4IiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA1LTMwVDIzOjM0OjAyLjc0NTIyMjQ0WiJ9&digest=sha256%3Ac88b95160c6dea133553bfb9397259a454d519a258aa232e0095107716568e4f:: 401 Unauthorized

Или еще "более" понятной:

unknown blob

И закономерно появляется вопрос: WAT?

-2

Мы ведь настроили аутентификацию, так почему Unauthorized и что за unknown blob?
Пришлось потратить изрядно времени, чтобы найти ответ. Он заключается всего лишь в одной строке в конфигурации NGINX. Необходимо добавить заголовок для запроса X-Forwarded-Proto="https" в каждый location:

proxy_set_header X-Forwarded-Proto "https";

Теперь можно со спокойной душой развлекаться с докером: пушить и пуллить в/из него. Но.... однажды появится ошибка:

413 Request Entity Too Large

Но здесь все просто: нужно лишь сконфигурировать максимальный размер тела запроса в NGINX. Для этого идем в nginx.conf и в блоке http добавляем параметр:

client_max_body_size 300M;

Note: естественно, можно выставить любой нужный вам размер.

Проверка

Для проверки работоспособности нашей вундервафли делаем три простых шага:

1. Логинимся в наш реджистри:

docker login -u username -p password docker.registry.my

2. Собираем докер-образ:

docker build -t docker.registry.my/repo/image-name:latest -f path/to/dockerfile/Dockerfile .

Здесь в теге обязательно указываем наш registry, иначе докер решит залить в Docker Hub. А оно нам надо?

3. Пушим образ:

docker push docker.registry.my/repo/image-name:latest

Если все сделали правильно, то ваш образ зальется в поднятый Docker Registry.

Чтобы стянуть его оттуда используем команду:

docker pull docker.registry.my/repo/image-name:latest

Заключение

На первый взгляд развертывание собственного Docker Registry кажется не самой сложной задачей. Но "благодаря" некоторым неочевидным проблемам приходится тратить довольно много времени, чтобы разобраться. Я описал свой путь по настройке этого зверя, и теперь санкции не помешают. Надеюсь, кому-то пригодится данный гайд.

May the Force be with you!