
ImagePullBackOff signifie que le kubelet n'arrive pas à télécharger l'image
du conteneur depuis le registre. Le pod est assigné à un nœud (il n'est plus
Pending), mais la création du conteneur échoue car l'image est introuvable,
inaccessible ou refusée. Ce guide vous donne une méthodologie en 4 étapes
pour identifier la cause et débloquer le déploiement.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Comprendre la différence entre
ErrImagePulletImagePullBackOff - Identifier les 7 causes principales d'un échec de pull d'image
- Appliquer une méthodologie de diagnostic reproductible en 4 étapes
- Configurer
imagePullSecretspour un registre privé - Reproduire et résoudre un ImagePullBackOff sur un cluster de test
Prérequis
Section intitulée « Prérequis »- Un cluster Kubernetes fonctionnel (v1.28+)
kubectlconfiguré et connecté au cluster- Des droits de lecture sur les pods et événements
- Connaissances de base sur les Pods et les images de conteneurs
Qu'est-ce qu'un ImagePullBackOff ?
Section intitulée « Qu'est-ce qu'un ImagePullBackOff ? »Quand le kubelet essaie de télécharger une image et échoue, il passe par deux états successifs :
| État | Signification |
|---|---|
ErrImagePull | Le pull vient d'échouer, c'est la première tentative (ou une nouvelle tentative après un backoff) |
ImagePullBackOff | Le kubelet a déjà échoué et attend avant de réessayer, c'est le backoff exponentiel |
Ces deux états alternent en boucle : le kubelet essaie (ErrImagePull), échoue,
attend (ImagePullBackOff), puis réessaie. Le délai d'attente augmente
progressivement (backoff exponentiel), mais la documentation officielle
ne détaille pas les timings exacts de ce backoff aussi précisément que pour
CrashLoopBackOff.
Le processus de pull d'une image
Section intitulée « Le processus de pull d'une image »Quand le kubelet doit démarrer un conteneur, il suit ces étapes :
- Vérifier le cache local, l'image existe-t-elle déjà sur le nœud ?
- Résoudre le nom de l'image, ajouter
docker.io/library/si pas de registre explicite, et:latestsi pas de tag - S'authentifier, si un
imagePullSecretest configuré, le kubelet l'utilise pour s'authentifier auprès du registre - Télécharger l'image, pull des layers manquants
- Vérifier l'intégrité, validation du digest SHA256
Si une de ces étapes échoue, le kubelet émet un événement Failed avec le
détail de l'erreur, et le pod passe en ErrImagePull.
Les 7 causes principales
Section intitulée « Les 7 causes principales »La colonne à exploiter en priorité est Message d'erreur typique : c'est
elle qui fait le tri, pas la description de la cause. Le kubelet recopie
verbatim la réponse du registre dans l'événement du pod, ce qui rend le
diagnostic quasi mécanique. Notez que les causes 1 et 6 produisent le même
message (manifest unknown), le registre ne faisant pas la différence entre
un tag qui n'a jamais existé et un tag supprimé ; c'est l'historique de votre
chaîne de publication qui tranche. La cause 7 est la plus trompeuse : l'image
existe bien, le tag aussi, mais aucune variante ne correspond à
l'architecture du nœud, un cas fréquent depuis l'arrivée des nœuds arm64
dans les parcs mixtes.
| # | Cause | Message d'erreur typique | Où chercher |
|---|---|---|---|
| 1 | Tag ou nom d'image incorrect | manifest unknown, not found | kubectl describe pod → Events |
| 2 | Registre privé sans authentification | unauthorized, authentication required | kubectl describe pod → Events |
| 3 | imagePullSecret absent ou invalide | no basic auth credentials, unauthorized | kubectl describe pod → Events + kubectl get secret |
| 4 | Registre inaccessible (réseau) | dial tcp: i/o timeout, no such host | kubectl describe pod → Events |
| 5 | Limite de pull Docker Hub | toomanyrequests, rate limit exceeded | kubectl describe pod → Events |
| 6 | Image ou tag supprimé | manifest unknown, not found | kubectl describe pod → Events |
| 7 | Architecture incompatible | no matching manifest for linux/amd64 | kubectl describe pod → Events |
Méthodologie de diagnostic en 4 étapes
Section intitulée « Méthodologie de diagnostic en 4 étapes »-
Identifier les pods en ImagePullBackOff
Fenêtre de terminal kubectl get pods -A | grep -E 'ImagePull|ErrImage'Vous pouvez aussi filtrer les événements de type pull échoué :
Fenêtre de terminal kubectl get events -A --field-selector reason=Failed --sort-by='.lastTimestamp' | grep -i pullNotez le nom du pod, son namespace et depuis combien de temps il est dans cet état.
-
Lire le message d'erreur exact
Fenêtre de terminal kubectl describe pod <pod> -n <namespace>Allez directement à la section Events et cherchez les lignes
FailedouWarning. Le message contient l'information essentielle. Exemples :Failed to pull image "mon-registre.example.com/app:v2.1":rpc error: code = Unknown desc = failed to pull and unpack image:failed to resolve reference: pull access denied, repository does notexist or may require authorizationCe message vous dit : le registre est privé et le kubelet n'a pas les credentials.
Failed to pull image "nginx:v99":rpc error: code = NotFound desc = failed to pull and unpack image:failed to resolve reference: docker.io/library/nginx:v99:not foundCelui-ci : le tag
v99n'existe pas sur Docker Hub. -
Vérifier l'image et l'authentification
Vérifier que l'image existe :
Consultez l'interface ou l'API du registre concerné (Docker Hub, Harbor, GitLab Registry, ECR...) pour confirmer que le tag existe et qu'il est disponible pour l'architecture de vos nœuds. Pour tester un pull directement depuis un nœud, utilisez
crictl:Fenêtre de terminal # Tester le pull depuis le nœud (via un Pod de debug ou en SSH)crictl pull mon-registre.example.com/app:v2.1Vérifier l'imagePullSecret :
Fenêtre de terminal # Le secret existe-t-il dans le namespace du pod ?kubectl get secret -n <namespace> | grep docker# Le pod référence-t-il le secret ?kubectl get pod <pod> -n <namespace> -o jsonpath='{.spec.imagePullSecrets}'Si le pod n'a pas d'
imagePullSecretset que le registre est privé, c'est la cause du problème.Vérifier la connectivité réseau (depuis le contexte d'un nœud) :
kubectl debug node/crée un Pod de debug attaché au nœud cible. Depuis ce Pod, vous pouvez tester la résolution DNS et la connectivité réseau dans le contexte réseau du nœud :Fenêtre de terminal kubectl debug node/<node-name> -it --image=busybox:1.37# Une fois dans le shell du Pod de debug :nslookup mon-registre.example.comwget -q --spider https://mon-registre.example.com/v2/ && echo "OK" || echo "INACCESSIBLE" -
Corriger et vérifier
Selon la cause identifiée :
Cause Action corrective Tag incorrect Corriger le tag dans le manifeste (vérifier sur le registre) Registre privé sans auth Créer un imagePullSecretet le référencer dans le podSecret invalide Recréer le secret avec les bons credentials Registre inaccessible Vérifier le DNS, le firewall, le proxy du nœud Rate limit Docker Hub Authentifier les pulls ou utiliser un mirror Image ou tag supprimé Corriger le tag ou rebuilder/repusher l'image Architecture incompatible Rebuilder l'image pour l'architecture cible ou utiliser un manifest multi-arch Après correction, le pod retente automatiquement le pull. Si vous souhaitez forcer un re-pull immédiat :
Fenêtre de terminal kubectl delete pod <pod> -n <namespace>Le contrôleur (Deployment, StatefulSet...) recréera un pod qui tentera le pull avec les nouveaux paramètres.
Configurer un imagePullSecret
Section intitulée « Configurer un imagePullSecret »L'une des causes les plus courantes est l'absence de credentials pour un registre privé. Voici la procédure complète :
Créer le secret
Section intitulée « Créer le secret »Le type docker-registry produit un Secret au format kubernetes.io/dockerconfigjson,
c'est-à-dire exactement le contenu d'un fichier ~/.docker/config.json que le
kubelet sait consommer. Le champ --docker-server doit contenir le même
hôte que celui écrit dans le champ image du pod : si l'image est
mon-registre.example.com/app:v2.1, un secret créé pour
https://mon-registre.example.com/v2/ ne sera pas apparié et le pull restera
refusé. Pour --docker-password, préférez un jeton d'accès révocable
plutôt que le mot de passe du compte.
read -rsp 'Jeton du registre : ' REGISTRY_TOKEN && echokubectl create secret docker-registry mon-registre-secret \ --docker-server=mon-registre.example.com \ --docker-username=mon-user \ --docker-password="$REGISTRY_TOKEN" \ --docker-email=admin@example.com \ -n <namespace>unset REGISTRY_TOKENLa lecture par read -rs évite d'écrire le jeton en clair dans l'historique du
shell et dans la table des processus, où n'importe quel ps aux de la machine
le rendrait visible. Le Secret lui-même n'est pas chiffré dans etcd par
défaut, il est simplement encodé en base64 : le durcissement passe par le
chiffrement au repos côté serveur d'API.
Référencer le secret dans le pod
Section intitulée « Référencer le secret dans le pod »Le champ imagePullSecrets se déclare au niveau du pod, pas du conteneur,
et vaut donc pour tous les conteneurs du pod, init containers compris. Le nom
référencé doit correspondre à un Secret présent dans le même namespace :
Kubernetes n'émet aucune erreur de validation si le Secret est absent, le pull
échoue simplement comme s'il n'y avait pas d'authentification.
apiVersion: v1kind: Podmetadata: name: app-priveespec: imagePullSecrets: - name: mon-registre-secret containers: - name: app image: mon-registre.example.com/app:v2.1Automatiser avec le ServiceAccount
Section intitulée « Automatiser avec le ServiceAccount »Pour éviter d'ajouter imagePullSecrets dans chaque pod, associez le secret
au ServiceAccount par défaut du namespace :
kubectl patch serviceaccount default -n <namespace> \ -p '{"imagePullSecrets": [{"name": "mon-registre-secret"}]}'Tous les pods du namespace utilisant le ServiceAccount default hériteront
automatiquement du secret.
Exemples pratiques : reproduire un ImagePullBackOff
Section intitulée « Exemples pratiques : reproduire un ImagePullBackOff »Rien ne remplace un échec provoqué pour reconnaître un vrai. Les trois cas
ci-dessous s'appliquent sur un cluster de test et produisent chacun un
statut et un message différents dans kubectl describe. Retenez surtout
que tous les échecs d'image ne débouchent pas sur ImagePullBackOff : un nom
d'image syntaxiquement invalide est rejeté avant toute tentative de pull,
sous le statut InvalidImageName, comme le montre le troisième cas.
Cas 1 : tag inexistant
Section intitulée « Cas 1 : tag inexistant »kubectl apply -f- <<'EOF'apiVersion: v1kind: Podmetadata: name: pull-bad-tag namespace: defaultspec: containers: - name: app image: nginx:version-inexistanteEOFDiagnostic :
kubectl describe pod pull-bad-tag | tail -10Vous verrez Failed to pull image "nginx:version-inexistante" avec
not found.
Cas 2 : dépôt privé sans credentials
Section intitulée « Cas 2 : dépôt privé sans credentials »Pour reproduire un vrai refus d'authentification, il faut viser un registre qui répond, mais sur un dépôt qui exige des droits. Un chemin sous un espace de noms Docker Hub que vous ne possédez pas convient : le registre est joignable, mais il refuse l'accès.
kubectl apply -f- <<'EOF'apiVersion: v1kind: Podmetadata: name: pull-no-auth namespace: defaultspec: containers: - name: app image: docker.io/stephanerobert/depot-prive-inexistant:v1EOFDiagnostic :
kubectl describe pod pull-no-auth | tail -10Le message contiendra pull access denied, repository does not exist or may require authorization suivi de insufficient_scope: authorization failed.
Cas 3 : nom d'image mal formé
Section intitulée « Cas 3 : nom d'image mal formé »kubectl apply -f- <<'EOF'apiVersion: v1kind: Podmetadata: name: pull-bad-name namespace: defaultspec: containers: - name: app image: "nginx ::latest"EOFDiagnostic :
kubectl describe pod pull-bad-name | tail -10Ce pod ne passe pas en ImagePullBackOff : son statut est
InvalidImageName, avec l'événement Failed to apply default image tag "nginx ::latest": ... invalid reference format. Le nom est rejeté à la
validation, avant même que le kubelet tente le moindre pull. C'est un cas voisin
mais distinct, utile à connaître pour ne pas chercher un problème de registre là
où il n'y a qu'une faute de frappe dans le manifeste.
Nettoyage
Section intitulée « Nettoyage »Ces pods de test restent bloqués indéfiniment sans jamais démarrer, autant les
supprimer une fois l'observation faite. Les options --force --grace-period=0 évitent d'attendre une terminaison propre, inutile ici
puisque aucun conteneur n'a réellement démarré.
kubectl delete pod pull-bad-tag pull-no-auth pull-bad-name --force --grace-period=0 2>/dev/nullCas particuliers
Section intitulée « Cas particuliers »ImagePullBackOff sur un seul nœud
Section intitulée « ImagePullBackOff sur un seul nœud »Si certains pods démarrent normalement et d'autres pas, le problème est probablement local au nœud :
- Le nœud n'a pas de connectivité réseau vers le registre (proxy manquant, DNS défaillant)
- Le disque du nœud est plein (plus de place pour stocker les layers)
- Le runtime de conteneur a un problème de configuration (containerd, CRI-O)
Ouvrez une session de debug sur le nœud affecté avec kubectl debug node/<node-name> puis vérifiez la résolution DNS, la connectivité réseau
vers le registre et l'état du runtime CRI. Pour l'inspection du runtime,
utilisez crictl :
# Session de debug interactive sur le nœudkubectl debug node/<node-name> -it --image=busybox:1.37# Depuis le pod de debug, testez la connectiviténslookup mon-registre.example.com# Sur le nœud (SSH ou debug), inspecter le runtimecrictl infocrictl images | grep mon-imageRate limit Docker Hub
Section intitulée « Rate limit Docker Hub »Docker Hub applique des limites de pull sur une fenêtre de 6 heures. Un accès non authentifié est plafonné à 100 pulls par adresse IPv4 (ou par sous-réseau IPv6 /64), un compte Personal gratuit authentifié à 200 pulls, tandis que les abonnements payants (Pro, Team, Business) sont sans limite. Dans un cluster où plusieurs nœuds sortent derrière la même IP publique, ces quotas sont vite atteints, car ils s'appliquent à l'adresse, pas au nœud.
Le message d'erreur contient toomanyrequests :
toomanyrequests: You have reached your pull rate limit.You may increase the limit by authenticating and upgrading:https://www.docker.com/increase-rate-limitSolutions :
- Authentifier les pulls avec un compte Docker Hub pour bénéficier d'une limite plus élevée
- Mettre en place un registry mirror (Harbor, Nexus, registry:2)
- Utiliser
imagePullPolicy: IfNotPresentpour éviter les pulls inutiles
ImagePullBackOff après un upgrade de cluster
Section intitulée « ImagePullBackOff après un upgrade de cluster »Si un upgrade de cluster change le runtime de conteneur (par exemple de Docker à containerd), le cache d'images local est peut-être perdu. Tous les pods doivent re-puller leurs images, ce qui peut provoquer des ImagePullBackOff simultanés si le registre ou le réseau ne tient pas la charge.
Dépannage rapide
Section intitulée « Dépannage rapide »Face à un pod bloqué, commencez toujours par lire l'événement exact avec
kubectl describe pod : c'est lui qui distingue une image mal nommée d'un
registre injoignable ou d'un refus d'authentification. Le tableau ci-dessous
part du symptôme visible pour vous amener à la bonne commande de confirmation.
| Symptôme | Première action | Commande |
|---|---|---|
ImagePullBackOff + not found | Vérifier le tag | kubectl describe pod <pod> → chercher le nom exact de l'image |
ImagePullBackOff + unauthorized | Vérifier l'imagePullSecret | kubectl get pod <pod> -o jsonpath='{.spec.imagePullSecrets}' |
ImagePullBackOff + i/o timeout | Problème réseau | Tester la connectivité depuis le nœud vers le registre |
ImagePullBackOff + toomanyrequests | Rate limit Docker Hub | Authentifier les pulls ou mettre en place un mirror |
ErrImageNeverPull | imagePullPolicy: Never mais image absente | Changer la policy ou pré-charger l'image sur le nœud |
| Un seul nœud en erreur | Problème local au nœud | Vérifier disque, DNS, proxy et runtime du nœud |
À retenir
Section intitulée « À retenir »ErrImagePullest l'erreur immédiate,ImagePullBackOffest l'attente entre deux tentatives, les deux apparaissent en alternancekubectl describe podest la commande clé, le message d'erreur dans les Events contient la raison exacte de l'échec- Le tag
:latestest un piège : s'il est supprimé du registre, les nouveaux pods échouent alors que les anciens continuent de fonctionner. Utilisez des tags explicites - Pour un registre privé, il faut un
imagePullSecretdans le même namespace que le pod, pensez à l'associer au ServiceAccount pour ne pas l'oublier - Docker Hub a un rate limit sur une fenêtre de 6 h, authentifiez vos pulls ou utilisez un mirror local en production
- Si le problème ne touche qu'un seul nœud, c'est probablement un souci réseau, disque ou runtime local
- Après correction, le kubelet retente le pull automatiquement, si vous voulez forcer un retry immédiat, supprimez le pod
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Supply Chain Security : La provenance et la signature des images, pour ne plus dépendre d'un tag mouvant.
- Image Scanning (Grype/Trivy) : L'analyse des vulnérabilités d'une image avant qu'elle n'atteigne le cluster.
- RBAC Kubernetes : Les droits du ServiceAccount et l'accès aux imagePullSecrets d'un namespace.