Intro
Ce guide transforme le CI/CD pour API REST d’une théorie abstraite en un flux opérable prêt à l’emploi. Vous allez dresser l’inventaire de l’existant, appliquer le plus petit changement sûr, vérifier par des contrôles observables et revenir en arrière si les signaux se dégradent. Les exemples s’appuient sur une API REST Node.js Express empaquetée avec Docker et déployée sur Kubernetes, mais les patrons se généralisent facilement à d’autres stacks.
Objectifs :
- Observer avant de changer : confirmer versions, topologie et état avec des commandes en lecture seule.
- Limiter le rayon d’impact : modifier un composant circonscrit par exécution.
- Écarter les secrets des journaux : utiliser des espaces réservés et des variables protégées.
- Vérifier les résultats : définir à l’avance les signaux de succès et d’échec.
- Rendre les changements réversibles : documenter un chemin de retour arrière testé.
Cibles supposées (à adapter à votre environnement) :
- API : Node.js 18+ (n’importe quel framework REST)
- Conteneur : Docker 24+
- Orchestrateur : Kubernetes 1.26+
Inventaire des versions et de l’environnement
Objectif : capturer l’exécution courante et le contexte de déploiement sans rien modifier.
Prérequis :
- Accès CLI à votre cluster et à votre registre d’images.
- Un endpoint de santé ou de version dans l’API.
- Un schéma de nommage des environnements faisant autorité (par exemple dev, staging, prod).
Commandes d’inventaire en lecture seule recommandées (remplacez les valeurs fictives) :
# 1) Horodatage pour vos notes d’exploitation
DATE_UTC=$(date -u +"%Y-%m-%dT%H:%M:%SZ"); echo "$DATE_UTC"
# 2) Outils locaux
node --version
npm --version
docker --version
kubectl version --client --output=yaml | sed -n '1,10p'
# 3) Contexte du cluster et de l’espace de noms
kubectl config current-context
kubectl get ns | grep -E '(^| )<namespace>( |$)'
# 4) Image déployée et réplicas
kubectl get deploy <service_name> -n <namespace> -o jsonpath='{.spec.replicas}{"\n"}'
kubectl get deploy <service_name> -n <namespace> -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
# 5) État de déploiement actuel et derniers changements
kubectl rollout status deploy/<service_name> -n <namespace> --timeout=30s || true
kubectl describe deploy <service_name> -n <namespace> | sed -n '1,50p'
# 6) Version rapportée par l’application (lecture seule)
curl -fsS https://api.<env>.example.com/version | jq -r '.version'
# Si un jeton d’authentification est requis :
# curl -fsS -H 'Authorization: Bearer <token_placeholder>' https://api.<env>.example.com/version | jq -r '.version'
Consignez :
- La référence d’image déployée et son digest (si disponible).
- La version rapportée par l’API.
- L’espace de noms, le nombre de réplicas et les événements récents.
Définissez le résultat attendu et les signaux d’échec avant tout changement. Exemple :
- Attendu : après déploiement, l’image tag v1.4.3 tourne ; /version renvoie 1.4.3 ; les probes de readiness restent vertes ; le taux d’erreur est inchangé.
- Signaux d’échec : ImagePullBackOff, CrashLoopBackOff, échecs de readiness au-delà de 5 minutes, hausse du taux de 5xx > 2× la ligne de base.
Chemin de configuration sûr
Objectif : exécuter le plus petit changement justifié avec un rayon d’impact défini et un chemin de retour arrière testé.
Périmètre du changement (exemple) : mettre à jour le tag de l’image du conteneur de l’API dans un espace de noms Kubernetes.
Versions prises en charge et prérequis :
- Le pipeline ci-dessous cible Node.js 18+, Docker Buildx et GitHub Actions. Adaptez à votre CI au besoin.
- Les secrets (identifiants du registre, kubeconfig) doivent être stockés dans le coffre de secrets de votre CI.
Exemple de pipeline GitHub Actions pour construire, tester, analyser et publier :
name: ci
on:
pull_request:
branches: [ main ]
push:
branches: [ main ]
tags: [ 'v*.*.*' ]
env:
REGISTRY: <registry.example.com>
IMAGE_NAME: <org>/<service_name>
jobs:
build_test_publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Récupérer le code
uses: actions/checkout@v4
- name: Utiliser Node.js 18
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Installer les dépendances
run: npm ci
- name: Lint et tests unitaires
run: |
npm run lint
npm test -- --ci --reporters=default --reporters=jest-junit
- name: Calculer le tag d’image
id: meta
run: |
SHA=$(git rev-parse --short HEAD)
echo "tag=$SHA" >> $GITHUB_OUTPUT
- name: Connexion au registre Docker
run: echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin
env:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
- name: Construire et pousser l’image
run: |
docker buildx create --use --name builder || true
docker buildx build \
--platform linux/amd64 \
-t "$REGISTRY/$IMAGE_NAME:${{ steps.meta.outputs.tag }}" \
-t "$REGISTRY/$IMAGE_NAME:latest" \
--push .
# Optionnel : ajoutez une analyse d’image avec votre outil préféré
Job de déploiement (déclenché sur tag vers staging ou promotion manuelle) :
deploy_staging:
if: startsWith(github.ref, 'refs/tags/v')
needs: [ build_test_publish ]
runs-on: ubuntu-latest
environment: staging
steps:
- name: Définir KUBECONFIG
run: |
mkdir -p $HOME/.kube
echo "$KUBE_CONFIG_BASE64" | base64 -d > $HOME/.kube/config
env:
KUBE_CONFIG_BASE64: ${{ secrets.KUBE_CONFIG_BASE64_STAGING }}
- name: Mettre à jour l’image (plus petit changement)
run: |
kubectl -n <namespace> set image deploy/<service_name> \
<container_name>=$REGISTRY/$IMAGE_NAME:${GITHUB_REF_NAME} --record
env:
REGISTRY: <registry.example.com>
IMAGE_NAME: <org>/<service_name>
- name: Attendre le déploiement
run: kubectl -n <namespace> rollout status deploy/<service_name> --timeout=5m
Rayon d’impact : un seul Deployment dans un espace de noms. Aucun changement de configuration globale.
Chemin de retour arrière (doit être testé) :
# Si le déploiement échoue ou que les signaux se dégradent
kubectl -n <namespace> rollout undo deploy/<service_name>
# Ou revenir à une révision connue comme saine
kubectl -n <namespace> rollout undo deploy/<service_name> --to-revision=<n>
Si vous utilisez Helm, privilégiez les mises à niveau atomiques pour un retour arrière intégré :
helm upgrade --install <release_name> <chart_path> \
--namespace <namespace> \
--set image.repository=<registry.example.com>/<org>/<service_name> \
--set image.tag=<version_tag> \
--atomic --timeout 5m
# Revenir en arrière si nécessaire
helm rollback <release_name> <revision> --namespace <namespace>
Conservez toujours les secrets dans le coffre de votre CI. N’affichez jamais de jetons, de clés ni d’identifiants de production dans les journaux.
Vérification et diagnostics
La vérification n’est pas une intuition : c’est un contrat scriptable. Exécutez ces contrôles après chaque changement :
Smoke tests (parcours dorés) :
# Santé
curl -fsS -o /dev/null -w '%{http_code}\n' https://api.<env>.example.com/health
# Readiness (doit renvoyer 200)
curl -fsS -o /dev/null -w '%{http_code}\n' https://api.<env>.example.com/ready
# Version (doit correspondre au tag déployé)
curl -fsS https://api.<env>.example.com/version | jq -r '.version'
Santé de la charge de travail dans Kubernetes :
# Pods et conditions
kubectl get pods -n <namespace> -l app=<service_name> -o wide
kubectl describe deploy <service_name> -n <namespace> | sed -n '1,120p'
# Journaux des nouveaux pods uniquement (ajustez le sélecteur)
kubectl logs -n <namespace> -l app=<service_name> --tail=200 --prefix | grep -E 'ERROR|WARN' || true
Diagnostic canari (si vous dirigez le trafic canari via un en-tête) :
curl -fsS -H 'X-Canary: 1' https://api.<env>.example.com/version | jq -r '.version'
Vérification des migrations de base de données (si votre service utilise une table de migrations) :
# Exemple Postgres via psql ; utilisez une chaîne de connexion en lecture seule issue de votre coffre de secrets
# NE collez PAS de véritables identifiants
psql '<readonly_conn_string_placeholder>' -c 'select version from schema_migrations order by applied_at desc limit 1;'
Définissez des seuils de réussite/échec :
- Réussite : health et readiness renvoient 200 ; la version correspond à la cible ; aucun nouveau ERROR dans les journaux sur 5 minutes ; redémarrages de pods inchangés.
- Échec : hausse soutenue du taux de 5xx ; readiness instable ; > 1 redémarrage par pod en 5 minutes ; version de migration manquante ou incohérente.
Modes de défaillance et remédiation
Problèmes courants et actions à entreprendre :
- ImagePullBackOff
- Signal : les événements kubectl indiquent des échecs de pull ; les pods ne démarrent pas.
- Causes probables : mauvais tag, authentification au registre manquante.
- Remédiation : vérifier l’existence de l’image ; corriger le secret ; redéployer le même tag ; si critique dans le temps, revenir en arrière :
kubectl -n <namespace> rollout undo deploy/<service_name>
- CrashLoopBackOff
- Signal : les conteneurs redémarrent à répétition.
- Causes : variable d’environnement erronée, configuration ou migration incompatible.
- Remédiation : récupérer les journaux du pod en échec, revenir à la dernière révision saine :
kubectl -n <namespace> logs deploy/<service_name> --tail=200
kubectl -n <namespace> rollout undo deploy/<service_name>
- Échecs de probe de readiness
- Signal : le déploiement ne se termine pas avant l’expiration du délai.
- Causes : démarrages à froid, dépendance manquante, mauvais port.
- Remédiation : revenir en arrière si la probe ne se stabilise pas rapidement ; revoir la configuration de la probe ; valider les services dépendants.
- Échecs de migration de schéma
- Signal : le nouveau code attend une colonne inexistante ; les erreurs explosent au démarrage.
- Stratégie : appliquer des migrations de type expand-contract (ajouts d’abord, suppressions en dernier). Si l’impact est déjà là, revenir l’API en arrière et appliquer une migration corrective compatible.
- Autorisation ou secret mal configuré
- Signal : 401/403 sur les appels internes ; échecs d’intégrations tierces.
- Remédiation : mettre à jour le secret dans le coffre de la CI, redémarrer le déploiement et relancer les smoke tests. Évitez d’imprimer des valeurs de secrets.
- Régression de performance
- Signal : latence en hausse, timeouts, CPU bridé.
- Remédiation : augmenter temporairement les réplicas, ajuster les limites de ressources, ou revenir en arrière pendant l’investigation.
Préférez avancer avec un correctif minimal si l’échec est bien compris et le risque faible. Sinon, revenez en arrière dans les limites de votre budget d’erreur prédéfini.
Liste de contrôle opérationnelle
Avant le changement :
- Confirmer le contexte du cluster, l’espace de noms et la charge visée.
- Capturer l’image courante, les réplicas et la version rapportée par l’API.
- Définir les métriques de succès et les seuils d’échec.
- Vérifier que tous les secrets et identifiants du registre sont dans le coffre de la CI.
- Si vous utilisez des migrations de base de données, confirmer la rétrocompatibilité et un plan de retour/désactivation testé.
Pendant le changement :
- Appliquer la plus petite modification (tag d’image uniquement, ou une seule valeur Helm).
- Attendre la fin du déploiement avec un délai d’expiration.
- Lancer les smoke tests et comparer aux signaux attendus.
Après le changement :
- Surveiller les journaux et les redémarrages de pods pendant au moins une fenêtre complète d’autoscaling.
- Vérifier les endpoints critiques métier (ex. POST /orders) avec une charge de test non sensible.
- Documenter le résultat : horodatage, version, approbateur, liens vers les exécutions CI.
Si un seuil d’échec est franchi à tout moment, exécutez immédiatement la commande de retour arrière et consignez la raison.
Conclusion
Un CI/CD fiable pour API REST est une discipline : inventorier d’abord, appliquer le plus petit changement sûr, vérifier avec des contrôles explicites et garder un retour arrière prêt. Utilisez des images versionnées, des secrets protégés et des observations en lecture seule pour réduire le risque. Pour la suite, choisissez un changement à faible risque (par exemple, mettre à jour le tag d’image en staging), exécutez les commandes d’inventaire, déployez avec le pipeline d’exemple, lancez les smoke tests et entraînez-vous au retour arrière une fois. La répétition ancre un flux de travail observable, réversible et sûr à toute échelle.