Для чего и чтобы что.
Иногда так бывает, что ты делаешь себе потихоньку что-то одно, а потом звезды сходятся в одну им, звездам, известную конфигурацию и ты бросаешь всё и начинаешь заниматься совсем дрегим делом.
Так случилось и со мной, а толчком этому послужила новая политика Docker Hub - не более 100 анонимных и не более 200 авторизованных пуллов в шесть часов. Сначала мы не придали этому большого значения, но количество проектов росло, количество деплоев на каждом - тоже и, в итоге, мы уткнулись в этот лимит. Можно было примотать какого-нибудь пользователя и жить до следующего лимита или же достать кошелек, но в кошельке мухи дохлые да дух святой и боле ничего, так что пришлось пилить нормальное решение.
Плюсом легло то, что я давно мечтал о собственном, сугубо локальном registry для хранения наших предсобранных образов - так две задачи объединились в одну.
Нам был нужен локальный docker-registry, доступный из локальной сети по внутреннему доменному имени через https без авторизации. К тому же этот registry должен не только хранить и отдавать наши собственные образы, но и проксировать запросы к Docker Hub без всяких дополнительных танцев.
Проще говоря, если условимся, что адрес будет registry.local
, то
все должно работать так:
docker pull registry.local/redis
отдает redis:latest
из собственного кэша, если он там есть или же из
docker hub, если в кэше не нашлось,
docker push registry.local/my-own-image
заливает образ в registry и, разумеется,
docker pull registry.local/my-own-image
этот образ отдает. При этом никаких действий на клиентских машинах производиться не должно.
Проблема
Для начала было решено использовать стандартный docker registry в режиме Registry as a pull through cache, но вот беда - registry не умеет одновременно работать и как, собственно, registry и как кэш - тут либо одно либо другое. Простой вариант - посадить два registry на соседние порты и пользоваться в таком вот режиме, но так, как любит говорить наш руководитель, только чужие для хищников пишут, а мы всё ж для людей стараемся. А значит, что точка входа должна быть одна, а там где одна точка входа на несколько сервисов, то там у нас что?
nginx
Куда ж без nginx? Но о нем мы поговорим чуть позже, а сначала - план!
Значит, первым на очереди на случай пуша образа у нас стоит обычный локальный registry, а на pull нам потребуется запросить образ в локальном registry, если нет - перенаправить запрос в кеш, ну а если и кеш не найдет ни у себя ни в вышестоящем registry, то ответить “Увы”. План прост как три рубля!
Но тут полезли подводные камушки.
library
Сперва надо было убедиться, что registry в режиме кэша работает как от него ожидается, так что на скорую руку я соорудил кэш:
version: '3'
services:
cache:
restart: always
image: registry:2
environment:
REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io
volumes:
- /DATA/registry:/var/lib/registry
После этого я запросил образ редиса и получил 404. WTF, подумал я и
полез а логи. Однако, выяснилось, что действительно, на registry-1.docker.io
нет никакого образа redis
. Зато есть образ library/redis
. И вообще,
как я потом уже нашел
отсюда
docker pull ubuntu
instructs docker to pull an image namedubuntu
from the official Docker Hub. This is simply a shortcut for the longerdocker pull docker.io/library/ubuntu
command
Проще говоря, все официальные образы хранятся у пользователя library
,
а docker hub это просто шорткатит.
Ну раз у докера есть такой шорткат, то и нам такой заиметь придется,
а тут уже без nginx
никак.
теперь действительно nginx
План наш немного дополнился - если на последнем шаге кэш ничего не нашел, то есть
ненулевая вероятность, что пользователь запрашивает официальный образ,
а значит, надо поискать в library
. Штош, в первом приближении собираем
что-то такое:
nginx.conf
upstream registry {
server registry:5000;
}
upstream cache {
server cache:5000;
}
client_max_body_size 10G;
server {
listen 80 default_server;
server_name registry.local;
location / {
proxy_pass http://registry;
error_page 404 = @cache;
}
location @cache {
proxy_pass http://cache;
error_page 404 = @library;
}
location @library {
rewrite /v2/(.*) /v2/library/$1 break;
proxy_pass http://cache;
}
}
Но на запрос редиса всё еще прилетает 404. Странно, не так ли?
Очередной приступ гугления принес следующую информацию: по умолчанию
nginx
делает только один фолбэк на ошибку, а дальнейшие игнорирует.
Почему - стоит Сысоева спросить, но есть способ это поправить:
recursive_error_pages on;
proxy_intercept_errors on;
И тут-то все заработало и тут-то… А нет, не работет. Ещё одним камнем оказалось то, что на отсутствующий образ docker hub вполне может отдать и 404 и 500. Пришлось добавить обработку и этой ошибки. Все, образы пуллятся, все счастливы, открываем шампанское! Но кажется, что что-то позабыли… Точно, пуш забыли! А пуш-то и сломался.
По причине, мне неизвестной, при пуше образа последний POST-запрос должен получить 404 вкачестве подтверждения, что образ запушен успешно. А на 404 у нас что? Правильно, редирект. Штош, отловим и это поведение:
if ($request_method = GET ) {
error_page 404 = @cache;
}
Рабочий вариант
Вот такой в итоге получается рабочий вариант конфига:
upstream registry {
server registry:5000;
}
upstream cache {
server cache:5000;
}
client_max_body_size 10G;
server {
listen 80 default_server;
server_name registry.local;
recursive_error_pages on;
proxy_intercept_errors on;
location / {
proxy_pass http://registry;
if ($request_method = GET ) {
error_page 404 = @cache;
}
}
location @cache {
proxy_pass http://cache;
error_page 404 500 = @library;
}
location @library {
rewrite /v2/(.*) /v2/library/$1 break;
proxy_pass http://cache;
}
}
Ну и docker-compose.yml для завершенности картины:
version: '3'
services:
web:
restart: always
image: nginx
ports:
- 80:80
volumes:
- ./nginx.conf:/etc/nginx/conf.d/registry.nginx.conf
cache:
restart: always
image: registry:2
environment:
REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io
volumes:
- /DATA/registry:/var/lib/registry
registry:
restart: always
image: registry:2
volumes:
- /DATA/registry:/var/lib/registry
Понятное дело, надо чтобы в локальной сети этот сервер откликался на
registry.local
ну и я опустил историю с ssl просто потому что она
довольно банальна и не имеет отношения к предмету статьи.
Вот в такой вот конфигурации все стало работать так, как и планировалось. Надеюсь, этот опыт будет вам полезен, ну и буду благодарен, если кто-нибудь расскажет мне про трюк с ошибкой 404 после пуша докер-образа.