docker pull повільно працює: виправлення DNS, MTU та проксі, які справді працюють

Було корисно?

Ви запускаєте docker pull, а воно повзе так, ніби завантаження відбувається через модем 1999 року, який ще й палає. Шари зупиняються. «Waiting» сидить і насміхається. CI-пайплайни таймаутяться. Хтось каже «мабуть Docker Hub», і ви відчуваєте, як душа залишає тіло.

Повільні pull’и рідко бувають «просто повільний інтернет». Зазвичай це один із трьох нудних лиходіїв: DNS, що бреше або зависає; MTU, що чорна діра для пакетів; або проксі, що перекроює реальність. Іноді всі три разом, складені як корпоративна лазанья.

Швидка процедура діагностики (перевірте це перш за все)

Це послідовність «перестаньте гадати». Оптимізована для реального світу: у вас один термінал, ніякого терпіння і пайплайн, що кричить.

1) Визначте: мережеве завантаження чи локальне витягування?

  • Якщо pull зависає на «Downloading» або «Waiting», думайте про DNS/проксі/MTU/CDN.
  • Якщо воно швидко проходить завантаження, а потім зависає на «Extracting» або диск завантажується на максимум, думайте про драйвер зберігання / файлову систему / ввід-вивід диска.

2) Перевірте затримку та коректність DNS

  • Запустіть запит до точної хост-імени реєстру (та вашого резолвера).
  • Якщо DNS займає >100ms послідовно або повертає дивні приватні IP, виправте DNS перед тим, як щось ще чіпати.

3) Перевірте path MTU (тест чорної діри)

  • Якщо TLS-handshake або великі завантаження шарів зависають, запустіть MTU-пробу з «do not fragment».
  • Проблеми MTU виглядають як «деякі речі працюють, великі трансфери зависають». Класика.

4) Перевірте налаштування проксі в оточенні та для daemon

  • Невідповідність між змінними проксі в оболонці користувача та конфігурацією Docker daemon викликає часткові дивні відмови.
  • Якщо у вас є проксі, що інспектує TLS, очікуйте додаткову затримку та іноді зламану поведінку HTTP/2.

5) Перевірте поведінку IPv6 (затримки подвійного стеку)

  • Зламаний IPv6 часто проявляється як довгі паузи перед відкатом на IPv4.
  • Не вимикайте IPv6 назавжди, якщо ви не готові до наслідків. Краще виправити маршрут/RA/DNS AAAA спочатку.

6) Лише потім: тротлінг реєстру та дзеркала

  • Ліміти та вибір CDN edge можуть бути реальними; не робіть їх першою теорією лише тому, що це заспокоює.
  • Дзеркало може допомогти, але додає нову область відмов. Вибирайте обережно.

Одна цитата для чесності: переказана думка від Richard Feynman: «Реальність має перевагу над піаром.» В опсах логи й трасування пакетів — це реальність.

Що насправді робить docker pull в мережі

docker pull — це не одне завантаження. Це конвеєр невеликих рішень:

  1. Розв’язання імен: резолв хост-імени реєстру (DNS; можливо кілька A/AAAA відповідей).
  2. TCP-з’єднання: встановлення з’єднання з edge-нодою (CDN) або сервісом реєстру.
  3. TLS-handshake: узгодження шифрування, валідація сертифікатів, можливо OCSP/CRL перевірки.
  4. Аутентифікація: отримання токена з auth-ендпоїнта; інколи додаткові редиректи.
  5. Отримання маніфесту: завантаження JSON-манифестів, вибір платформи/архітектури, локалізація шарів.
  6. Завантаження шарів: паралельні HTTP range-запити для блобів; повтори; експоненційний бекоф.
  7. Розпакування + витяг: інтенсивні по CPU і диску; багато метаданих; залежить від драйвера зберігання.
  8. Облік в сховищі контенту: containerd/Docker оновлюють локальні метадані; збір сміття пізніше.

Повільність ховається на будь-якому етапі. DNS може додавати секунди на кожне ім’я. MTU-проблеми можуть зависати великі TLS-записи. Проксі можуть ламати keep-alive, тож кожен шар платитиме за повторне встановлення з’єднання. А зберігання може перетворити «завантажено» на «все ще чекаємо», бо витяг заштовхується.

Жарт #1: «Завжди це DNS» смішно, бо це правда — і бо це єдине, що тримає нас від крику.

