## 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.

<div class="my-stack-md p-6 bg-surface-container-low border-l-4 border-primary rounded-r-lg">
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.

</div>

## 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 kubectl et un kubeconfig n'accordant que les permissions nécessaires.

- Appliquer – exécuter kubectl apply ou helm 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="devnull@example.com" \
 --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-registry existe dans le même espace de noms et est référencé par imagePullSecrets ou 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="devnull@example.com" 

- Surprises de politique et de cache

- Si vous utilisez :latest , définissez imagePullPolicy: Always pour é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=.lastTimestamp fait remonter les erreurs récentes.

- Logs – kubectl -n demo logs deploy/myapp --all-containers=true --tail=200 pour les erreurs applicatives.

- Sondes et ressources – Les échecs de readiness bloquent le rollout. Vérifiez le chemin et le port de la sonde ; augmentez initialDelaySeconds si l'application démarre lentement. Contrôlez les demandes/limites CPU et mémoire ; OOMKilled indique 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 targetPort correspond 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é myapp dans un espace de noms de staging demo-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/ et kubectl 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 ImagePullBackOff ou CrashLoopBackOff parmi les 50 derniers événements.

Sécurité et rollback

- Gardez kubectl rollout undo deploy/myapp dans 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.