Главной новостью этой недели стала блокировка пользователей из России ресурсом Docker Hub. Она осуществляется по Geo IP.

Ирония в том, что у самого докера есть инструменты, чтобы обойти эту блокировку. Используем докер, чтобы обойти блокировку докера и дальше использовать докер.

В статье три проверенных мною способа, как получить доступ к ресурсу.


Знакомо?

~> docker pull alpine:3.17
Error response from daemon: pull access denied for alpine, repository does not exist or may require 'docker login': denied: <html><body><h1>403 Forbidden</h1>
Since Docker is a US company, we must comply with US export control regulations. In an effort to comply with these, we now block all IP addresses that are located in Cuba, Iran, North Korea, Republic of Crimea, Sudan, and Syria. If you are not in one of these cities, countries, or regions and are blocked, please reach out to https://hub.docker.com/support/contact/
</body></html>

Такой ответ сейчас получают пользователи из России. Что можно с этим сделать?

1. Зеркала

В текущей ситуации зеркала актуальны скорее для серверов, а не для настольных компьютеров. Вам всё равно придётся идти в https://hub.docker.com/ чтобы посмотреть имя образа, теги, переменные и т.д.

Публичные зеркала

Сейчас все пишут про публичные зеркала. Некоторые люди предлагают использовать ноунейм и правительственные зеркала. Но я не советую этого делать. В какой-то момент в образ может добавиться слой с каким-нибудь вредоносом.

Если хотите использовать публичные зеркала, то используйте от компаний, которым важна репутация. Сейчас это:

  • https://mirror.gcr.io - Гугл. Он и так знает уже всё, что можно было о вас знать. Теперь ещё узнает, что вы используйте полный образ debian, а не slim версию.

  • https://dockerhub.timeweb.cloud/ - Зеркало от TimeWeb. Компания не маленькая и не новая.

Думаю, скоро подобное поднимут многие компании.

Своё зеркало

Это в первую очередь актуально для компаний и команд разработки. Компания Docker уже очень давно предоставляет образ registry, с помощью которого любой желающий может поднять своё зеркало.

Мы вас заблокировали. Ну зеркало там поднимите, если что. Вот образ.

Логика простая:

Вам нужен образ, например, alpine:3.17, вы просите его у зеркала, зеркало проверяет свой кэш. Если образ у него уже есть, отдаёт вам, если нет - качает и потом отдаёт вам.

Соответственно, чтоб зеркало могло само качать образы, его IP-адрес не должен быть в списках бана у Docker. Скорее всего, ему нужно находиться вне РФ, но не всегда. Проверить, что образы будут выкачиваться на виртуалке без установленного докера:

curl -I https://hub.docker.com/

403 - VPS не подходит

200 - подходит

Опишу самый простой вариант поднятия зеркала через compose.

Без аутентификации

Своё "публичное" зеркало. Публичное зеркало могут использовать другие люди, если узнают о его существовании. В результате будет лишняя нагрузка на канал и занятое место на диске. Но плюс в том, что без аутентификации использовать зеркало намного проще.

А ограничить можно, например, на уровне firewall по подсетям.

Для подтягивания сертификатов Let’s Encrypt предлагаю traefik, весь его конфиг помещается в compose файл. Вся конфигурация registry хранится в одном файле и легко перетаскивается в случае чего.

$DOMAIN - домен для вашего зеркала. Не используйте IP-адрес. Без шифрования таскать образы через магистраль идея плохая. И ещё придётся подкручивать докер, чтоб не ругался.

$EMAIL - Для Let’s Encrypt. Отправляет письма с предупреждениями, не особо полезные.