Цікаві факти та коротка історія

  • Розповсюдження образів Docker багато чого позичило у Git. Шари поводяться як повторно використовувані коміти: завантажив раз — використовуй завжди (поки не перестає).
  • Протокол реєстру еволюціонував. Registry v1 був застарілий; v2 покращив адресацію вмісту й дозволив кращий кешинг та інтеграцію CDN.
  • Адресація за вмістом важлива. Сучасні реєстри ключують блоби за digest; кешинг працює, бо той самий digest — то той самий вміст скрізь.
  • containerd — це робоча конячка. Багато «Docker» pull’ів на сучасних системах фактично проходять через content store і snapshotters containerd.
  • HTTP range-запити поширені. Великі блоби можуть завантажуватись шматками; нестабільні мережі можуть викликати повторні часткові завантаження, що виглядає як «повільно».
  • PMTUD мучить з 1990-х. Фаєрволи, що крадуть ICMP «Fragmentation Needed», досі ламають сучасні навантаження.
  • Корпоративні проксі змінили профіль відмов. Інспекція TLS, таймаути простою та переписування заголовків викликають проблеми, що виглядають як «Docker глючить».
  • IPv6 подвійний стек може підсилити затримки. Happy Eyeballs допомагає, але зламаний IPv6 все одно може додавати секунди затримки на підключення.
  • Витяг шарів — це багато метаданих. Мільйони дрібних файлів (поширено в рантаймах мов) карають overlay-файлові системи та повільні диски.

Практичні діагностичні завдання (команди, виходи, рішення)

Вам потрібні завдання, що дають доказ. Ось більше ніж на десяток. Кожне містить: виконувану команду, що означає її вихід і що робити далі.

Завдання 1: Заміряйте pull з debug-логами

cr0x@server:~$ sudo docker -D pull alpine:3.19
DEBU[0000] pulling image                               image=alpine:3.19
3.19: Pulling from library/alpine
DEBU[0001] resolved tags                               ref=alpine:3.19
DEBU[0002] received manifest                           digest=sha256:...
DEBU[0003] fetching layer                              digest=sha256:...
f56be85fc22e: Downloading [==================>                                ]  1.2MB/3.4MB
DEBU[0015] attempting next endpoint
DEBU[0035] Download complete
DEBU[0036] Extracting
DEBU[0068] Pull complete

Що це означає: Debug-вихід показує, куди йде час: розв’язання, отримання маніфесту, завантаження блобів, витяг.

Рішення: Якщо затримка перед «fetching layer», переслідуйте DNS/auth/проксі. Якщо це «Downloading» з повторними спробами, дивіться MTU/проксі/CDN. Якщо «Extracting» — перевіряйте зберігання/CPU.

Завдання 2: Підтвердьте версії daemon і клієнта (зміни в поведінці)

cr0x@server:~$ docker version
Client: Docker Engine - Community
 Version:           26.1.1
 API version:       1.45
 Go version:        go1.22.2
 OS/Arch:           linux/amd64

Server: Docker Engine - Community
 Engine:
  Version:          26.1.1
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.22.2
  OS/Arch:          linux/amd64
 containerd:
  Version:          1.7.19
 runc:
  Version:          1.1.12

Що це означає: Різні версії Docker/containerd мають різні дефолти для мережі, конкурентності та поведінки з реєстрами.

Рішення: Якщо у вас дуже стара (або надто нова) версія і ви бачите дивні речі, протестуйте на відомій добрій версії перш ніж переробляти мережу.

Завдання 3: Визначте, які кінцеві точки реєстру використовуються

cr0x@server:~$ docker pull --quiet busybox:latest && docker image inspect busybox:latest --format '{{.RepoDigests}}'
sha256:...
[docker.io/library/busybox@sha256:3f2...]

Що це означає: Ви тягнете з Docker Hub (docker.io), що часто редиректить на CDN-ендпоїнти.

Рішення: Якщо повільний лише Docker Hub, а інші реєстри в нормі — підозрюйте маршрутизацію CDN, ліміти або політику проксі для цих доменів.

Завдання 4: Перегляньте DNS-налаштування daemon і загальну конфігурацію

cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
  "dns": ["10.10.0.53", "1.1.1.1"],
  "log-level": "info"
}

Що це означає: Docker daemon може використовувати специфічні резолвери, відмінні від резолверу хоста.

Рішення: Якщо ці DNS-сервери повільні/недосяжні з network namespace daemon’а, виправте або приберіть їх. Віддавайте перевагу швидким локальним кешуючим резолверам вашої організації.

Завдання 5: Заміряйте час DNS-запиту до резолвера, який використовує Docker

cr0x@server:~$ dig @10.10.0.53 registry-1.docker.io +stats +tries=1 +time=2
;; ANSWER SECTION:
registry-1.docker.io. 60 IN A 54.87.12.34
registry-1.docker.io. 60 IN A 54.87.98.21

;; Query time: 420 msec
;; SERVER: 10.10.0.53#53(10.10.0.53)
;; WHEN: Tue Jan 02 10:11:07 UTC 2026
;; MSG SIZE  rcvd: 92

Що це означає: 420ms на запит — жорстоко, коли pull запускає багато запитів (auth-ендпоїнти, сервіс токенів, імена CDN).

Рішення: Виправте продуктивність DNS перш за все: локальний кеш, розміщення резолвера або зменшення латентності до upstream. Ще не чіпайте MTU.

Завдання 6: Перевірте жахи search-domain для DNS

