Внутренний Docker Registry за nginx: basic auth, TLS и прощание с Docker Hub
Подняли приватный Docker Registry v1 внутри инфраструктуры, закрыли его nginx с basic auth и TLS. Образы теперь не уходят на Docker Hub - хранятся дома.
Docker Registry v1 позволяет поднять приватный реестр образов внутри инфраструктуры компании; разработчики перестали пушить образы на Docker Hub - всё хранится на внутреннем сервере
Когда Docker только появился в боевом использовании, первый рефлекс был понятный: пушить образы на Docker Hub. Публичный, бесплатный, работает из коробки. Но примерно через месяц стало очевидно, что для клиентских проектов это не очень хорошая идея - в образах оказывается конфигурация, иногда сертификаты, иногда просто хочется, чтобы CI/CD не зависел от внешнего сервиса. Поэтому решили поднять внутренний реестр.
Docker Registry v1 - это python-приложение, которое docker.io предоставляет как отдельный компонент. Запускается как контейнер, хранит образы в файловой системе или в S3. Для начала взяли вариант с локальной файловой системой - S3 оставили на потом.
Схема
Схема получилась такая: Registry слушает на 5000-м порту на localhost, nginx принимает HTTPS снаружи и проксирует внутрь. Basic auth проверяется на уровне nginx. Клиенты (разработчики, CI-сервер) работают только с nginx - прямой доступ к Registry-порту закрыт фаерволом.
разработчик
|
| docker push registry.internal/myapp:1.0
v
nginx :443 (TLS + basic auth)
|
| proxy_pass
v
docker-registry :5000 (localhost only)
|
| хранит слои
v
/var/lib/docker-registry/
Почему nginx, а не встроенная аутентификация Registry? В v1 аутентификация реализована через отдельный auth-сервис, который надо поднимать дополнительно и настраивать. nginx с htpasswd проще, понятнее и уже стоит на всех наших серверах.
Запуск Registry
Registry запускаем как контейнер:
docker run -d \
--name registry \
--restart=always \
-p 127.0.0.1:5000:5000 \
-v /var/lib/docker-registry:/tmp/registry \
registry:0.9.1
Важный момент - 127.0.0.1:5000:5000, не просто -p 5000:5000. Второй вариант открывает порт на всех интерфейсах, что нам не нужно. Файловая система монтируется через том - иначе данные пропадут при пересоздании контейнера.
nginx: TLS и basic auth
Сертификат взяли тот же, что описывали раньше при работе с SSL-терминацией nginx. Внутреннее имя registry.internal прописали в корпоративном DNS.
htpasswd-файл создаём стандартно:
htpasswd -c /etc/nginx/registry.htpasswd developer1
htpasswd /etc/nginx/registry.htpasswd developer2
htpasswd /etc/nginx/registry.htpasswd ci-server
Конфиг nginx:
upstream docker-registry {
server 127.0.0.1:5000;
}
server {
listen 443 ssl;
server_name registry.internal;
ssl_certificate /etc/nginx/ssl/registry.crt;
ssl_certificate_key /etc/nginx/ssl/registry.key;
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:!aNULL:!eNULL:!RC4';
ssl_prefer_server_ciphers on;
client_max_body_size 0;
chunked_transfer_encoding on;
auth_basic "Docker Registry";
auth_basic_user_file /etc/nginx/registry.htpasswd;
location / {
proxy_pass http://docker-registry;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 900;
}
}
Две вещи, на которые налетели и без которых не работает:
client_max_body_size 0. По умолчанию nginx ограничивает тело запроса одним мегабайтом. Docker-образ - это набор слоёв, каждый слой может быть сотни мегабайт. Без этой директивы push любого нетривиального образа падал с 413 Request Entity Too Large.
proxy_read_timeout 900. Push большого образа занимает время. Дефолтные 60 секунд nginx не хватает - соединение разрывается посередине, и docker client выдаёт невнятную ошибку про broken pipe. 900 секунд с запасом для всего, что мы пушим.
Настройка на клиентской стороне
Разработчикам надо добавить CA-сертификат в доверенные, чтобы docker не ругался на самоподписанный сертификат. На Ubuntu/Debian:
cp registry-ca.crt /usr/local/share/ca-certificates/registry-internal.crt
update-ca-certificates
service docker restart
После этого пуш выглядит как обычно, только адрес другой:
docker login registry.internal
docker tag myapp:latest registry.internal/myapp:latest
docker push registry.internal/myapp:latest
На CI-сервере завели отдельного пользователя ci-server в htpasswd и прописали credentials в переменные окружения сборки. Пароли не в конфиге, а в секретах - это дисциплина, которую проще завести сразу.
Что получилось
Образы теперь не уходят за периметр. Скорость push/pull заметно выросла - не потому что Registry быстрее Docker Hub, а потому что он в локальной сети. Крупные образы, которые раньше тащились минутами, теперь деплоятся за секунды.
Из неочевидного: Registry v1 не умеет удалять образы штатными средствами. Если наплодить тегов - место на диске будет расти. Пока решаем руками, разбирая /var/lib/docker-registry по датам изменения. Не идеально, но для текущего объёма терпимо.
В рамках managed-инфраструктуры это ещё один компонент, который надо мониторить: Registry-контейнер, место на диске, доступность nginx. Добавили в Zabbix - проверку HTTP-статуса и свободное место на разделе с образами. Пока без автоматической очистки - пишем скрипт.
Схема вышла незатейливая, но рабочая. Несколько клиентских команд уже перешли, Docker Hub в проектах больше не фигурирует.
- Docker 1.0 в staging: bridge-сеть не для нескольких хостов · 3 июля 2014
- SSL-аудит nginx после Heartbleed: от B до A минимальными правками · 19 августа 2014