services:
  mirror:
    container_name: "mirror"
    image: registry:2.8.3
    volumes:
      - ./data/:/var/lib/registry
    environment:
      - REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io
      - REGISTRY_HTTP_ADDR=0.0.0.0:80
      - REGISTRY_STORAGE_DELETE_ENABLED=true
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mirror.rule=Host(`$DOMAIN`)"
      - "traefik.http.routers.mirror.entrypoints=websecure"
      - "traefik.http.routers.mirror.tls.certresolver=myresolver"
      - "traefik.http.services.mirror.loadbalancer.server.port=80"

  traefik:
    image: "traefik:v2.11.3"
    container_name: "traefik"
    command:
#      - "--log.level=DEBUG"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=с"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.permanent=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"
    restart: always

Прописываете А-запись для IP-адреса, помещаете compose-файл куда-нибудь на сервере. Затем docker compose up -d и всё, у вас есть своё "публичное" зеркало.

URI для проверки зеркала через браузер/curl:
https://$DOMAIN/v2/_catalog

С аутентификацией

То же самое, но используем Basic Auth. Аутентификация для зеркала таит свой подводный камень, про него будет далее в клиентах.

Registry сам умеет в Basic auth, но там нужно создавать htpasswd файл и, соответственно, отдельный bind/volume под него. Описываю самый простой вариант, поэтому за аутентификацию будет также отвечать traefik.

У traefik свой прикол: символы $ в хэше нужно экранировать ещё одним $.

Хэш для пароля генерируется стандартно

htpasswd -bBn user password

Либо сразу с фиксом $

echo $(htpasswd -bBn user password) | sed -e s/\\$/\\$\\$/g

Полученный hash вставляется в label traefik.http.middlewares.mirror-auth.basicauth.users.
Можно задать несколько юзеров через запятую

services:
  mirror:
    container_name: "mirror"
    image: registry:2.8.3
    volumes:
      - ./data:/var/lib/registry
      - ./auth/:/auth
    environment:
      - REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io
      - REGISTRY_HTTP_ADDR=0.0.0.0:80
      - REGISTRY_STORAGE_DELETE_ENABLED=true
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mirror.rule=Host(`$DOMAIN`)"
      - "traefik.http.routers.mirror.entrypoints=websecure"
      - "traefik.http.routers.mirror.tls.certresolver=myresolver"
      - "traefik.http.services.mirror.loadbalancer.server.port=80"
      - "traefik.http.routers.mirror.middlewares=mirror-auth"
      - "traefik.http.middlewares.mirror-auth.basicauth.users=user:$$2y$$05$$D/a0AJqMRN/18eu9hD2Oxe5b9BJDgUvYaXQYSH.aQpjqhhYqswXPO"

  traefik:
    image: "traefik:v2.11.3"
    container_name: "traefik"
    command:
#      - "--log.level=DEBUG"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=$EMAIL"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.permanent=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"
    restart: always

Получили "приватное" зеркало на минималках.

Не забудьте, что место на диске рано или поздно может закончиться.

Периодически чистить слои без тегов можно так

docker exec mirror bin/registry garbage-collect /etc/docker/registry/config.yml -m

Использование зеркал на "клиентах"

Если вы раньше не использовали зеркала, приготовьтесь к тому, что что-то может не заработать сразу или вообще не заработать. По моему опыту, не всегда всё получается с первого раза.
В основном всплывают проблемы при частом редактировании ~/.docker/daemon.json. Если вы сталкивались с этим и решили, - расскажите в комментариях.

Linux

Опять же простой сервер с докером. Без этих ваших кубернетусов.

Используем публичное зеркало

Добавляете в файл ~/.docker/config.json. Возможно, его будет нужно создать.

  "registry-mirrors": [
    "https://$DOMAIN"
  ]

Если там ничего, нет кроме зеркала, то выглядит так:

{
  "registry-mirrors": [
    "https://$DOMAIN"
  ]
}

Рестарт докера

systemctl restart docker.service

UPD 1.06.2024
В комментариях подсказали, что не обязательно рестартить docker.service при добавлении зеркал. Для добавления и редактирования зеркал действительно достаточно

systemctl reload docker.service

Но если вы захотите удалить зеркало, то без рестарта оно не удалится.