cr0x@server:~$ cat /etc/resolv.conf
search corp.example internal.example
nameserver 10.10.0.53
options ndots:5 timeout:2 attempts:3

Що це означає: ndots:5 означає, що багато імен трактуються як «відносні» спочатку, викликаючи кілька марних запитів на кожен lookup.

Рішення: В середовищах, де ви тягнете з багатьох зовнішніх регістрів, подумайте про зменшення ndots (зазвичай до 1–2) або звуження пошукових доменів. Тестуйте обережно; це може порушити внутрішні очікування імен.

Завдання 7: Перевірте, чи використовується systemd-resolved (і чи він некоректно працює)

cr0x@server:~$ resolvectl status
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Current DNS Server: 10.10.0.53
       DNS Servers: 10.10.0.53 1.1.1.1

Що це означає: Ви використовуєте stub-резолвер; Docker міг прочитати stub IP на кшталт 127.0.0.53, і це може або не може працювати в залежності від namespace та конфігурації.

Рішення: Якщо контейнери або daemon не можуть досягти stub, вкажіть явні DNS-сервери в daemon.json або підлаштуйте налаштування резолвера.

Завдання 8: Спостерігайте спроби підключення в реальному часі (IPv6 vs IPv4, повтори)

cr0x@server:~$ sudo ss -tpn dst :443 | head
ESTAB 0 0 10.20.30.40:51524 54.87.12.34:443 users:(("dockerd",pid=1321,fd=62))
SYN-SENT 0 1 10.20.30.40:51526 2600:1f18:2148:...:443 users:(("dockerd",pid=1321,fd=63))

Що це означає: Docker пробує IPv6 і зависає в SYN-SENT, тоді як IPv4 працює. Це «податок таймауту подвійного стеку».

Рішення: Виправте маршрутизацію IPv6/AAAА або тимчасово віддайте перевагу IPv4 для daemon, якщо треба негайно зупинити кровотечу.

Завдання 9: Протестуйте MTU пінгом без фрагментації

cr0x@server:~$ ping -M do -s 1472 registry-1.docker.io -c 2
PING registry-1.docker.io (54.87.12.34) 1472(1500) bytes of data.
ping: local error: message too long, mtu=1460
ping: local error: message too long, mtu=1460

--- registry-1.docker.io ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1024ms

Що це означає: Ваш path MTU — 1460 (типово, коли є оверхед: VPN, тунелі, деякі cloud-фабрики). Спроба посилати 1500-байтові фрейми призводить до чорної діри або помилок.

Рішення: Встановіть MTU мосту Docker, щоб відповідати реальному path MTU (або трохи нижче), та/або виправте мережу, щоб PMTUD працював (ICMP «frag needed» має проходити).

Завдання 10: Підтвердіть MTU інтерфейсу та оверхед тунелів

cr0x@server:~$ ip -br link
lo               UNKNOWN        00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
eth0             UP             02:42:ac:11:00:02 <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
tun0             UP             00:00:00:00:00:00 <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420
docker0          UP             02:42:8f:01:23:45 <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500

Що це означає: Ваш VPN-тунель має MTU 1420, але Docker bridge — 1500. Контейнери можуть відправляти пакети, що надто великі для реального шляху, і PMTUD може не врятувати ситуацію.

Рішення: Узгодьте MTU Docker з найменшим ефективним MTU на шляху виходу.

Завдання 11: Перевірте proxy-конфіг daemon (systemd drop-in)

cr0x@server:~$ sudo systemctl cat docker | sed -n '1,120p'
# /lib/systemd/system/docker.service
[Service]
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

# /etc/systemd/system/docker.service.d/proxy.conf
[Service]
Environment="HTTP_PROXY=http://proxy.corp.local:3128"
Environment="HTTPS_PROXY=http://proxy.corp.local:3128"
Environment="NO_PROXY=localhost,127.0.0.1,.corp.local"

Що це означає: Docker daemon налаштований на використання проксі. Це впливає на доступ до реєстру, термінацію TLS і іноді продуктивність радикально.

Рішення: Якщо проксі потрібний, переконайтеся, що він стабільний і підтримує сучасний TLS. Якщо проксі опціональний, спробуйте обходити його для хостів реєстру через NO_PROXY, щоб зменшити затримки й складність.

Завдання 12: Переконайтеся, що змінні проксі в оболонці вас не вводять в оману

cr0x@server:~$ env | egrep -i 'http_proxy|https_proxy|no_proxy'
HTTP_PROXY=http://proxy.corp.local:3128
HTTPS_PROXY=http://proxy.corp.local:3128
NO_PROXY=localhost,127.0.0.1

Що це означає: Ваші shell-налаштування відрізняються від daemon’а. Ви можете робити curl у shell і воно працює, тоді як Docker daemon йде іншим шляхом — або навпаки.

Рішення: Считайте конфігурацію daemon авторитетною для pull’ів. Узгодьте налаштування проксі між daemon, CI-раннерами і хостом.

