Vous lancez docker pull et ça rampe comme si vous téléchargiez via un modem 56k de 1999 en feu. Les couches se figent. « Waiting » reste là en vous narguant. Les pipelines CI expirent. Quelqu’un dit « c’est Docker Hub », et vous avez l’impression de perdre votre âme.
Les pulls lents ne sont rarement « juste une connexion lente ». Il s’agit le plus souvent d’un des trois méchants ennuyeux : un DNS qui ment ou qui bloque, un MTU qui crée des trous noirs pour les paquets, ou des proxies qui réécrivent la réalité. Parfois les trois ensemble, empilés comme une lasagne d’entreprise.
Mode opératoire de diagnostic rapide (vérifiez d’abord ceci)
Ceci est la séquence « cesser de deviner ». Elle est optimisée pour le monde réel : vous avez un terminal, pas de patience, et un pipeline qui hurle.
1) Décidez : téléchargement réseau ou extraction locale ?
- Si le pull se fige à « Downloading » ou « Waiting », pensez DNS/proxy/MTU/CDN.
- Si le téléchargement est rapide puis que ça bloque à « Extracting » ou que votre disque sature, pensez driver de stockage / système de fichiers / IO disque.
2) Vérifiez la latence et la correction du DNS
- Exécutez une requête contre le nom exact du registry (et votre résolveur choisi).
- Si DNS prend >100 ms de façon consistante, ou renvoie des IP privées bizarres, corrigez le DNS avant toute autre chose.
3) Vérifiez le MTU du chemin (test trou noir)
- Si les handshakes TLS ou les gros téléchargements de couches se bloquent, lancez une sonde MTU avec « ne pas fragmenter ».
- Les problèmes de MTU ressemblent à « certaines choses fonctionnent, gros transferts gelés ». Classique.
4) Vérifiez l’environnement proxy et la config proxy du daemon
- Un décalage entre les variables proxy du shell utilisateur et la configuration proxy du daemon Docker cause des échecs partiels déroutants.
- Si vous avez un proxy qui inspecte TLS, attendez-vous à une latence supplémentaire et parfois à un mauvais comportement HTTP/2.
5) Vérifiez le comportement IPv6 (timeouts dual-stack)
- Un IPv6 cassé se matérialise souvent par de longues pauses avant la bascule vers l’IPv4.
- Ne désactivez pas IPv6 définitivement sauf si vous assumez les conséquences. Préférez corriger la route/RA/les enregistrements AAAA d’abord.
6) Ensuite seulement : throttling du registry et miroirs
- Les limites de débit et la sélection d’edge CDN peuvent être réelles ; ne faites pas d’elles votre premier diagnostic parce que c’est rassurant.
- Un miroir peut aider, mais ajoute aussi un nouveau domaine de défaillance. Choisissez avec soin.
Une citation pour rester honnête : idée paraphrasée
de Richard Feynman : « La réalité doit primer sur les relations publiques. » En exploitation, les logs et les traces de paquets sont la réalité.
Ce que fait réellement docker pull sur le réseau
docker pull n’est pas un seul téléchargement. C’est un pipeline de petites décisions :
- Résolution de nom : résolution du nom du registry (DNS ; parfois plusieurs réponses A/AAAA).
- Connexion TCP : établir une connexion vers un nœud edge (CDN) ou un service de registry.
- Handshake TLS : négocier le chiffrement, valider les certificats, éventuellement faire des vérifications OCSP/CRL.
- Auth : obtenir un token depuis un endpoint d’auth ; parfois des redirections supplémentaires.
- Récupération du manifest : récupérer les manifest JSON, choisir plateforme/architecture, localiser les couches.
- Téléchargements de couches : requêtes HTTP range parallèles pour les blobs ; retries ; backoff.
- Décompression + extraction : intensif CPU + disque ; beaucoup de métadonnées ; dépend du driver de stockage.
- Tenue du magasin de contenu : containerd/Docker met à jour les métadonnées locales ; garbage collection plus tard.
La lenteur peut se cacher à n’importe quelle étape. Le DNS peut ajouter des secondes par nom. Les problèmes de MTU peuvent bloquer de gros enregistrements TLS. Les proxies peuvent casser les keep-alives si bien que chaque couche paye le coût d’un nouvel établissement de connexion. Et le stockage peut transformer « téléchargé » en « toujours en attente » parce que l’extraction étouffe.
Blague #1 : « C’est toujours le DNS » est drôle parce que c’est vrai—et parce que c’est la seule chose nous empêchant de hurler.
Faits intéressants et brève histoire
- La distribution d’images Docker a beaucoup emprunté à la pensée Git. Les couches se comportent comme des commits réutilisables : on tire une fois, on réutilise toujours (jusqu’à ce que non).
- Le protocole de registry a évolué. Le registry v1 a été déprécié ; v2 a amélioré l’adressage de contenu et permis une meilleure intégration CDN.
- L’adressage par contenu compte. Les registries modernes indexent les blobs par digest ; le caching fonctionne parce que le même digest signifie le même contenu partout.
- containerd est le cheval de trait. Beaucoup de pulls « Docker » sur les systèmes modernes passent en réalité par le content store et les snapshotters de containerd.
- Les requêtes HTTP range sont courantes. Les gros blobs peuvent être récupérés en morceaux ; les réseaux fragiles peuvent provoquer des fetchs partiels répétés qui ressemblent à de la lenteur.
- PMTUD est un fléau récurrent depuis les années 1990. Les pare-feux qui filtrent ICMP « Fragmentation Needed » cassent encore les charges modernes aujourd’hui.
- Les proxies d’entreprise ont changé le profil de défaillance. L’inspection TLS, les timeouts d’inactivité et la réécriture d’en-têtes causent des problèmes qui ressemblent à « Docker est buggué ».
- Le dual-stack IPv6 peut amplifier la latence. Happy Eyeballs aide, mais un IPv6 cassé peut encore ajouter des secondes de délai par connexion si mal configuré.
- L’extraction de couches est lourde en métadonnées. Des millions de petits fichiers (courant dans certains runtimes) punissent les filesystems overlay et les disques lents.
Tâches de diagnostic pratiques (commandes, sorties, décisions)
Vous voulez des tâches qui produisent des preuves. Voici plus d’une douzaine. Chacune inclut : une commande exécutable, ce que signifie la sortie et ce que vous faites ensuite.
Task 1: Time the pull with debug logs
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
Ce que cela signifie : La sortie debug montre où le temps est passé : résolution, récupération du manifest, téléchargement des blobs, extraction.
Décision : Si le blocage survient avant « fetching layer », poursuivez DNS/auth/proxy. Si c’est « Downloading » avec retries, poursuivez MTU/proxy/CDN. Si c’est « Extracting », poursuivez stockage/CPU.
Task 2: Confirm daemon and client versions (behavior changes)
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
Ce que cela signifie : Différentes versions de Docker/containerd ont des valeurs par défaut différentes pour le réseau, la concurrence et le comportement vis-à-vis des registries.
Décision : Si vous êtes ancien (ou très récent) et constatez des bizarreries, testez sur une version connue bonne avant de repenser votre réseau.
Task 3: Identify which registry endpoints are used
cr0x@server:~$ docker pull --quiet busybox:latest && docker image inspect busybox:latest --format '{{.RepoDigests}}'
sha256:...
[docker.io/library/busybox@sha256:3f2...]
Ce que cela signifie : Vous tirez depuis Docker Hub (docker.io), qui redirige souvent vers des endpoints CDN.
Décision : Si seul Docker Hub est lent mais que d’autres registries vont bien, suspectez le routage CDN, les limites de débit ou une politique proxy spécifique à ces hostnames.
Task 4: Inspect daemon DNS settings and overall config
cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
"dns": ["10.10.0.53", "1.1.1.1"],
"log-level": "info"
}
Ce que cela signifie : Le daemon Docker peut utiliser des résolveurs spécifiques différents de ceux de l’hôte.
Décision : Si ces serveurs DNS sont lents/inaccessibles depuis le namespace réseau du daemon, corrigez-les ou supprimez-les. Préférez les résolveurs de cache locaux rapides de votre organisation.
Task 5: Measure DNS query time against the resolver Docker uses
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
Ce que cela signifie : 420 ms par lookup est brutal quand un pull déclenche de nombreux lookups (endpoints d’auth, services de token, noms CDN).
Décision : Corrigez la performance DNS en premier : cache local, emplacement du résolveur, ou réduction de la latence en amont. Ne touchez pas encore au MTU.
Task 6: Check for DNS search-domain disasters
cr0x@server:~$ cat /etc/resolv.conf
search corp.example internal.example
nameserver 10.10.0.53
options ndots:5 timeout:2 attempts:3
Ce que cela signifie : ndots:5 signifie que de nombreux hostnames sont traités comme « relatifs » d’abord, déclenchant plusieurs requêtes inutiles par lookup.
Décision : Dans des environnements où vous tirez depuis de nombreux registries externes, envisagez d’abaisser ndots (souvent à 1–2) ou de restreindre les domaines de recherche. Testez attentivement ; cela peut casser des résolutions internes.
Task 7: Verify if systemd-resolved is in play (and misbehaving)
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
Ce que cela signifie : Vous utilisez un stub resolver ; Docker pourrait lire une IP stub comme 127.0.0.53 et cela peut ou non fonctionner selon le namespace et la config.
Décision : Si les containers ou le daemon Docker ne peuvent pas atteindre le stub, définissez des serveurs DNS explicites dans daemon.json ou ajustez votre configuration de résolveur.
Task 8: Observe live connection attempts (IPv6 vs IPv4, retries)
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))
Ce que cela signifie : Docker tente IPv6 et reste en SYN-SENT tandis que l’IPv4 fonctionne. C’est la « taxe timeout dual-stack ».
Décision : Corrigez le routage IPv6/les AAAA DNS, ou préférez temporairement IPv4 pour le daemon si vous avez besoin d’un remède rapide.
Task 9: Test MTU with a no-fragment ping probe
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
Ce que cela signifie : Votre path MTU est de 1460 (typique quand il y a un overhead : VPN, tunnels, certains tissus cloud). Essayer d’envoyer des frames de 1500 octets provoque des trous noirs ou erreurs.
Décision : Réglez le MTU du bridge Docker pour correspondre au path MTU réel (ou légèrement en dessous), et/ou corrigez le réseau afin que PMTUD fonctionne (les ICMP « frag needed » doivent transiter).
Task 10: Confirm interface MTU and tunnel overhead
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
Ce que cela signifie : Votre tunnel VPN a un MTU de 1420 mais le bridge Docker est à 1500. Les containers peuvent émettre des paquets trop gros pour le chemin réel, et PMTUD pourrait ne pas vous sauver.
Décision : Alignez le MTU Docker sur le plus petit MTU effectif du chemin de sortie.
Task 11: Check daemon proxy configuration (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"
Ce que cela signifie : Le daemon Docker est configuré pour utiliser un proxy. Cela affecte l’accès aux registries, la terminaison TLS, et parfois la performance de façon spectaculaire.
Décision : Si le proxy est requis, assurez-vous qu’il est stable et supporte TLS moderne. S’il est optionnel, essayez de contourner pour les hostnames de registry via NO_PROXY pour réduire la latence et la complexité.
Task 12: Verify your shell proxy vars aren’t misleading you
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
Ce que cela signifie : Vos paramètres de shell diffèrent de ceux du daemon. Vous pouvez faire un curl correctement dans le shell alors que le daemon Docker prend une autre route—ou l’inverse.
Décision : Traitez la config du daemon comme faisant foi pour les pulls. Alignez les paramètres proxy entre daemon, runners CI et hôte.
Task 13: Test TLS handshake time to likely 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
Ce que cela signifie : 2,4 secondes pour un handshake est suspect sauf si vous êtes sur un lien haute latence. L’inspection TLS par un proxy et un IPv6 cassé causent fréquemment cela.
Décision : Si le handshake est lent, vérifiez le chemin de résolution DNS, le chemin proxy et les tentatives IPv6 avant d’accuser Docker.
Task 14: Observe ICMP “frag needed” or retransmits during pulls
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
Ce que cela signifie : Voir « need to frag » est en réalité une bonne chose : PMTUD fonctionne et vous indique le MTU. Voir des RSTs peut indiquer des resets proxy, des timeouts d’inactivité ou un middlebox problématique.
Décision : Si vous voyez « need to frag » mais que les téléchargements restent bloqués, une partie du chemin bloque encore ICMP. Si vous voyez des resets, concentrez-vous sur la politique proxy/pare-feu et les timers TCP d’inactivité.
Task 15: Check extraction bottleneck: disk and 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
Ce que cela signifie : Un iowait élevé et une utilisation disque proche de 100 % signifient que l’extraction est limitée par le stockage. La lenteur du pull n’est pas votre réseau.
Décision : Traitez le choix du filesystem/snapshotter, les performances disque et la composition des images (trop de petits fichiers). Envisagez de déplacer le root Docker vers un stockage plus rapide.
Modes de défaillance DNS qui rendent les pulls « lents »
Le DNS est la première dominos. Un DNS lent ressemble à des téléchargements lents, parce que chaque nouvel endpoint ou redirection commence par une résolution. Et les registries n’hésitent pas à multiplier les hostnames : services d’auth, endpoints de registry, edges CDN, parfois réponses géo-basées.
Patterns DNS courants qui nuisent aux pulls
- Résolveurs récursifs lents : DNS centralisé traversant un WAN ; chaque requête paie un RTT.
- Cache cassé : résolveurs qui ne mettent pas en cache efficacement (misconfiguration, cache minuscule, politique d’éviction agressive).
- Amplification par domaines de recherche :
ndotset longues listes de recherche provoquant plusieurs requêtes infructueuses par nom. - Split-horizon surprenant : DNS interne renvoyant des IP privées ou d’interception pour des registries publics.
- Latence DNSSEC/validation : échecs de validation menant à des retries et comportements de repli.
Que faire (opinionnel)
- Utilisez un résolveur de cache proche pour les nœuds de build. Si votre « résolveur central » est à l’autre bout du continent, c’est une erreur de conception.
- Ne pas hardcoder des résolveurs publics par réflexe. Ils peuvent être rapides, mais aussi violer le routage corporate, casser le split DNS, ou être bloqués.
- Corrigez ndots / domaines de recherche intentionnellement. Les gens copient-collent des configs de résolveur comme si c’était anodin. Ce ne l’est pas.
Correctif concret : définir explicitement le DNS du daemon Docker
Si les containers (pas l’hôte) subissent des délais DNS, définissez le DNS du daemon. Cela n’affecte pas toujours directement les pulls de registry (qui sont effectués par le daemon), mais évite des douleurs secondaires une fois que les containers démarrent.
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
Décision : Si le redémarrage règle le problème, vous aviez une dérive de résolveur ou le daemon utilisait un stub inaccessible de façon fiable.
MTU et PMTUD : le mystère muet des paquets disparus
Les problèmes de MTU n’échouent pas bruyamment. Ils échouent comme une mauvaise réunion : tout le monde est techniquement présent, et rien n’avance.
Le pattern : les petites requêtes fonctionnent, les gros transferts stagnent. Vous pouvez pinguer. Vous pouvez récupérer un petit manifest. Puis un blob commence à télécharger et s’arrête à un nombre d’octets étonnamment constant, ou il se bloque indéfiniment avec des retries.
Comment le MTU casse les pulls
- PMTUD blackholé : les ICMP « Fragmentation Needed » sont bloqués par un pare-feu/middlebox, donc les endpoints continuent d’envoyer des paquets qui n’arrivent jamais.
- Surcharge d’overlay et de tunnel : VPNs, GRE, VXLAN, WireGuard, IPSec réduisent tous le MTU effectif.
- MTU du bridge hors synchro : les réseaux Docker sont configurés à 1500 alors que le chemin réel est plus petit.
Correctif : définir délibérément le MTU du réseau Docker
Vous pouvez définir le MTU par défaut pour les réseaux bridge de Docker via la configuration du daemon. Choisissez une valeur adaptée à votre chemin d’egress. Si votre tunnel est à 1420, mettez Docker entre 1400–1420 pour conserver de la marge.
cr0x@server:~$ sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
"mtu": 1420,
"log-level": "info"
}
EOF
sudo systemctl restart docker
Ce que cela signifie : Les nouveaux réseaux utiliseront ce MTU ; les réseaux existants peuvent nécessiter une recréation.
Décision : Si les pulls se stabilisent immédiatement après l’alignement du MTU (surtout sur VPN), vous avez trouvé le coupable. Ensuite corrigez PMTUD proprement pour ne pas masquer le problème réseau.
Correctif : autoriser ICMP pour PMTUD
Si vous contrôlez les pare-feux, autorisez les types ICMP spécifiques nécessaires pour PMTUD (IPv4 « frag needed », IPv6 Packet Too Big). Les bloquer est une superstition héritée.
Blague #2 : Certains réseaux bloquent ICMP « pour la sécurité », ce qui revient à enlever les panneaux de signalisation pour empêcher les excès de vitesse.
Proxies d’entreprise : quand « utile » devient nocif
Les proxies sont soit un outil de conformité nécessaire, soit un collecteur de taxe de performance. Parfois les deux. Les pulls Docker sollicitent les proxies de plusieurs façons : beaucoup de connexions concurrentes, gros flux TLS, redirections, et mélange d’endpoints.
Comportements de proxy qui ralentissent les pulls
- Timeouts d’inactivité courts : les téléchargements de blobs se mettent en pause, le proxy coupe la connexion, le client retry.
- Inspection TLS : ajoute un coût au handshake, casse la réutilisation de session, et peut interagir mal avec HTTP/2 ou des suites modernes.
- Limites de concurrence de connexions : le proxy bride les téléchargements parallèles si bien que chaque blob rampe.
- NO_PROXY incorrect : le daemon envoie le trafic registry via le proxy alors qu’il ne devrait pas.
Correctif : configurer correctement (et complètement) le proxy du daemon
Définissez le proxy pour le daemon Docker via un drop-in systemd, pas seulement via vos variables d’environnement 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
Ce que cela signifie : Le daemon utilise maintenant de manière cohérente les paramètres proxy. Cela élimine le split-brain « marche avec curl, échoue avec docker ».
Décision : Si les pulls s’améliorent, votre état précédent était incohérent. Si ça empire, votre proxy est le goulot—contournez-le pour les registries quand c’est autorisé.
Correctif : ajouter un miroir de registry local (prudemment)
Un miroir peut éliminer le passage par le proxy et réduire le trafic WAN. Il peut aussi devenir votre nouveau point unique de défaillance. Si vous exécutez un miroir, opérez-le comme une infrastructure de production : monitoring, capacité de stockage, hygiène TLS, et politique d’éviction prévisible.
IPv6, dual-stack et le long timeout
Un IPv6 cassé est un type particulier de douleur : tout semble « plutôt OK », mais chaque nouvelle connexion paie un délai car les clients essaient d’abord IPv6, attendent, puis basculent.
Diagnostiquer le délai dual-stack
Cherchez des connexions en SYN-SENT sur v6, ou de longues pauses avant le premier octet. Vérifiez aussi si le DNS renvoie des enregistrements AAAA qui ne routent nulle part depuis vos hôtes.
Atténuations (choisir le moindre mal)
- Corriger IPv6 proprement : routes, RA, règles de pare-feu et enregistrements DNS AAAA corrects.
- Préférer IPv4 temporairement : comme mesure d’urgence si la CI est hors service et que vous avez besoin des images maintenant.
Si vous choisissez la préférence IPv4 temporaire, soyez explicite et tracez-le comme dette technique. Les réglages « temporaires » deviennent souvent de l’archéologie.
Throttling des registries, limites de débit et bizarreries CDN
Oui, parfois c’est vraiment la registry. Les limites de débit, le throttling ou un edge CDN malchanceux peuvent transformer les pulls en gadoue. Mais diagnostiquez d’abord. N’utilisez pas « l’internet est lent » comme stratégie de monitoring.
Signes que vous êtes throttlé
- Les pulls sont rapides hors heures de bureau et lents en heures de pointe.
- Des erreurs mentionnent trop de requêtes, ou vous voyez un comportement fréquent proche de 429 dans les outils supérieurs.
- Un seul registry est lent ; les autres sont normaux.
Correctifs souvent rentables
- Authentifier les pulls quand possible pour obtenir des limites plus élevées.
- Utiliser un cache/miroir interne pour des flottes CI, surtout si vous reconstruisez souvent.
- Réduire le churn d’images : pinner les images de base, éviter les rebuilds qui invalident chaque couche.
Stockage et extraction : quand le réseau n’est pas le goulot
Un secret gênant : beaucoup de « pulls lents » sont des téléchargements rapides suivis d’extractions lentes. Surtout sur des runners partagés, des disques finement provisionnés, ou des overlays imbriqués.
L’extraction est un benchmark de stockage déguisé
- Beaucoup de petits fichiers : churn de métadonnées et d’inodes.
- Compression : temps CPU, puis écritures.
- Comportement des filesystems overlay : pénalités de copy-up, pression sur dentry, amplification d’écritures.
Correctifs qui comptent
- Déplacez le data root de Docker vers un stockage plus rapide (
/var/lib/dockersur NVMe vaut mieux que des disques réseaux). - Gardez les images plus petites et réduisez le nombre de fichiers par couche (builds multi-stage, images sveltes, prune des caches).
- Sur des hôtes occupés, planifiez les pulls pour éviter la contention IO (ou isolez des nœuds de build).
Trois mini-histoires d’entreprise issues du terrain
Mini-histoire 1 : L’incident causé par une mauvaise hypothèse
L’entreprise avait une baseline réseau « secure-by-default ». Une équipe a déployé de nouvelles règles de firewall pour « serrer » l’accès entre les workers de build et internet. Les pulls ne tombaient pas complètement ; ils devenaient douloureusement lents et parfois se bloquaient. C’est pire, parce que ça ressemble à une panne aléatoire plutôt qu’à une coupure nette.
L’hypothèse était simple et fausse : « ICMP est optionnel ». Le jeu de règles bloquait le type ICMP 3 code 4 (IPv4 fragmentation needed) et l’équivalent IPv6. La plupart de la navigation web continuait de fonctionner. Les petites requêtes HTTPS passaient. Les pulls Docker ? Les gros enregistrements TLS et les blobs importants atteignaient le MTU effectif, disparaissaient dans un trou noir, et restaient là jusqu’aux retries et backoff qui rendaient le pipeline hanté.
L’on-call a passé des heures à poursuivre « Docker Hub en panne », puis « proxy », puis « nos runners sont surchargés ». Quelqu’un a finalement lancé une sonde MTU et a eu l’arme fumante : le path MTU était inférieur au MTU de l’interface, et PMTUD ne pouvait pas l’indiquer.
La correction n’a rien d’héroïque. Ils ont autorisé les bons types ICMP sur le chemin egress et défini un MTU Docker conservateur sur les runners traversant des tunnels. Les temps de pull sont revenus à la normale. L’action postmortem la plus utile : chaque changement réseau touchant l’egress doit désormais passer une vérification MTU/PMTUD comme gate standard.
Mini-histoire 2 : L’optimisation qui s’est retournée contre eux
Une équipe plateforme voulait des builds plus rapides, alors elle a introduit un miroir de registry dans chaque région. Idée sensée : localiser le trafic, réduire la bande passante WAN, garder la CI réactive. Le déploiement rapide semblait bien fonctionner au départ.
Puis le retour de bâton. Le miroir disposait d’un petit budget disque et d’une éviction agressive. Quand plusieurs équipes ont commencé à pousser des images plus volumineuses (runtimes, dépendances ML, symboles de debug—choisissez votre poison), le miroir a churné constamment. Un blob était mis en cache, évincé, puis refetché dans la même journée. Le proxy devant le miroir avait aussi un timeout d’inactivité court ; les téléchargements partiels étaient resetés, provoquant des retries, augmentant la charge, provoquant plus de resets. Un petit cercle vicieux de misère.
Le symptôme était trompeur : les pulls étaient parfois rapides et parfois catastrophiquement lents, même dans la même région. Les ingénieurs blâmaient la registry, puis Docker, puis leurs propres images. La réelle cause racine était l’« optimisation » : un cache trop petit pour être stable, plus des timeouts calibrés pour des pages web, pas pour des blobs de centaines de mégaoctets.
La solution fut ennuyeuse : dimensionner correctement le stockage, ajuster les timeouts, ajouter du monitoring sur le hit rate du cache, et désactiver le comportement proxy qui cassait les transferts longs. Ils ont aussi posé une règle : les miroirs sont des services de production, pas des projets bricolés sur des VM résiduelles.
Mini-histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une entreprise financière avait des contrôles sortants stricts. Elle pratiquait aussi une habitude que j’aimerais voir partout : chaque worker de build et nœud Kubernetes exécutait les mêmes diagnostics de base au démarrage et publiait les résultats en interne.
Un lundi, les développeurs se sont plaints que les pulls d’images étaient lent « partout ». L’SRE en astreinte n’a pas lancé de débat dans Slack sur l’état de Docker Hub. Il a ouvert le dashboard des diagnostics des nœuds. Les temps de requête DNS avaient triplé sur un sous-ensemble de sous-réseaux. Les sondes MTU étaient normales. L’IO disque normal. C’était le DNS, proprement et sans romantisme.
Le coupable était un basculement de résolveur qui fonctionnait techniquement mais faisait remonter le trafic vers un site plus éloigné. La latence a augmenté. Les caches étaient froids. Les pulls de registry payaient soudain des millisecondes supplémentaires des dizaines de fois par pull. Pas assez pour crier « incident », mais largement suffisant pour faire fondre le débit CI.
Ils ont remis la préférence de résolveur, réchauffé les caches, et la plainte a disparu. La pratique salvatrice n’était pas un outil malin ; c’était une mesure de base cohérente. Ennuyeux. Correct. Incroyablement efficace quand la salle déborde de théories.
Erreurs courantes (symptômes → cause racine → fix)
-
Symptôme : Le pull se bloque à « Downloading » autour du même pourcentage à chaque fois.
Cause racine : MTU/PMTUD en trou noir ; gros paquets dropped, pas de feedback ICMP.
Fix : Sondez le MTU avecping -M do, autorisez frag-needed/Packet Too Big, alignez le MTU Docker sur le chemin tunnel. -
Symptôme : Longue pause avant tout progrès, puis accélération soudaine.
Cause racine : IPv6 cassé avec bascule lente vers IPv4 ; des AAAA existent mais ne routent pas.
Fix : Corrigez le routage/firewall/DNS IPv6 ; comme mitigation, préférez IPv4 pour le daemon ou supprimez les mauvais AAAA au résolveur si vous le contrôlez. -
Symptôme :
curlvers le registry est rapide, maisdocker pullest lent ou timeout.
Cause racine : Split-brain proxy : variables proxy du shell différentes de la config proxy du daemon.
Fix : Configurez le proxy via un drop-in systemd pour le daemon Docker ; alignez leNO_PROXY. -
Symptôme : Pull rapide sur laptops, lent sur serveurs.
Cause racine : Les serveurs utilisent des résolveurs/domaines de recherche différents, ou traversent VPN/tunnels avec MTU réduit.
Fix : Comparez résolveur et MTU ; alignez DNS et MTU du daemon sur le chemin réel du serveur. -
Symptôme : « Downloading » est rapide, « Extracting » prend une éternité, le CPU est OK mais l’IO est saturé.
Cause racine : Disque lent, stockage finement provisionné, overhead overlay, trop de petits fichiers.
Fix : Déplacez le root Docker vers un stockage plus rapide ; optimisez les images ; réduisez le nombre de fichiers par couche ; isolez les runners. -
Symptôme : Un seul registry est lent ; les autres sont normaux.
Cause racine : Sélection d’edge CDN, rate limiting, ou politique proxy ciblée sur certains domaines.
Fix : Authentifiez les pulls ; utilisez un miroir ; ajustez le bypass proxy ; validez les réponses DNS (géolocalisation, split horizon). -
Symptôme : La vitesse du pull oscille énormément minute par minute.
Cause racine : Resets proxy, churn du cache miroir, ou congestion réseau avec retries.
Fix : Ajustez les timeouts proxy, dimensionnez correctement le miroir, mesurez le hit rate du cache, réduisez les pulls concurrents durant les fenêtres de congestion.
Listes de contrôle / plan étape par étape
Checklist A: Triage sur un nœud (15 minutes)
- Exécutez
docker -D pullet notez où le temps est consommé (resolve/auth/download/extract). - Mesurez le temps DNS pour le hostname du registry avec
dig +stats. - Vérifiez
/etc/resolv.confpourndotset domaines de recherche amplificateurs. - Vérifiez le mismatch MTU :
ip -br linket sondesping -M do. - Vérifiez la config proxy du daemon via
systemctl cat docker. - Vérifiez les tentatives de connexion IPv6 avec
ss -tpn. - Si l’extraction est lente, confirmez avec
iostatet cessez d’accuser le réseau.
Checklist B: Correction à l’échelle de la flotte (ce qui va au-delà d’un hôte)
- Standardisez la config du daemon : serveurs DNS, MTU, proxy, niveau de logs.
- Tests de baseline au démarrage : latence DNS, sonde MTU vers endpoints clés, sanity IO disque.
- Observabilité centrale : durées de pull, taux d’échec, et où le temps est passé (download vs extraction).
- Introduisez des miroirs délibérément : traitez-les comme de la production—capacité, monitoring, TTL/politique d’éviction, timeouts.
- Hygiène des politiques réseau : autorisez explicitement les types ICMP nécessaires à PMTUD ; documentez-le pour éviter qu’on « durcisse » cela par la suite.
Checklist C: Hygiène des images (ce que vous contrôlez)
- Gardez les images de base pined et cohérentes pour maximiser la réutilisation des couches.
- Utilisez des builds multi-stage pour réduire taille et nombre de fichiers.
- Évitez d’emballer les caches de gestionnaires de paquets dans les couches.
- Privilégiez moins de couches significatives plutôt que des dizaines de micro-couches.
FAQ
1) Pourquoi docker pull semble-t-il plus lent que le téléchargement d’un gros fichier ?
Parce que ce n’est pas un seul fichier. C’est plusieurs lookups DNS, appels d’auth, redirections, téléchargements parallèles de blobs, puis décompression + extraction. N’importe quel maillon faible devient « Docker est lent ».
2) Je résous le DNS rapidement sur l’hôte. Pourquoi Docker est-il quand même lent ?
Le daemon peut utiliser des paramètres DNS différents de votre shell ou de vos containers. Vérifiez /etc/docker/daemon.json, le comportement de systemd-resolved, et si le daemon pointe vers un stub résolveur qu’il ne peut pas atteindre de façon fiable.
3) Quelle est la façon la plus rapide de confirmer un problème MTU ?
Utilisez une sonde ping « ne pas fragmenter » et faites une recherche binaire sur la taille de payload. Si les gros paquets échouent mais que les plus petits fonctionnent, et surtout si vous êtes sur VPN/tunnels, considérez immédiatement le MTU comme suspect.
4) Dois-je simplement définir le MTU Docker à 1400 partout ?
Non. C’est une méthode brutale. Cela peut réduire la performance sur des réseaux propres et masquer de vrais défauts PMTUD/pare-feu. Définissez le MTU en fonction des contraintes réelles du chemin, et réparez le traitement ICMP pour ne pas avoir besoin de superstition.
5) Pourquoi désactiver IPv6 « répare » parfois le problème ?
Si IPv6 est cassé, les clients perdent du temps à l’essayer avant de retomber. Désactiver IPv6 force IPv4 immédiatement et évite la taxe du timeout. Mieux : faites fonctionner IPv6 correctement ou cessez de publier de mauvais AAAA.
6) Mon proxy est requis. Que dois-je demander à l’équipe réseau ?
Demandez : des timeouts d’inactivité plus longs pour les gros téléchargements, le support TLS moderne sans casser la réutilisation de session, la capacité à gérer de nombreuses connexions concurrentes, et une liste de bypass claire pour les hostnames de registry si la politique le permet.
7) Le pull est lent uniquement sur des nœuds Kubernetes. Pourquoi ?
Les nœuds ont souvent des chemins d’egress différents (gateways NAT, overlays CNI, pare-feux plus stricts, DNS différents). De plus, kubelet utilise containerd directement, donc les paramètres proxy/DNS peuvent différer de vos hypothèses sur l’hôte Docker.
8) Comment distinguer « téléchargement lent » de « extraction lente » sans deviner ?
Utilisez docker -D pull et observez où ça stagne, puis confirmez avec des métriques disque. Si l’IO est saturé pendant « Extracting », le réseau n’est pas le goulot.
9) Un miroir de registry vaut-il toujours le coup ?
Il vaut le coup lorsque vous avez de nombreux nœuds tirant régulièrement des images communes, surtout en CI. Il ne vaut pas le coup si vous ne pouvez pas l’exploiter de manière fiable. Un miroir instable est une façon créative d’inventer des pannes.
10) Pourquoi les pulls deviennent-ils parfois plus lents après « optimisation » du DNS ?
Parce qu’« optimiser » signifie souvent changer de résolveur sans comprendre le split DNS, le comportement de cache ou les réponses géo. Un RTT résolveur plus faible n’aide pas si le résolveur renvoie un edge CDN pire ou casse le routage corporate.
Conclusion : prochaines étapes qui rapportent
Si docker pull est douloureusement lent, traitez-le comme un incident de production : collectez des preuves, isolez l’étape, et corrigez le vrai goulot. Commencez par la latence et la correction DNS, ensuite MTU/PMTUD, ensuite le comportement proxy, puis IPv6, et seulement après discutez avec la registry.
Prochaines étapes pratiques :
- Exécutez le playbook de diagnostic rapide sur un nœud affecté et un nœud connu bon. Comparez les résultats.
- Standardisez la configuration du daemon Docker sur votre flotte (DNS, MTU, proxy) et redémarrez de manière planifiée.
- Ajoutez des vérifications baseline DNS/MTU à la provision des nœuds pour attraper ça avant que la CI ne devienne une œuvre de performance.
- Si l’extraction est votre goulot, migrez le stockage Docker vers des disques plus rapides et nettoyez vos images. Le tuning réseau ne sauvera pas un hôte lié au disque.