Docker Hub vs. Private registry
Докер это не только набор программного обеспечения, но и инфраструктура в виде docker hub, где любой желающий может хранить образы в публичном доступе абсолютно бесплатно. Плюс этот хаб используется компаниями для выкладывания своих публичных образов ПО и операционных систем. Репозитории таких компаний проверены и безопасны (я надеюсь). Это очень удобно для опенсорс разработки, экспериментов и домашних проектов.
Docker как и github монетизирует свой сервис платными аккаунтами с частными(приватными) репозиториями. Но основание для создание своего частного хранилища образов даже не платность сервиса, а то что объемные образы внутрь своей инфраструктуры придется тянуть из интернета и при чем на каждый железный сервер по отдельности.
Своё собственное хранилище
Докер сам предоставляет образ с приватным хранилищем. Им я и воспользуюсь:
docker pull registry
Эту команду я выполню на удаленном рабочем сервере. После скачивания образ можно будет увидеть в системе командой docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
registry latest fc251f2434a1 7 days ago 413.6 MB
Чтоб сохранить файлы образов, которые будут заливаться в хранилище необходимо использовать концепцию контейнера только с данными.
Для создания такого контейнера потребуется следующий Dockerfile:
FROM busybox
VOLUME /registry/storage
CMD /bin/sh
Это простейший контейнер на базе busybox в котором создан один том /registry/storage
.
Создадим контейнер на нашем сервере, выполнив команду рядом с нашим докерфайлом: docker build -t registry_datastore .
Для начала запустим хранилище в режиме без авторизации и поддержки ssl:
-
Проинициализируем контейнер с данными
docker run --name registry_datastore registry_datastore true
-
Непосредственно запустим registry
docker run --name registry --volumes-from registry_datastore \ -e STORAGE_PATH=/registry/storage -e SEARCH_BACKEND=sqlalchemy \ -p 10.200.200.28:5000:5000 -d registry
Контейнер с именем registry использует том /registry/storage от контейнера registry_datastore. Переменная STORAGE_PATH указывает путь до места хранения базы образов, а SEARCH_BACKEND включает встроенный в образ механизм поиска. Команда -p 10.200.200.28:5000:5000 пробрасывает на внутренний интерфейс сервера порт 5000, через который и будет осуществляться доступ к хранилищу.
Это минимальный запуск. Если вас не беспокоит авторизация и безопасность. И вы полностью доверяете своей локальной сети, то этого базового варианта хватит для использования в боевую.
Но если вам требуется авторизация и передача данных поверх HTTPS, то останавливаем и удаляем контейнер docker stop registry; docker rm registry
и продолжаем.
Сертификаты
Инфраструктура будет построена по следующей схеме:
Базовую авторизацию будет обеспечивать nginx, но по спецификации докера, если включена авторизация, то необходим протокол https. Значит понадобиться сделать самоподписанные сертификаты:
-
Создаю корневой ключ:
openssl genrsa -out dev-docker-ca.key 2048
-
Генерирую корневой сертификат на основе ключа (можно вводить любые данные):
openssl req -x509 -new -nodes -key dev-docker-ca.key -days 10000 -out dev-docker-ca.crt
-
Создам ключ для сайта registry:
openssl genrsa -out dev-docker-registry.k12.key 2048
-
Далее необходимо создать запрос на сертификат для нашего сайта:
openssl req -new -key dev-docker-registry.k12.key -out dev-docker-registry.k12.csr
Здесь будет диалог с мастером. В Common name необходимо указать имя вашего сайта для registry, в моём случае я имею внутреннюю зону .k12 и собственный dns, который эту зону обслуживает. И для Common name буду использовать registry.k12. На последнем этапе ввода пароля лучше оставить поля пустыми:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:RU
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:K12
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:registry.k12
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
-
Далее следует создание сертификата сайта:
openssl x509 -req -in dev-docker-registry.k12.csr -CA dev-docker-ca.crt -CAkey dev-docker-ca.key -CAcreateserial -out dev-docker-registry.k12.crt -days 10000
-
Так же, если вы планируете использовать веб-интерфейс, то необходимо подключить корневой сертификат к java внутри контейнера ui-registry, иначе этот шаг можно пропустить:
keytool -importcert -file dev-docker-ca.crt -alias ca -keystore truststore -storepass PaSsw0Rd -noprompt
Утилита keytools входит в состав jdk. На основе корневого сертификата dev-docker-ca.crt создатся фaил truststore, который позднее будет подключен к java внутри контейнера с ui.
Nginx
Для создания контейнера nginx я использую Dockerfile из первой части статей, но необходимо внести модификации:
#
# Nginx stable Dockerfile
#
# Pull base image.
FROM phusion/baseimage
# Install Nginx.
RUN \
add-apt-repository -y ppa:nginx/stable && \
apt-get update && \
apt-get install -y nginx apache2-utils && \
echo "\ndaemon off;" >> /etc/nginx/nginx.conf && \
chown -R www-data:www-data /var/lib/nginx && \
unlink /etc/nginx/sites-enabled/default && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Runit Nginx service
ADD nginx.sh /etc/service/nginx/run
# Disable ipv6
ADD ipv6off.sh /etc/rc.local
# Use baseimage-docker's init system.
CMD ["/sbin/my_init"]
Изменение одно, это установка пакета apache2-utils из которого будет использоваться утилита htpasswd.
Образ можно собрать: docker build -t nginx .
Авторизация и SSL
Для хранение конфигов nginx и сертификатов понадобится контейнер с данными. Его описание в следующем докерфайле:
FROM busybox
VOLUME /etc/nginx/conf.d
VOLUME /etc/nginx/pki
VOLUME /java-pki
CMD /bin/sh
Создается три тома. Под конфиги nginx, сертификаты и ключи, и под хранилище ключей для java. Сборка образа и инициализация контейнера:
docker build -t conf_datastore .
docker run --name conf_datastore conf_datastore true
Будет использовано два сайта. Один непосредственно для private registry с адресом https://registry.k12, а так же веб интерфейс http://ui.registry.k12. За это будут отвечать следующие конфиги:
# registry.k12
upstream docker-registry {
server registry:5000;
}
server {
listen 443;
server_name registry.k12;
ssl on;
ssl_certificate pki/dev-docker-registry.k12.crt;
ssl_certificate_key pki/dev-docker-registry.k12.key;
proxy_set_header Host $http_host; # required for Docker client sake
proxy_set_header X-Real-IP $remote_addr; # pass on real client IP
client_max_body_size 0;
chunked_transfer_encoding on;
location / {
auth_basic "Restricted";
auth_basic_user_file registry.htpasswd;
proxy_pass http://docker-registry;
}
location /_ping {
auth_basic off;
proxy_pass http://docker-registry;
}
location /v1/_ping {
auth_basic off;
proxy_pass http://docker-registry;
}
}
# ui.registry.k12
upstream docker-ui-registry {
server ui-registry:8080;
}
server {
listen 80;
server_name ui.registry.k12;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
location / {
auth_basic "Restricted";
auth_basic_user_file registry.htpasswd;
proxy_pass http://docker-ui-registry;
}
}
Данные конфиги, а так же сгенереннные файлы dev-docker-registry.k12.crt, dev-docker-registry.k12.key и truststore необходимо поместить в одну папку. Переходим внутрь этой папки и подключаемся к дата контейнеру conf_datastore следующей командой:
docker run --rm -it --volumes-from conf_datastore -v `pwd`:/volume busybox /bin/sh
Вы окажитесь внутри контейнера с доступом к томам conf_datastore и "прокинутой" внутрь контейнера папкой /volume. Теперь осталось разложить файлы по томам:
root@server# docker run --rm -it --volumes-from conf_datastore -v `pwd`:/volume busybox /bin/sh
/ # cd /volume
/volume # ls -lA
-rw-r--r-- 1 root root 1103 Mar 19 08:25 dev-docker-registry.k12.crt
-rw-r--r-- 1 root root 1679 Mar 19 07:55 dev-docker-registry.k12.key
-rw-r--r-- 1 root root 775 Mar 20 07:30 registry.k12.conf
-rw-r--r-- 1 root root 910 Mar 20 08:59 truststore
-rw-r--r-- 1 root root 374 Mar 20 07:31 ui.registry.k12.conf
/volume # cp dev-docker-registry.k12.crt /etc/nginx/pki/
/volume # cp dev-docker-registry.k12.key /etc/nginx/pki/
/volume # cp truststore /java-pki/
/volume # cp *.conf /etc/nginx/conf.d/
/volume # exit
Web-ui registry interface
Последний подготовительный этап - это веб интерфейс к хранилищу. Я для своих нужды выбрал docker-registry-ui.
docker pull atcol/docker-registry-ui
Внутри контейнера находится java-tomcat сервер приложений и именно для него было необходимо создавать java хранилище сертификатов truststore.
Запуск
Теперь со всем этим необходимо взлететь в правильной последовательности.
Но перед запуском контейнеров необходимо на сервере зарегистрировать сгенерированный корневой сертификат.
root@server# mkdir /usr/local/share/ca-certificates/dev-docker-ca
root@server# cp dev-docker-ca.crt /usr/local/share/ca-certificates/dev-docker-ca/ # копирование корневого сертификата
root@server# update-ca-certificates
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d....done.
Далее необходимо сделать рестарт главному демону докера: service docker restart
Корневой сертификат лучше положить в легкодоступное место, например по http, т.к. добавления сертификата в систему необходимо сделать на каждом железном сервере, который участвует в построении docker-инфраструктуры
Уже должны быть проинициализированы два контейнера с данными (registry_datastore, conf_datastore). Если нет, то необходимо сделать это:
docker run --name registry_datastore registry_datastore true
docker run --name conf_datastore conf_datastore true
Далее необходимо запустить контейнер registry:
docker run --name registry --volumes-from registry_datastore \
-e STORAGE_PATH=/registry/storage -e SEARCH_BACKEND=sqlalchemy -d registry
Следом контейнер с веб интерфейсом к хранилищу:
docker run --name ui-registry --volumes-from conf_datastore \
-e 'JAVA_OPTS=-Djavax.net.ssl.trustStore=/java-pki/truststore -Djavax.net.ssl.trustStorePassword=PaSsw0Rd' \
-e REG1=https://registry.k12/v1/ \
-d atcol/docker-registry-ui
Переменной 'JAVA_OPTS=-Djavax.net.ssl.trustStore=/java-pki/truststore -Djavax.net.ssl.trustStorePassword=PaSsw0Rd' java внутри контейнера ui-registry подхватит корнейвой сертификат из хранилища /java-pki/truststore под соответствующим паролем. Переменной REG1 задается адрес до api private registry.
В финале запускаю фронтенд - nginx:
docker run --name nginx --link registry:registry --link ui-registry:ui-registry --volumes-from conf_datastore \
-p 10.200.200.28:443:443 -p 10.200.200.28:80:80 -d nginx
Docker login, push
Подключусь к контейнеру nginx и создам фаил для базовой авторизации /etc/nginx/registry.htpasswd
root@server# docker exec -it nginx /bin/bash
root@d4b659658677:/# htpasswd -b /etc/nginx/pki/registry.htpasswd admin
New password:
Re-type new password:
Adding password for user admin
root@d4b659658677:/# exit
Теперь на сервере с докером можно логиниться:
root@server# docker login registry.k12
Username: admin
Password:
Email:
Login Succeeded
Email можно оставить пустым.
Для того чтоб запушить образ в реджестри необходимо повесить правильный тег, а затем уже сделать пуш. Протестирую на образе nginx:
root@server# docker tag nginx registry.k12/nginx
root@server#
root@server# docker images | grep nginx
nginx latest bbb605efa1e9 4 days ago 299.7 MB
registry.k12/nginx latest bbb605efa1e9 4 days ago 299.7 MB
root@server#
root@server# docker push registry.k12/nginx
The push refers to a repository [registry.k12/nginx] (len: 1)
Sending image list
Pushing repository registry.k12/nginx (1 tags)
511136ea3c5a: Image successfully pushed
53f858aaaf03: Image successfully pushed
837339b91538: Image successfully pushed
615c102e2290: Image successfully pushed
b39b81afc8ca: Image successfully pushed
8254ff58b098: Image successfully pushed
ec5f59360a64: Image successfully pushed
2ce4ac388730: Image successfully pushed
2eccda511755: Image successfully pushed
5a14c1498ff4: Image successfully pushed
83e12e068086: Image successfully pushed
c2db9a7375fb: Image successfully pushed
4cd8296c1a93: Image successfully pushed
bbb605efa1e9: Image successfully pushed
Pushing tag for rev [bbb605efa1e9] on {https://registry.k12/v1/repositories/nginx/tags/latest}
Поиск и веб интерфейс
Для того чтоб осуществить поиск в private registry можно воспользоваться api и curl`ом
# Вывод всех образов в хранилище
root@server# curl https://admin:SecRetPassWord@registry.k12/v1/search
{"num_results": 1, "query": "nginx", "results": [{"description": "", "name": "library/nginx"}]}
# Персонализированный поиск
root@server# curl https://admin:SecRetPassWord@registry.k12/v1/search?q=nginx
{"num_results": 1, "query": "nginx", "results": [{"description": "", "name": "library/nginx"}]}
# То же, но в человеческом виде
root@server# curl https://admin:SecRetPassWord@registry.k12/v1/search?q=nginx | python -mjson.tool
{
"num_results": 1,
"query": "",
"results": [
{
"description": "",
"name": "library/nginx"
}
]
}
Или можно использовать веб интерфейс. В моём случае нужно перейти на страницу http://ui.registry.k12
Необходимо заполнить Username и Password на странице добавления/редактирования хранилища.
Теперь возможно воспользоваться поиском.
На этом можно считать процесс внедрения private registry в инфраструктуру законченным. Веб интерфейс сделан для галочки и не несет особой нагрузки и юзабилити, но возможно будет улучшен в будущем и окажется более полезен. Приватное хранилище готово для централизованного распространения образов в частной сети. В следующей статье private registry будет использовано для запуска сервисов через docker. Одним из первых на очереди logstash.