Буквально на днях внезапно 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?
Мы ведь настроили аутентификацию, так почему 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!