Завдання 13: Заміряйте час TLS-handshake до ймовірних endpoints

cr0x@server:~$ time bash -lc 'echo | openssl s_client -connect registry-1.docker.io:443 -servername registry-1.docker.io -brief 2>/dev/null | head -n 3'
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_128_GCM_SHA256

real    0m2.418s
user    0m0.032s
sys     0m0.012s

Що це означає: 2.4 секунди для handshake — підозріло, якщо ви не на високолатентному каналі. Проксі з TLS-інспекцією та зламаний IPv6 часто викликають це.

Рішення: Якщо handshake повільний, перевірте шлях DNS, проксі та IPv6-спроби перш ніж звинувачувати Docker.

Завдання 14: Спостерігайте ICMP «frag needed» та повторні передавання під час pull’ів

cr0x@server:~$ sudo tcpdump -ni eth0 '(icmp and (icmp[0]=3 and icmp[1]=4)) or (tcp and port 443 and (tcp[tcpflags] & (tcp-rst) != 0))' -c 20
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
IP 10.20.30.40 > 54.87.12.34: ICMP 10.20.30.40 unreachable - need to frag (mtu 1460), length 556
IP 10.20.30.40.51524 > 54.87.12.34.443: Flags [R], seq 123, win 0, length 0

Що це означає: Побачити «need to frag» — це насправді добре: PMTUD працює і повідомляє вам MTU. Побачити RST може означати скидання проксі, таймаути простою або поведінку middlebox.

Рішення: Якщо бачите «need to frag», але завантаження все ще зависають, десь на шляху щось блокує ICMP. Якщо бачите скидання, зосередьтеся на політиках проксі/фаєрвола та TCP-таймерах простою.

Завдання 15: Перевірте вузьке місце при витяганні: диск і CPU

cr0x@server:~$ sudo iostat -xz 1 3
Linux 6.5.0 (server)  01/02/2026  _x86_64_  (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.00    0.00    6.00   42.00    0.00   40.00

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await  aqu-sz  %util
nvme0n1          5.00    80.00     0.00   0.00   10.00    16.00  450.00  62000.00   35.00   18.00  98.00

Що це означає: Високий iowait і майже 100% завантаження диска означає, що витяг обмежений зберіганням. «Повільний pull» — це не мережа.

Рішення: Вирішіть питання вибору файлової системи/snapshotter, продуктивності диска та структури образів (занадто багато дрібних файлів). Розгляньте переміщення Docker root на швидше сховище.

Режими відмови DNS, що роблять pull’и «повільними»

DNS — це перша доміно. Повільний DNS виглядає як повільні завантаження, бо кожний новий ендпоїнт або редирект починається з lookup’у. І реєстри не шкодують імен: сервіс токенів, ендпоїнти реєстру, CDN-edge’и, іноді гео-залежні відповіді.

Поширені шаблони DNS, що шкодять pull’ам

  • Повільні рекурсивні резолвери: централізовані DNS-сервери через WAN; кожний запит оплачує RTT.
  • Зламаний кеш: резолвери, що не кешують ефективно (помилкова конфігурація, маленький кеш, агресивна політика видалення).
  • Підсилення через search domains: ndots і довгі пошукові списки викликають багато марних запитів на ім’я.
  • Split-horizon сюрпризи: внутрішній DNS повертає приватні або перехоплювані IP для публічних реєстрів.
  • Затримки валідації DNSSEC: відмови валідації призводять до повторів і fallback’ів.

Що робити (досить категорично)

  • Використовуйте локальний кешуючий резолвер для билд-нод. Якщо ваш «центральний резолвер» через континент — це архітектурна помилка.
  • Не жорстко прописуйте публічні резолвери по рефлексу. Вони можуть бути швидкими, але також порушити корпоративну маршрутизацію, split DNS або блокуватись.
  • Виправляйте ndots/search domains свідомо. Люди копіюють конфіги резолвера, ніби це нешкідливо. Це не так.

Конкретне виправлення: вкажіть DNS для Docker daemon явно

Якщо контейнери (не хост) страждають від затримок DNS, вкажіть DNS для daemon. Це не завжди вплине напряму на pull’и реєстру (вони робляться daemon’ом), але запобігає подальшому болю, коли контейнери починають працювати.

cr0x@server:~$ sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
  "dns": ["10.10.0.53", "10.10.0.54"],
  "log-level": "info"
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

Рішення: Якщо рестарт це виправив, у вас була дрейф-резолвера або daemon використовував stub, до якого він не мав надійного доступу.

MTU та PMTUD: мовчазна таємниця вбивства пакетів

Проблеми MTU зазвичай не падають голосно. Вони провалюються як нудна нарада: всі технічно присутні, але нічого не рухається вперед.

Шаблон: дрібні запити працюють, великі трансфери зависають. Ви можете пінгувати. Ви можете отримати маленький маніфест. Потім блоб починає завантажуватись і зупиняється на підозріло постійному байт-кордоні або зависає назавжди з повторними спробами.

Як MTU ламає pull’и

  • Чорна діра PMTUD: ICMP «Fragmentation Needed» блокується фаєрволом/middlebox’ом, тож кінцеві точки продовжують надсилати пакети, що ніколи не доходять.
  • Оверхед оверлеїв і тунелів: VPN, GRE, VXLAN, WireGuard, IPSec зменшують ефективний MTU.
  • Несумісний bridge MTU: мережі Docker на 1500, тоді як реальний шлях менший.

Виправлення: встановіть MTU для Docker мережі свідомо

Ви можете встановити дефолтний MTU для bridge-мереж Docker через конфіг daemon. Оберіть значення, що підходить для вашого egress-шляху. Якщо ваш тунель має 1420 — встановіть Docker на 1400–1420, щоб лишилось місце на оверхед.

cr0x@server:~$ sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
  "mtu": 1420,
  "log-level": "info"
}
EOF
sudo systemctl restart docker