Проверка, что зеркало подцепилось

docker info

В конце должно быть

 Registry Mirrors:
  https://mirror-example.com/

Теперь при docker pull alpine сервис docker будет ходить в зеркало. Это можно отследить в логах поднятого ранее registry mirror.

На сервере зеркала можно также добавить в /etc/docker/daemon.json. Возможно, его будет нужно создать.

Используем приватное зеркало

У зеркалом с аутентификацией всё немного (много) по-другому.

docker login $DOMAIN

Вводите логин и пароль. И он сам создаёт запись в ~/.docker/config.json.

Но при docker pull alpine качать он будет не с зеркала. Ха-ха. Думали всё так просто?

Но зато, если явно указать путь через зеркало docker pull my-mirror.com/library/alpine, он пойдёт просить образ у зеркала.

Проблема давняя, вот issue.

Есть workround, на ваш страх и риск. Описан он в этой же issue.

После

docker login mirror-example.com

в ~/.docker/config.json получаете такой json:

{
	"auths": {
		"mirror-example.com": {
			"auth": "dXNlcjp1c2Vy"
		}
	}
}

Преобразуем. Нужно добавить ещё один registry с url https://index.docker.io/v1/ и таким же паролем:

{
  "auths": {
          "mirror-example.com": {
                  "auth": "dXNlcjp1c2Vy"
          },
          "https://index.docker.io/v1/": {
                  "auth": "dXNlcjp1c2Vy"
          }
  }
}

Docker Desktop. Windows, MacOS

У Docker Desktop зеркало можно добавить через Settings - Docker engine. Синтаксис такой же.
Пример с публичным зеркалом:

{
  "builder": {
    "gc": {
      "defaultKeepStorage": "20GB",
      "enabled": true
    }
  },
  "experimental": false,
  "registry-mirrors": [
    "https://mirror-example.com"
  ]
}

registry-mirrors добавлено после существующих настроек.

На винде можно, не лазя в GUI, поправить файл C:\Users\$USER\.docker\daemon.json.

В MacOS вроде в ~/.docker/daemon.json.

Но нужны ли зеркала в Docker Desktop? Скорее нет, чем да.

2. Прокси

Зеркала позволяют только пулить через себя. Просмотр образов, push и прочее через зеркало не сделаешь.

Но докер умеет ходить к своему же hub через прокси. Компания Docker продолжает:

Не осилил mirror? Пушить ещё надо? Ну вот через прокси тогда сходи, вот настройка. И пушить заодно сможешь ещё.

Linux

Тут есть некоторая путаница. Для того чтобы пулить образы через прокси, нужно делать systemd службу, а не править ~/.docker/daemon.json.

Переменные в daemon.json применяются при сборке и в запущенных контейнерах. Они не используются для docker cli. В документации это прописано:

These settings are used to configure proxy environment variables for containers only, and not used as proxy settings for the Docker CLI or the Docker Engine itself.

Поэтому если вам нужны прокси при build и/или для использования внутри контейнера, то для этого нужно прописать их как раз в daemon.json. Вот так вот.

Вам потребуется рабочий HTTP или SOCKS прокси. Продублирую инструкцию по настройке systemd с официального сайта:

Создаём каталог

sudo mkdir -p /etc/systemd/system/docker.service.d

Открываем /etc/systemd/system/docker.service.d/http-proxy.conf
и вставляем

[Service]
Environment="HTTP_PROXY=http://proxy-example.com:3128"
Environment="HTTPS_PROXY=http://proxy-example.com:3128"

Заставляем systemd найти новый сервис и рестартуем docker

sudo systemctl daemon-reload
sudo systemctl restart docker

Проверяем, что применилось. Должны показаться Environments

~> sudo systemctl show --property=Environment docker

Environment=HTTP_PROXY=http://proxy-example.com:3128 HTTPS_PROXY=http://proxy-example.com:3128

Теперь docker pull выкачивает образ через прокси

