ADG Оставить заявку
Блог DevOps 4 мин чтения

Внутренний 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 в проектах больше не фигурирует.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.