Що це означає: Нові мережі використовуватимуть цей MTU; існуючі мережі можливо доведеться пересоздати.

Рішення: Якщо pull’и стабілізуються відразу після вирівнювання MTU (особливо через VPN), ви знайшли винуватця. Потім виправте PMTUD правильно, щоб не латати мережу.

Виправлення: дозволіть ICMP для PMTUD

Якщо ви контролюєте фаєрволи, дозволіть конкретні типи ICMP, потрібні для PMTUD (IPv4 «frag needed», IPv6 Packet Too Big). Блокування їх — це стара забобонна практика.

Жарт #2: Деякі мережі блокують ICMP «з міркувань безпеки», що схоже на зняття дорожніх знаків, щоб не було перевищення швидкості.

Корпоративні проксі: коли «допомога» стає шкідливою

Проксі або необхідний інструмент відповідності, або збирач податків продуктивності. Іноді — обидва. Docker pull навантажує проксі кількома способами: багато паралельних з’єднань, великі TLS-потоки, редиректи та мікс ендпоїнтів.

Поведінка проксі, що сповільнює pull’и

  • Короткі таймаути простою: завантаження блобів паузуються, проксі падає з’єднання, клієнт повторно підключається.
  • Інспекція TLS: додає витрати на handshake, ламає повторне використання сесій і може конфліктувати з HTTP/2 чи сучасними шифрами.
  • Обмеження паралелізму з’єднань: проксі уповільнює паралельні завантаження, тож кожен блоб повзе.
  • Неправильний NO_PROXY: daemon шле трафік реєстру через проксі, коли не мав би.

Виправлення: налаштуйте proxy для daemon правильно (і повністю)

Вкажіть проксі для Docker daemon через systemd drop-in, а не лише в shell-оточенні.

cr0x@server:~$ sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/proxy.conf >/dev/null <<'EOF'
[Service]
Environment="HTTP_PROXY=http://proxy.corp.local:3128"
Environment="HTTPS_PROXY=http://proxy.corp.local:3128"
Environment="NO_PROXY=localhost,127.0.0.1,::1,registry.corp.local,.corp.local"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

Що це означає: Тепер daemon послідовно використовує налаштування проксі. Це прибирає розрив «в curl працює, а в docker — ні».

Рішення: Якщо pull’и покращаться, ваш попередній стан був неконсистентним. Якщо стане гірше — ваш проксі вузьке місце: обійдіть його для реєстрів, де дозволено.

Виправлення: додати локальне дзеркало реєстру (обережно)

Дзеркало може усунути проходження через проксі і зменшити WAN-трафік. Але воно також може стати вашим новим єдиним місцем відмов. Якщо запускаєте дзеркало — експлуатуйте його як продакшн: моніторинг, місткість сховища, TLS-гігієна та передбачуване видалення кешу.

IPv6, подвійний стек і довгі таймаути

Зламаний IPv6 — це особливий вид болю: все здається «переважно в порядку», але кожне нове з’єднання платить податок часу, оскільки клієнти спочатку пробують IPv6, чекають, потім відступають.

Діагностика затримок подвійного стеку

Шукайте SYN-SENT по v6 або довгі паузи перед першим байтом. Також перевірте, чи DNS повертає AAAA, що нікуди не маршрутизуються з ваших хостів.

Пом’якшення (обирайте найменш погане)

  • Виправте IPv6 правильно: коректні маршрути, RA, правила фаєрвола і DNS AAAA відповіді.
  • Віддайте перевагу IPv4 тимчасово: як екстрений захід, якщо CI впав і вам потрібні образи зараз.

Якщо обираєте тимчасовий пріоритет IPv4 — задокументуйте це як борг. «Тимчасові» налаштування мають властивість ставати археологією.

Тротлінг реєстру, ліміти і дивності CDN