~> docker pull alpine:3.16
3.16: Pulling from library/alpine
a88dc8b54e91: Pull complete 
Digest: sha256:452e7292acee0ee16c332324d7de05fa2c99f9994ecc9f0779c602916a672ae4
Status: Downloaded newer image for alpine:3.16
docker.io/library/alpine:3.16

Docker desktop

Тут всё совсем просто.
Идём в Settings - Resourses - Proxies

И в HTTP и в HTTPS вставляем одно и то же значение.

http://proxy-example.com:3128

Теперь и поиск работает

Откуда взять прокси

Это уже другая тема, вкратце с чем у меня успешно завелось:

  1. Образ 3proxy от @paramtamtam:

docker run --rm \
    -p "3128:3128/tcp" \
    -p "1080:1080/tcp" \
    ghcr.io/tarampampam/3proxy:latest

Самый простой пример для тестов без аутентификации. Использовать недолго и с осторожностью.

  1. Прикручиваем логин и пароль, помещаем в compose

services:
  3proxy:
    image: "ghcr.io/tarampampam/3proxy:1.9.1"
    container_name: "3proxy"
    ports:
      - "3128:3128"
      - "1080:1080"
    environment:
      - PROXY_LOGIN=user
      - PROXY_PASSWORD=password
    restart: always

Теперь есть свои простые HTTP (3128 порт) и SOCKS (1080 порт) прокси.

С паролем переменные выглядят так

[Service]
Environment="HTTP_PROXY=http://user:password@proxy-example.com:3128"
Environment="HTTPS_PROXY=http://user:password@proxy-example.com:3128"
  1. Приложение Nekobox подключенное к серверу по Shadowsocks2022 (либо любые другие из семейства Xray). Обычный режим прокси, без tun и "системного прокси". По дефолту слушает порт 2080.

Вбиваем везде

http://localhost:2080

3. Роутеры, роутеры, роутеры

Docker Hub не первый и не последний. Самое удобное для дома/офиса - это маршрутизация в туннель/прокси по доменам на роутере. Никаких клиентов на девайсах, всем занимается роутер.

Ну и мой ТГ-канал по этой тематике.

Как видите, есть варианты получить доступ к Docker Hub. Делитесь опытом и мыслями в комментариях.

