Intro
Le déploiement vers Kubernetes depuis GitLab CI/CD devient fiable quand on maîtrise les quatre pièces mobiles : le runner, le kubeconfig, l'authentification au registre et la vérification du rollout. Ce guide propose des correctifs concrets pour les échecs les plus fréquents et des extraits copier‑coller adaptables dès aujourd'hui. Vous apprendrez à créer un kubeconfig au privilège minimal, diagnostiquer les erreurs de pipeline et de cluster, vérifier les rollouts et exécuter un petit pilote avant d'étendre.
Acronymes : CI/CD (Continuous Integration / Continuous Delivery) : intégration continue / livraison continue. RBAC (Role‑Based Access Control) : contrôle d'accès basé sur les rôles. API (Application Programming Interface) : interface de programmation d'application.
Aperçu du workflow
Un chemin clair, étape par étape, du commit aux pods sains maintient les problèmes isolés à une seule phase :
- Build – produire une image conteneur taguée avec le SHA du commit.
- Push – s'authentifier au registre et pousser l'image.
- Configurer – fournir
kubectlet un kubeconfig n'accordant que les permissions nécessaires. - Appliquer – exécuter
kubectl applyouhelm upgrade --install. - Attendre – surveiller le statut du rollout avec un délai d'expiration.
- Vérifier – lancer un test de fumée rapide contre le Service ou l'Ingress.
- Rollback – revenir au dernier ReplicaSet sain si la vérification échoue.
Gardez chaque étape dans son propre job afin de voir exactement où survient l'échec.
Configuration : runners et kubeconfig
Compte de service et RBAC (limité à l'espace de noms)
Créez un compte de service dédié avec les droits minimaux requis dans l'espace de noms cible :
apiVersion: v1
kind: Namespace
metadata:
name: demo
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: ci-deployer
namespace: demo
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ci-deployer
namespace: demo
rules:
- apiGroups: ["", "apps", "extensions
tesources: ["pods", "deployments", "replicasets", "services", "configmaps", "secrets
ebri: ["get", "list", "watch", "create", "update", "patch", "delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ci-deployer
namespace: demo
subjects:
- kind: ServiceAccount
name: ci-deployer
namespace: demo
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ci-deployer
Générez un jeton à durée de vie courte (Kubernetes 1.24+) :
kubectl -n demo create token ci-deployer > ci-deployer.token
Construisez un kubeconfig pointant vers votre serveur API et incluant les données CA, puis stockez le YAML complet comme variable CI protégée et masquée nommée KUBE_CONFIG_B64 (encodé base64) ou KUBE_CONFIG_YAML (multiligne).
Écriture du kubeconfig dans le job
# .gitlab-ci.yml (extrait kubeconfig)
before_script:
- mkdir -p ~/.kube
- |
if [ -n "$KUBE_CONFIG_B64" ]; then
echo "$KUBE_CONFIG_B64" | base64 -d > ~/.kube/config
else
echo "$KUBE_CONFIG_YAML" > ~/.kube/config
fi
- chmod 600 ~/.kube/config
Choix de l'image du runner
Utilisez une image de job déjà pourvue de kubectl (par exemple bitnami/kubectl:latest) ou installez kubectl à l'exécution. Si vous construisez des images avec Docker‑in‑Docker, le runner doit s'exécuter en mode privilégié. Lorsque les runners privilégiés ne sont pas possibles, passez à un constructeur sans racine comme Kaniko ou BuildKit.
Pipeline de référence : build, push et deploy
stages: [build, deploy]
variables:
IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
KUBE_NAMESPACE: "demo"
# Build et push de l'image avec Docker-in-Docker (nécessite un runner privilégié)
build:
stage: build
image: docker:25
services:
- name: docker:25-dind
command: ["--mtu=1460\
variables:
DOCKER_TLS_CERTDIR: /certs
script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
- docker build -t "$IMAGE" .
- docker push "$IMAGE"
rules:
- if: "$CI_COMMIT_BRANCH"
# Déploiement avec kubectl apply et attente du rollout
deploy:
stage: deploy
image: bitnami/kubectl:latest
before_script:
- mkdir -p ~/.kube
- if [ -n "$KUBE_CONFIG_B64" ]; then echo "$KUBE_CONFIG_B64" | base64 -d > ~/.kube/config; else echo "$KUBE_CONFIG_YAML" > ~/.kube/config; fi
- chmod 600 ~/.kube/config
script:
# S'assurer que l'espace de noms existe
- kubectl get ns "$KUBE_NAMESPACE" || kubectl create ns "$KUBE_NAMESPACE"
# Créer ou mettre à jour le secret de pull d'image pour l'espace de noms (une fois par ns)
- |
kubectl -n "$KUBE_NAMESPACE" create secret docker-registry gitlab-regcred \
--docker-server="$CI_REGISTRY" \
--docker-username="$CI_REGISTRY_USER" \
--docker-password="$CI_REGISTRY_PASSWORD" \
--docker-email="[email protected]" \
--dry-run=client -o yaml | kubectl apply -f -
# Appliquer les manifests (le deployment utilise imagePullSecrets)
- kubectl -n "$KUBE_NAMESPACE" apply -f k8s/
# Définir l'image du conteneur sur le tag SHA du commit (patch optionnel)
- kubectl -n "$KUBE_NAMESPACE" set image deploy/myapp myapp-container="$IMAGE" --record=true || true
# Attendre le rollout avec timeout
- kubectl -n "$KUBE_NAMESPACE" rollout status deploy/myapp --timeout=120s
# Vérification simple via ClusterIP avec busybox curl (optionnel)
- kubectl -n "$KUBE_NAMESPACE" run tmp-curl --image=busybox:1.36 --restart=Never --rm -it -- curl -sS myapp:8080/healthz
environment:
name: demo/$CI_COMMIT_REF_NAME
url: https://demo.example.com
rules:
- if: "$CI_COMMIT_BRANCH == \"main\""
Exemple de manifest Deployment (k8s/deploy.yaml) :
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
serviceAccountName: ci-deployer
imagePullSecrets:
- name: gitlab-regcred
containers:
- name: myapp-container
image: registry.example.com/group/project:CHANGE_ME
imagePullPolicy: Always
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
ports:
- port: 8080
targetPort: 8080
Dépannage : échecs de pipeline
| Symptôme | Cause probable | Correctif rapide |
|---|---|---|
| Job bloqué en pending | Aucun runner ne correspond aux tags du job ou tous les runners sont occupés | Ajoutez des tags correspondant à un runner enregistré ; vérifiez l'état du runner dans les paramètres du projet |
docker build échoue ou ne peut pas se connecter au démon Docker | DinD non activé ou runner non privilégié | Activez le mode privilégié pour le runner ou passez à Kaniko ; assurez‑vous que DOCKER_HOST et DOCKER_TLS_CERTDIR sont définis avec DinD |
kubectl: command not found | L'image du job ne contient pas kubectl | Utilisez une image avec kubectl (ex. bitnami/kubectl) ou installez‑le dans le job |
The request could not be authenticated / Forbidden | Jeton kubeconfig invalide/expiré ou RBAC insuffisant | Rafraîchissez le jeton du compte de service, vérifiez l'URL du serveur API et le CA dans le kubeconfig, et accordez les verbes requis sur les ressources de l'espace de noms cible |
apply échoue avec no matches for kind ou invalid | Mauvaise apiVersion ou incompatibilité de schéma | Validez localement avec kubectl apply --dry-run=client -f k8s/ et fixez les bonnes valeurs apiVersion |
| Espace de noms introuvable | Déploiement dans un espace de noms qui n'existe pas | Créez l'espace de noms avant d'appliquer les ressources (kubectl create ns <nom>) |
Dépannage : erreurs de pull d'image
Le pod affiche ErrImagePull ou ImagePullBackOff :
- Mauvais tag d'image
- Vérifiez le tag exact dans le Deployment :
kubectl -n demo get deploy myapp -o jsonpath='{.spec.template.spec.containers[0].image}'
- Privilégiez des tags immuables comme
$CI_COMMIT_SHORT_SHA.
- Authentification au registre manquante
- Assurez‑vous qu'un secret
docker-registryexiste dans le même espace de noms et est référencé parimagePullSecretsou le compte de service. - Recréez le secret si les identifiants ont changé :
kubectl -n demo delete secret gitlab-regcred --ignore-not-found
kubectl -n demo create secret docker-registry gitlab-regcred \
--docker-server="$CI_REGISTRY" \
--docker-username="$CI_REGISTRY_USER" \
--docker-password="$CI_REGISTRY_PASSWORD" \
--docker-email="[email protected]"
- Surprises de politique et de cache
- Si vous utilisez
:latest, définissezimagePullPolicy: Alwayspour éviter les images obsolètes. - Mieux : épinglez au SHA du commit et conservez
imagePullPolicy: IfNotPresent. - Les secrets sont limités à l'espace de noms ; créez le même secret de pull dans chaque espace de noms cible.
Pour confirmer la correction, supprimez le pod en échec et laissez le ReplicaSet le recréer, ou déclenchez un redémarrage du rollout :
kubectl -n demo rollout restart deploy/myapp
Dépannage : vérifications de rollout et débogage
Automatisez l'attente et enrichissez les logs d'échec :
kubectl -n demo rollout status deploy/myapp --timeout=120s || {
echo "Rollout did not complete in time"
kubectl -n demo get pods -o wide
kubectl -n demo describe deploy/myapp
kubectl -n demo get events --sort-by=.lastTimestamp | tail -n 50
exit 1
}
Si le rollout échoue ou les pods plantent :
- Describe et événements –
kubectl -n demo describe pod <nom>montre les problèmes de pull d'image, d'ordonnancement et de sondes.kubectl -n demo get events --sort-by=.lastTimestampfait remonter les erreurs récentes. - Logs –
kubectl -n demo logs deploy/myapp --all-containers=true --tail=200pour les erreurs applicatives. - Sondes et ressources – Les échecs de readiness bloquent le rollout. Vérifiez le chemin et le port de la sonde ; augmentez
initialDelaySecondssi l'application démarre lentement. Contrôlez les demandes/limites CPU et mémoire ;OOMKilledindique une limite mémoire trop basse. - Services et Ingress – Assurez‑vous que les sélecteurs du Service correspondent exactement aux labels des pods. Confirmez que
targetPortcorrespond au port du conteneur. Pour l'Ingress, validez l'hôte, le TLS et que les backends du Service sont sains. - ConfigMaps et Secrets – Clés manquantes ou montages incorrects provoquent souvent
CrashLoopBackOff. Comparez le spec du pod en cours au manifest :kubectl -n demo get pod <nom> -o yaml.
Rollback rapide :
kubectl -n demo rollout undo deploy/myapp
Vérifications de sécurité scriptées dans CI
Ajoutez des contrôles pré‑déploiement et post‑déploiement pour réduire l'incertitude :
# Valider les manifests avant apply
kubectl -n demo apply --dry-run=client -f k8s/
# Voir ce qui va changer
kubectl -n demo diff -f k8s/ || true
# Après rollout, confirmer la readiness et la santé HTTP
kubectl -n demo get pods -l app=myapp -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.phase}{"\n"}{end}'
# Curl via ClusterIP depuis un pod temporaire
kubectl -n demo run netcheck --image=busybox:1.36 --rm -it --restart=Never -- \
sh -c 'wget -qO- http://myapp:8080/healthz'
Plan pilote local
Commencez petit, mesurez et gardez le tout facile à inspecter localement avant d'élargir.
Périmètre
- Un service nommé
myappdans un espace de noms de stagingdemo-pilot. - Tags d'image immuables utilisant le SHA du commit.
Pipeline
- Jobs :
build,deploy,verify. - Pré‑déploiement :
kubectl apply --dry-run=client -f k8s/etkubectl diff -f k8s/. - Déploiement : appliquer les manifests, définir l'image sur le SHA, attendre 120 s.
- Vérification : curl
/healthz, récupérer les logs et lister les événements.
Critères de succès mesurables
- Rollout terminé en moins de 2 minutes.
- Endpoint de santé renvoie HTTP 200.
- Aucun événement
ImagePullBackOffouCrashLoopBackOffparmi les 50 derniers événements.
Sécurité et rollback
- Gardez
kubectl rollout undo deploy/myappdans le job en cas d'échec. - Utilisez un espace de noms et un compte de service distincts ; aucun secret de production.
Lorsque le pilote s'exécute proprement sur plusieurs itérations, reproduisez le même schéma vers un environnement de staging plus large ou en canary.
Conclusion
Séparez les étapes de déploiement, fournissez un kubeconfig sécurisé, et scriptez à la fois l'attente du rollout et la vérification pour réduire le temps de débogage. Démarrez avec le plan pilote, mesurez les résultats, et étendez‑vous dès que vous obtenez systématiquement des rollouts propres, des sondes saines et des rollbacks en une commande.