Так, іноді це справді реєстр. Ліміти за запитами, тротлінг або невдалий CDN-edge можуть перетворити pull’и на болото. Але діагностуйте спочатку. Не використовуйте «інтернет повільний» як стратегію моніторингу.

Ознаки, що вас тротлять

  • Pull’и швидкі в поза-години і повільні в робочі години.
  • Помилки згадують надто багато запитів або ви бачите часте 429-подібне поводження в інструментах.
  • Лише один реєстр повільний; інші в нормі.

Виправлення, які зазвичай варті зусиль

  • Аутентифікуйте pull’и, де можливо, щоб отримати вищі ліміти.
  • Використовуйте внутрішній кеш/дзеркало для флотів CI, особливо якщо ви часто перебудовуєте.
  • Зменшіть churn образів: закріплюйте базові образи, уникайте перебудов, що інвалідовують кожен шар.

Сховище та витяг: коли мережа не є вузьким місцем

Брудний секрет: багато «повільних pull’ів» — це швидкі завантаження і повільне витягування. Особливо на shared-ранерах, thin-provisioned дисках або overlay-на-overlay ситуаціях.

Витяг — це прихований бенчмарк зберігання

  • Багато дрібних файлів: метаданичний і inode-рух.
  • Стиснення: час CPU, потім записи.
  • Поведінка overlay-файлової системи: copy-up штрафи, dentry-навантаження та write amplification.

Виправлення, що мають значення

  • Перемістіть Docker data root на швидше сховище (/var/lib/docker на NVMe краще за мережеві диски).
  • Зменшуйте розміри образів і кількість файлів у шарах (multi-stage builds, slim base images, очищення кешів).
  • На завантажених хостах плануйте pull’и так, щоб уникнути конкуренції за I/O (або ізолюйте билд-ноди).

Три корпоративні історії з польових робіт

Міні-історія 1: Інцидент через хибне припущення

Компанія мала baseline мережевої безпеки «secure-by-default». Одна команда розгорнула нові правила фаєрвола, щоб «затягнути гайки» між билд-воркерами та інтернетом. Pull’и не падали цілком; вони просто стали нестерпно повільними і іноді зависали. Це гірше, бо виглядає як випадкова флейка, а не чіткий аутедж.

Припущення було просте і неправильне: «ICMP не потрібний». Правила блокували ICMP тип 3 код 4 (IPv4 fragmentation needed) і еквівалент для IPv6. Більшість веб-серфінгу все ще працювала. Малі HTTPS-запити були в порядку. А Docker pull’и? Великі TLS-записи і блоби стикалися з ефективним MTU, зникали в чорній дірі й сиділи там, доки повтори й бекоф не зробили пайплайн схожим на примару.

On-call провів години, ганяючи «проблеми Docker Hub», потім «проксі», потім «можливо наші раннери навантажені». Хтось нарешті запустив MTU-пробу і отримав курячу хвилю: path MTU був нижчий за MTU інтерфейсу, і PMTUD не міг повідомити про це.

Виправлення не було героїчним. Вони дозволили потрібні типи ICMP на egress-шляху і встановили консервативний Docker MTU на раннерах, що йшли через тунелі. Часи pull’ів повернулись до нормальних. Постмортем-дескриптор був ціннішим: тепер кожна зміна мережі, що зачіпає egress, отримує перевірку MTU/PMTUD як стандартну умову.

Міні-історія 2: Оптимізація, що обернулась проти

Команда платформи захотіла швидших билдiв, тож вони ввели дзеркало реєстру в кожному регіоні. Звучить розумно: локалізувати трафік, зменшити WAN-ширину, зробити CI швидким. Вони швидко розгорнули, і спочатку все виглядало чудово.

Потім зворотний ефект. Дзеркало мало малий диск і агресивне витіснення. Коли кілька команд почали відправляти більші образи (рантайми мов, ML-залежності, debug-symbols — оберіть отруту), дзеркало постійно «перемелювалося». Блоб кешувався, витіснявся і завантажувався знову в той же день. Проксі перед дзеркалом також мав короткий idle timeout; часткові завантаження скидалися, викликаючи повтори, що підвищувало навантаження і викликало ще більше скидань. Маленький петля зворотного зв’язку misery.

Симптом був оманливим: pull’и іноді швидкі, іноді катастрофічно повільні, навіть в одному регіоні. Інженери звинувачували реєстр, потім Docker, потім власні образи. Справжня причина була «оптимізація»: кеш занадто малий, щоб бути стабільним, плюс таймаути налаштовані для веб-сторінок, а не для сотень мегабайт блобів.

Кінцеве виправлення — нудне: правильно підібрана місткість сховища, налаштування таймаутів, моніторинг cache hit rate і відключення проксі-поведінки, що ламала довгі трансфери. Також політика: дзеркала — це продакшн-сервіси, а не побічні проекти на залишкових ВМ.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Фінансова компанія мала суворий контроль вихідного трафіку. У них також була звичка, яку я бажаю бачити скрізь: кожен билд-воркер і Kubernetes-нод запускали однакові базові діагностики при старті й публікували результати внутрішньо.