Комментарии (37)


  1. aax
    31.05.2024 07:42
    +18

    По части возведения железного занавеса у нашего Рос[**********] все больше и большее не менее достойных ситуативных союзников на Западе.

    Завет дедушки Черчилля живее всех живых.


    1. OldNileCrocodile
      31.05.2024 07:42
      +11

      Вангую, что список стран будет расширяться.


      1. NeoNN
        31.05.2024 07:42

        пусть расцветают сто цветов, пусть соперничают сто школ


    1. martin_sardin
      31.05.2024 07:42

      А где тут железный занавес? Тебя из страны не выпускают? Или на завод силой загнали? Оброк повысили до 99%? Не дают поменять барина?

      Про Европу и США, по твоей же логике, тоже можно сказать: "Всё меньше и меньше у них союзников в Евразии (РФ, Белоруссия и даже Грузия в последнее время), и в Азии, и на Ближнем Востоке, и в Африке, и в Латинском Америке. Никто уже с ними дело иметь не хочет."


  1. AlexOnBeta
    31.05.2024 07:42
    +14

    Рестарт докера


    Буквально вчера для себя узнал о возможности перезагрузки конфигурации без рестарта контейнеров (для настроек зеркала релоада достаточно). Оно вроде бы очевидно, и с nginx-ом таким пользуюсь периодически, а вот про докер не знал
    sudo systemctl reload docker


    1. me21
      31.05.2024 07:42
      +3

      Это, вроде, стандарт линуксовый. Системд при этом шлёт определённый сигнал процессу демона, а тот должен при этом перечитать конфиг.

      То есть это общий подход.


      1. Prototik
        31.05.2024 07:42
        +4

        Это не "стандарт" ни в коем разе. Если в юните нет ExecReload - то systemd вообще откажется что-либо делать при systemctl reload.
        Есть устоявшийся паттерн, что приложение может реализовать перезагрузку конфига через отправление ему определенного сигнала (обычно SIGHUP, но в сущности любого), но не существует некоего стандартного SIGRELOAD или ещё чего в таком духе. Да и не сигналами едиными, можно реализовать это через некий ipc (dbus, например), или через дёрганье http endpoint'a для http сервисов, кто во что горазд короче.


        1. mayorovp
          31.05.2024 07:42
          +1

          И тем не менее, systemctl reload - первое что нужно попробовать. Вот когда не сработало - тогда и нужно думать над рестартом.


    1. itdog Автор
      31.05.2024 07:42

      Добавил, спасибо. Но для удаления прокси всё равно потребуется рестарт


  1. tea
    31.05.2024 07:42
    +4

    docker pull работает через зеркало, а docker build все равно тащит с docker.io


    1. lazy_val
      31.05.2024 07:42

      Кстати хороший вопрос, ну стянул я с зеркала / через прокси условный alpine@latest, а дальше мне как на него то что мне нужно установить?


      1. lazy_val
        31.05.2024 07:42
        +1

        А, нашел

        Всем спасибо ))


    1. asarkhipov
      31.05.2024 07:42

      export HTTP_PROXY=socks5://192.168.1.153:1081
      export HTTPS_PROXY=socks5://192.168.1.153:1081

      И потом build тоже работает через прокси (прописать надо свой прокси, а экспорт поместить в bashrc/zshrc)


      1. tea
        31.05.2024 07:42
        +1

        Через proxy работает, не вопрос, не работает через зеркала. Вопрос был о том, как заставить build брать имиджи с зеркал? Через мой халявный proxy все грузится ооочень медленно, с зеркал гораздо быстрее. Вопрос как?


    1. PaulZi
      31.05.2024 07:42
      +2

      Чтобы докер билд работал, надо прописать прокси в конфигах, но только engine version>=23
      /etc/docker/daemon.json:
      {
      "proxies": {
      "http-proxy": "http://proxy.example.com:3128",
      "https-proxy": "https://proxy.example.com:3129",
      "no-proxy": "*.test.example.com,.example.org,127.0.0.0/8"
      }
      }


  1. SH33011
    31.05.2024 07:42

    про huecker забыли


    1. vvzvlad
      31.05.2024 07:42
      +1

      Не забыли, написали в статье же.


  1. aol-nnov
    31.05.2024 07:42
    +1

    не работает на маке прописывание зеркал.. Docker Desktop For Mac 4.30.0 там в виртуалке, кроме докера еще спрятан свой insecure registry, который невозможно исключить через конфиг и прокси, которые ломятся прямиком на заблокированный ресурс

    HTTP Proxy: http.docker.internal:3128
    HTTPS Proxy: http.docker.internal:3128
    No Proxy: hubproxy.docker.internal

    работает явное прописывание зеркало в имя образа (docker run mirror.gcr.io/hello-world) а так же, планирую попробовать colima, которая как-то более понятна в плане состава и настройки. (и работает через тот же virtualisation.framework)

    на линуксе, к слову, всё поднялось без сучка, без задоринки )


    1. mmjurov
      31.05.2024 07:42
      +2

      На маке будет работать зеркало, если включить containerd в настройках Docker Desktop.

      General > Use containerd for pulling and storing images


      1. aol-nnov
        31.05.2024 07:42

        Да, так, действительно, работает... Это было неочевидно. И все мои закешированные образы остались "за бортом". Случайно, не знаете метода перенести их из того режима в этот?


        1. mmjurov
          31.05.2024 07:42

          Я думаю, что способа перенести их нет. Хранилище containerd и хранилище docker образов разделены. Старые образы никуда не делись, просто демон вам их не показывает. Хотя физически они лежат и занимают место на диске.

          Чем вызвано именно такое поведение Docker Desktop - к сожалению не знаю, не разобрался пока.


          1. aol-nnov
            31.05.2024 07:42

            Да, я знаю причины такого поведения, думал, как-то можно.. даже заглядывал в виртуалку этого докер десктопа. Там всё, кхм, "очень необычно" по сравнению с обычным линуксом - поэтому и подумывал уже свалить на Colima...


  1. nochnoj
    31.05.2024 07:42

    Чтобы никто не портил образы, используйте шифрование образов.


    1. aol-nnov
      31.05.2024 07:42

      только не шифрование, а подписывание. docker notary, то-сё...


      1. nochnoj
        31.05.2024 07:42

        Я имел в виду шифрование, podman encryption-key , и т.д.


  1. Jhoony
    31.05.2024 07:42

    Работать под VPN проще всего, мне кажется в наших реалиях, у человека кто связан с it, должен быть VPN. Поднять свой не так уж сложно, и уж тем более дорого (вы же айтишники, зарабатывайте хорошо). Остальные способы тоже имеют место быть, но минимум танцев в бубном - vpn


  1. Jagmesh
    31.05.2024 07:42
    +1

    Хорошо, когда статья написана с хорошим чувством юмора)


  1. VasyaDurkin
    31.05.2024 07:42
    +1

    Извините, а для Synology и NAS решений вообще, есть выход?


    1. MrB4el
      31.05.2024 07:42

      А синолоджи докер или чего сложнее? На FreeNas Scale вообще кубик используется к примеру


    1. BAF285
      31.05.2024 07:42

      Конечно есть, на бафиста инструкция и видео ролик есть.


    1. NSGrid
      31.05.2024 07:42
      +1

      У меня подобная проблема, но к ней еще добавляется Watchtower. Я обошел блокировку по этому методу. https://bafista.ru/ustanovka-adguard-home-i-razblokirovka-docker-hub-clamav-i-tmdb/#comment-4272

      Это именно для владельцев NAS Synology c установленным Adguard Home особенно актуально. Но вот почему-то, хотя сам реестр в контейнер-менеджере DSM теперь стал открываться, у меня все контейнеры перестали обновляться автоматом через Watchtower.

      В логах самое интересное, это вот:

      2024/06/01 05:00:04    stderr    time="2024-06-01T05:00:04+03:00" level=debug msg="Got response to challenge request" header= status="403 Forbidden"

      Непонятно, где-то еще копать надо.


    1. dbodnar
      31.05.2024 07:42

      В Synology попробовал сначала в штатном Container Manager прописать гугловское зеркало https://mirror.gcr.io, но визуально ничего не поменялось, также выдавал ошибку. Помогло вручную через ssh командой docker pull обновить образ, думаю потому что зеркало уже было в настройках. Потом уже сбросил контейнер в Container Manager, и он запустился с новым образом.

      Но видимо пришла пора и часть трафика от NAS через VPN маршрутизировать.


      1. NSGrid
        31.05.2024 07:42

        У меня, как я писал в комменте выше все работает после способа с DNS с сайта бафисты. А Watchtower у вас работает?


  1. Ranger21
    31.05.2024 07:42

    А как перенастроить докер на локальный socks5 proxy? Через tor работать будет?


  1. rorosin
    31.05.2024 07:42

    у меня при запуске билда докерфайла ошибка (использую свое зеркало, докер пул все прекрасно тянет):
    ERROR [internal] load metadata for docker.io/tiangolo/uwsgi-nginx-flask:python3.8-alpine

    upd: починил уже просто рестартом докера


  1. Smile3D
    31.05.2024 07:42

    Казалось бы , то что обычно для всего мира для рф стало работать через пень колоду.


  1. sacai
    31.05.2024 07:42

    ну, допустим, как пуллить, разжевали. а мне вот пушить надо. и, кажется. прокси при этом не работают, docker login фэйлится. как быть?