Docker Registry 2.0 и Nexus 2: корпоративный реестр образов без Docker Hub
Развернули Nexus Repository Manager как прокси Docker Registry: кешируем публичные образы и храним внутренние - один адрес для всего парка серверов клиента с требованиями ИБ.
Docker Registry 2.0 и Nexus Repository Manager 2 позволяют поднять корпоративный реестр образов без зависимости от Docker Hub
У одного из наших клиентов служба ИБ поставила ультиматум: никакого прямого доступа продакшн-серверов в интернет, в том числе к Docker Hub. Требование понятное - серверы с данными не должны сами что-то тянуть снаружи. Проблема в том, что Docker без реестра - это как git без удалённого репозитория: технически можно, практически неудобно до боли.
Вариантов было немного. Либо каждый образ доставлять вручную через docker save / docker load на каждую машину, либо поднять собственный реестр внутри периметра. Второй вариант правильный, первый - это путь страданий.
Что вообще менялось в Docker Registry
Docker Registry 1.0 существовал давно, но был написан на Python и имел репутацию нестабильного. В начале 2015 года Docker Inc. выкатили Registry 2.0 - переписанный на Go, с новым API (Distribution API), принципиально другой архитектурой хранения. Это уже не «поделка для разработчиков», а нечто на что можно опираться.
Можно было поднять Docker Registry 2.0 напрямую - это один контейнер, официально поддерживаемый Docker Inc. Но тут возникал второй вопрос: а как быть с публичными образами из Hub? Разработчики тянут nginx:1.8, postgres:9.4, redis:2.8 - не класть же их все в реестр вручную. Нужен прокси-кеш: первый запрос идёт в Hub через DMZ-шлюз, второй и все последующие берутся из внутреннего кеша.
Docker Registry 2.0 поддерживает режим pull-through cache, но только для одного upstream. Если нужно одновременно хранить внутренние образы и проксировать внешние - нужно держать два экземпляра с разными настройками и как-то маршрутизировать запросы между ними. Схема рабочая, но громоздкая.
Nexus как универсальный прокси
Sonatype Nexus Repository Manager мы использовали раньше для Maven и npm. Nexus 2.11 добавил поддержку Docker Registry API поверх того же движка. Это значит: один сервис умеет работать как:
- hosted-репозиторий - хранит собственные образы клиента
- proxy-репозиторий - проксирует Docker Hub с локальным кешем
- group-репозиторий - единый адрес, который отдаёт и то, и другое
С точки зрения docker pull всё выглядит как один реестр по одному адресу. Разработчики и серверы настроены на registry.internal.client.ru:5000 и не думают о том, откуда именно взялся образ.
Архитектура получилась прямолинейная:
продакшн-серверы
|
v
registry.internal (Nexus 2, group)
| |
| proxy-repo -> Docker Hub (через DMZ-шлюз с allowlist)
|
hosted-repo (внутренние образы клиента)
Единственный хост, который имеет доступ к Docker Hub - это сам Nexus через контролируемый шлюз. Все остальные серверы в него не смотрят.
Как разворачивали
Nexus мы поставили на отдельную виртуалку с выделенным диском под данные. Nexus 2 поставляется как Java-приложение; данные хранит в локальной файловой системе (или можно вынести на S3/объектное хранилище, но для этого клиента хватило локального тома).
Несколько практических наблюдений:
- Nexus 2 требует настройки HTTP-коннектора отдельно от HTTPS. Docker-клиент работает с реестром только через HTTPS (или через явное исключение для
insecure-registries). Пришлось выписать внутренний сертификат от корпоративного CA клиента и добавить его в доверенные на всех серверах. - Proxy-репозиторий кеширует слои, но метаданные тегов обновляет по TTL. По умолчанию Nexus держит метаданные 24 часа. Это значит что
latestна вашем реестре может отставать от Hub на сутки. Для продакшна это скорее плюс - непредвиденного обновления базового образа не будет. - Group-репозиторий нужно настраивать аккуратно с порядком поиска. Hosted-репозиторий должен идти первым: если вы залили образ
myapp:1.2во внутренний, он должен отдаваться оттуда, а не искаться в Hub. - Аутентификация. Nexus умеет интегрироваться с Active Directory - это прямо закрывало требование ИБ: доступ к push только для CI-системы, pull - для всех серверов из внутреннего диапазона.
Что не сработало с первого раза
Самой неочевидной проблемой оказался Docker daemon на клиентских серверах. Команда docker pull registry.internal:5000/nginx:1.8 работала, а вот docker-compose up с image: nginx:1.8 - нет: Compose пытался ходить напрямую в Hub, игнорируя наш реестр.
Это ожидаемое поведение - Docker не знает что nginx:1.8 надо брать из внутреннего реестра. В docker-compose.yml надо явно писать полный адрес образа: image: registry.internal:5000/nginx:1.8. Переписали все Docker Compose-файлы клиента, добавили в CI-пайплайн шаг который при сборке образа тегирует его полным адресом реестра и пушит.
Другой момент - место на диске. Nexus охотно кеширует всё что через него проходит. Без политики очистки диск заполняется быстрее чем ожидаешь. В Nexus есть задачи очистки по расписанию, но их надо настроить явно - по умолчанию всё растёт.
Итог на сегодня
Схема работает. Продакшн-серверы не имеют прямого выхода в интернет, внутренние образы хранятся локально, публичные кешируются и отдаются с приемлемой скоростью внутри сети. ИБ-аудит прошёл без замечаний по этому пункту.
Для новых managed-проектов с похожими требованиями ИБ это теперь стандартная часть инфраструктуры - поднять Nexus с Docker Group занимает полдня включая выписку сертификатов. Раньше этот же вопрос решался через «ну давайте поставим Docker Registry 1.0 и посмотрим» - и стабильностью там не пахло.
Nexus 2 с Docker-поддержкой появился не так давно и некоторые вещи ещё шероховатые - например, UI для просмотра содержимого Docker-репозиториев заметно беднее чем для Maven. Но как бэкенд для docker pull/push - работает надёжно, и главное что одним сервисом закрывается и кеш публичных образов, и хранение внутренних.