Одного понеділка розробники скаржились, що pull’и повільні «повсюди». SRE на чергуванні не відкривав Slack-дебати про те, чи Docker Hub має поганий день. Вони відкрили дешборд діагностик нод. Часи DNS-запитів втричі зросли на підмножині підмереж. MTU-проби були нормальні. Диск I/O нормальний. Це був DNS, прозоро і без романтики.

Причина — failover резолвера, що технічно працював, але перемістив трафік у віддаленіший сайт. Латентність зросла. Кеші охолодились. Pull’и реєстру раптом платили додаткові мілісекунди десятки разів за pull. Недостатньо, щоб кричати «аутедж», але цілком достатньо, щоб розплавити пропускну здатність CI.

Вони повернули пріоритет резолвера, прогріли кеші і скарга зникла. Рятівна практика не була хитрим інструментом; це було послідовне базове вимірювання. Нудно. Правильно. Надзвичайно ефективно, коли кімната наповнена теоріями.

Поширені помилки (симптом → корінь → виправлення)

  • Симптом: Pull зависає на «Downloading» приблизно на тому ж відсотку кожного разу.
    Корінь: MTU/PMTUD чорна діра; великі пакети відкидаються, немає ICMP-фідбеку.
    Виправлення: Пробуйте MTU з ping -M do, дозволіть frag-needed/Packet Too Big, встановіть Docker MTU відповідно до тунельного шляху.
  • Симптом: Довга пауза перед будь-яким прогресом, потім воно раптово прискорюється.
    Корінь: Зламаний IPv6 із повільним відкатом на IPv4; існують AAAA, але немає маршруту.
    Виправлення: Виправте маршрути/фаєрвол IPv6/DNS; як пом’якшення — надайте перевагу IPv4 для daemon або приберіть погані AAAA в резолвері, якщо ви ним володієте.
  • Симптом: curl до реєстру швидкий, але docker pull повільний або таймаутиться.
    Корінь: Проксі split-brain: shell-змінні проксі відрізняються від конфігурації daemon.
    Виправлення: Налаштуйте проксі через systemd drop-in для Docker daemon; узгодьте NO_PROXY.
  • Симптом: Pull швидкий на ноутбуках, повільний на серверах.
    Корінь: Сервери використовують інші DNS резолвери/пошукові домени або проходять через VPN/тунелі з меншим MTU.
    Виправлення: Порівняйте резолвери та MTU; узгодьте daemon DNS і MTU з фактичним шляхом сервера.
  • Симптом: «Downloading» швидкий, «Extracting» триває вічно, CPU в нормі, але I/O завантажений.
    Корінь: Повільний диск, thin-provisioned сховище, overlay overhead, занадто багато дрібних файлів.
    Виправлення: Перемістіть Docker root на швидше сховище; оптимізуйте образи; зменшуйте кількість шарів; розгляньте ізоляцію раннерів.
  • Симптом: Лише один реєстр повільний; інші нормальні.
    Корінь: Вибір CDN-edge, лімітування або політика проксі для певних доменів.
    Виправлення: Аутентифікуйте pull’и; використовуйте дзеркало; налаштуйте обходження проксі; перевірте DNS-відповіді (гео, split-horizon).
  • Симптом: Швидкість pull’а дико коливається хвилина до хвилини.
    Корінь: Скидання проксі-з’єднань, churn кешу дзеркала або мережеві затори з повторними спробами.
    Виправлення: Налаштуйте таймаути проксі, правильно підберіть розмір дзеркала, міряйте cache hit rate, зменшіть паралельні pull’и під час піків.

Чек-листи / покроковий план

Чек-лист A: Тріаж на одній машині (15 хвилин)

  1. Запустіть docker -D pull і зауважте, куди йде час (resolve/auth/download/extract).
  2. Заміряйте час DNS для хосту реєстру з dig +stats.
  3. Перевірте /etc/resolv.conf на ndots і пошукові домени, що посилюють запити.
  4. Перевірте невідповідність MTU: ip -br link і ping -M do проба.
  5. Перевірте proxy-налаштування daemon через systemctl cat docker.
  6. Перевірте спроби IPv6 підключень з ss -tpn.
  7. Якщо витяг повільний — підтвердіть це з iostat і припиніть звинувачувати мережу.

Чек-лист B: Флаєт рішення (що масштабується за межі одної машини)

  1. Уніфікуйте конфіг daemon: DNS-сервери, MTU, проксі, рівень логів.
  2. Базові тести при старті: латентність DNS, MTU-проба до ключових endpoint’ів, sanity disk I/O.
  3. Централізована спостережуваність: тривалість pull’ів, частота відмов та куди йде час (download vs extract).
  4. Впроваджуйте дзеркала свідомо: ставте їх як продакшн — ємність, моніторинг, TTL/політика видалення, таймаути.
  5. Гігієна мережевих політик: явно дозволяйте ICMP типи для PMTUD; документуйте це, щоб не «закритись» знову.

Чек-лист C: Гігієна образів (це під вашим контролем)

  1. Фіксуйте базові образи, щоб максимально використовувати повторне використання шарів.
  2. Використовуйте multi-stage builds, щоб зменшити розмір і кількість файлів.
  3. Не вбудовуйте кеші менеджерів пакетів у шари.
  4. Переважайте менше, але змістовних шарів замість десятків мікрошарів.

Питання та відповіді

1) Чому docker pull здається повільнішим за завантаження великого файлу?

Бо це не один файл. Це множинні DNS lookup’и, auth виклики, редиректи, паралельні завантаження блобів, а потім стиснення + витяг. Будь-яке слабке місце перетворює це на «Docker повільний».

2) Я можу швидко резолвити DNS на хості. Чому Docker все одно повільний?

Daemon може використовувати інші DNS-налаштування, ніж ваша оболонка або контейнери. Перевірте /etc/docker/daemon.json, поведінку systemd-resolved і чи daemon спрямований на stub-резолвер, до якого він не має надійного доступу.

3) Який найшвидший спосіб підтвердити проблему MTU?

Використайте пінг «не фрагментувати» з бінарним пошуком payload-розміру. Якщо великі пакети падають, а менші працюють, особливо коли ви на VPN/тунелях — вважайте MTU підозрюваним.

4) Чи просто виставити Docker MTU 1400 повсюди?

Ні. Це тупий інструмент. Може погіршити продуктивність на чистих мережах і приховати реальні дефекти PMTUD/фаєрвола. Встановлюйте MTU на основі фактичних обмежень шляху і виправляйте ICMP, щоб не покладатись на забобони.

5) Чому вимкнення IPv6 «виправляє» проблему?

Якщо IPv6 зламаний, клієнти витрачають час на спроби, перш ніж відкотитись. Вимкнення IPv6 змушує використовувати IPv4 одразу, уникаючи таймаутів. Краще: зробіть IPv6 працездатним або перестаньте оголошувати некоректні AAAA.

6) Мій проксі обов’язковий. Що просити в мережевого?

Попросіть: довші idle таймаути для великих завантажень, підтримку сучасного TLS без ламання reuse сесій, ємність для багатьох паралельних з’єднань і чіткий список обходу для хостів реєстрів, якщо політика дозволяє.

7) Pull повільний лише на Kubernetes-нодах. Чому?

Ноди часто мають інші шляхи egress (NAT-шлюзи, CNI-оверлеї, строгіші фаєрволи, інший DNS). Також kubelet використовує containerd напряму, тож налаштування проксі/DNS можуть відрізнятися від ваших припущень про «Docker host».

8) Як відрізнити «завантаження повільне» від «витяг повільний» без здогадок?

Використайте docker -D pull і подивіться, де воно зависає, потім підтвердіть за метриками диска. Якщо I/O завантажений під час «Extracting», мережа не є вузьким місцем.

9) Чи завжди варто ставити дзеркало реєстру?

Воно того варте, коли у вас багато вузлів, що часто тягнуть спільні образи, особливо в CI. Немає сенсу, якщо ви не можете надійно його експлуатувати. Ненадійне дзеркало — креативний спосіб вигадати аутеджі.

10) Чому pull’и іноді повільнішають після «оптимізації» DNS?

Бо «оптимізація» часто означає зміну резолверів без розуміння split DNS, поведінки кеша або гео-відповідей. Швидший RTT резолвера не допоможе, якщо він повертає гірший CDN-edge або ламає корпоративну маршрутизацію.

Висновок: наступні кроки з найбільшим ефектом

Якщо docker pull болісно повільний, ставтеся до цього як до інциденту в продакшні: збирайте докази, ізолюйте етап і виправляйте справжній вузький гол. Почніть з латентності та коректності DNS, потім MTU/PMTUD, потім поведінку проксі, потім IPv6, і лише потім сперечайтеся з реєстром.

Практичні наступні кроки:

  1. Запустіть швидку процедуру діагностики на одній ураженій машині і на одній відомо робочій. Зробіть diff результатів.
  2. Уніфікуйте конфіг Docker daemon по флоту (DNS, MTU, проксі) і перезапускайте свідомо.
  3. Додайте базові DNS/MTU перевірки до процесу підготовки нод, щоб ловити це, перш ніж CI стане перформанс-артом.
  4. Якщо витяг — ваш вузький момент, перемістіть Docker storage на швидші диски і очистіть образи. Налаштування мережі не врятує хост, який обмежений диском.
← Попередня
Docker мережі: bridge vs host vs macvlan — оберіть те, що не підведе пізніше
Наступна →
ZFS Readonly: трюк проти рансомвару, який можна розгорнути сьогодні

Залишити коментар