30/01/2026

Déploiement continu avec Docker et Kubernetes

Le déploiement continu (Continuous Deployment – CD) représente l’étape ultime d’un pipeline d’intégration et de livraison continues (CI/CD). L’objectif est simple : fournir rapidement et en continu des mises à jour fiables en production.

Grâce à Docker et Kubernetes, le déploiement continu gagne en robustesse et en flexibilité. Docker permet de livrer des artefacts applicatifs uniformes et portables sous forme d’images, tandis que Kubernetes orchestre leur exécution à grande échelle, avec des stratégies de mise à jour maîtrisées et des outils comme Helm pour simplifier la gestion des déploiements.

Dans ce chapitre, nous allons :

  • Construire un pipeline CI/CD pour automatiser les déploiements Docker
  • Explorer les stratégies de déploiement continu dans Kubernetes (rolling updates, canary releases)
  • Apprendre à gérer les déploiements en production à l’aide de Helm, véritable package manager de Kubernetes

Concepts fondamentaux du CI/CD

L’intégration continue (CI)

L’intégration continue vise à vérifier automatiquement l’intégrité du code source à chaque commit.

Les développeurs partagent un dépôt Git commun ; à chaque modification, un serveur d’intégration (GitLab CI, Jenkins, GitHub Actions, etc.) :

  • Récupère le code
  • Installe les dépendances
  • Exécute les tests unitaires et d’intégration
  • Construit les artefacts ou images Docker nécessaires
  • Publie le résultat dans un registre (Docker Registry, Nexus, etc…)

Cette première phase permet d’éviter les régressions et de garantir que le code reste toujours exécutable.

Le déploiement continu (CD)

Le déploiement continu automatise la mise à jour des environnements de test, préproduction et production.

Une fois le build validé, le pipeline déclenche :

  • Le déploiement de la nouvelle image Docker sur Kubernetes
  • La surveillance du bon déroulement du rollout
  • Éventuellement le rollback en cas d’échec

Le déploiement devient ainsi un processus technique maîtrisé, et non plus une opération manuelle risquée.

Mise en place d’un pipeline CI/CD avec Docker

Un pipeline CI/CD automatise la totalité du cycle de vie applicatif :

  1. Intégration continue (CI) : compilation, tests unitaires, build Docker
  2. Livraison continue (CD) : push de l’image dans un registre (Docker Hub, GitLab Registry, etc…)
  3. Déploiement continu : mise à jour automatique de l’application sur l’environnement Kubernetes

L’objectif est d’éliminer les opérations manuelles sujettes à l’erreur et de garantir une cohérence absolue entre les environnements de développement, de test et de production.

Préparation de l’environnement

Sur notre machine, Docker et Minikube sont déjà installés. Vérifions que tout est opérationnel :

docker --version
systemctl status docker 
minikube status

Si tout est prêt, nous pouvons travailler dans un répertoire de projet dédié :

mkdir -p /opt/ci-cd-demo/app 
cd /opt/ci-cd-demo/app

Création d’une application conteneurisée

Pour la démonstration, utilisons une simple page web index.html servie par Nginx :

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>CI/CD Demo</title>
</head>
<body>
  <h1>Bienvenue sur la version 1.0 de notre déploiement automatisé</h1>
</body>
</html>

Créons notre Dockerfile :

FROM nginx:1.27-alpine 
COPY index.html /usr/share/nginx/html/index.html 
EXPOSE 80

Construisons et testons l’image :

docker build -t ci-cd-demo:1.0 . 
docker run -d -p 8080:80 ci-cd-demo:1.0

Vérifions le résultat : ouvrons dans notre navigateur : http://localhost:8080

Nous devrions voir le message affichant la version 1.0.

Préparation des accès GitHub : clé SSH et configuration locale

Avant de pouvoir initialiser un dépôt Git et y pousser le code source, il est indispensable que le poste de travail soit configuré pour communiquer de manière sécurisée avec GitHub.

Dans un environnement professionnel, cette communication se fait toujours via une authentification SSH par clé publique — un mécanisme plus sûr et plus automatisable qu’un simple mot de passe.

Remarque préalable :

Le cours part du principe que le lecteur dispose déjà de son compte GitHub et d’un dépôt distant existant, ainsi que d’une paire de clés SSH prête à l’emploi.

La séquence ci-dessous illustre la création d’une telle clé et sa configuration locale. Elle ne doit être exécutée qu’une fois par machine. La clé publique générée doit être ajoutée dans votre compte GitHub.

Initialisation du dépôt Git

Exécutons les commandes suivantes :

cd /opt/ci-cd-demo
git init
git add .
git commit -m "Initial commit : version 1.0"

Cette séquence montre l’initialisation complète du dépôt et la première synchronisation réussie avec GitHub.

  • La branche principale a été renommée en main, conformément aux conventions modernes
  • Le dépôt distant est défini sous le nom origin, pointant vers le dépôt SSH
  • Le premier commit (Initial commit : version 1.0) a été transmis au serveur GitHub.

Le message branch 'main' set up to track 'origin/main' confirme que la branche locale est maintenant liée à la branche distante : les prochains push et pull se feront automatiquement sur ce dépôt.

Vérification du dépôt

Pour confirmer que tout est en place :

git remote -v

Et pour vérifier l’état de la branche :

git status

Tout est donc synchronisé, et le projet est prêt à être automatisé via le pipeline CI/CD.

Configuration d’un pipeline GitHub Actions

À présent que le dépôt Git GitHub existe et que le push initial est effectué, nous allons définir un pipeline CI/CD avec GitHub Actions. Ce pipeline va :

  1. Construire l’image Docker
  2. Tester l’image (smoke test)
  3. Publier l’image dans un registre (GHCR par défaut)
  4. Déployer sur Kubernetes avec kubectl.

Prérequis pour la mise en place :

  • Compte GitHub et dépôt existant : le projet repose sur le dépôt GitHub déjà initialisé et synchronisé avec le code local. Ce dépôt servira à héberger le fichier de configuration du pipeline et les actions automatisées.
  • Aucun compte supplémentaire requis pour GHCR : le pipeline publiera les images Docker dans le GitHub Container Registry (GHCR). Ce registre est directement intégré à GitHub : il n’est donc pas nécessaire de créer de compte ni de générer de jeton d’accès externe. Le workflow CI/CD utilisera simplement le jeton interne GITHUB_TOKEN, fourni automatiquement par GitHub pour chaque exécution, à condition de lui attribuer la permission suivante :
permissions:
  contents: read
  packages: write

Cette autorisation permet au pipeline de pousser des images vers GHCR en toute sécurité.

  • Alternative optionnelle : Docker Hub : si l’on souhaite utiliser Docker Hub à la place de GHCR, il faut disposer d’un compte Docker Hub et enregistrer deux secrets dans la section Settings → Secrets and variables → Actions du dépôt GitHub.
DOCKERHUB_USERNAME : identifiant Docker Hub
DOCKERHUB_TOKEN : jeton d’accès personnel généré depuis Docker Hub.

Ces secrets seront ensuite référencés dans le workflow pour l’étape d’authentification au registre.

Emplacement et nom du workflow

Créons le fichier ci-cd.yml dans notre repo, à savoir ici :

mkdir -p .github/workflows

Voici notre pipeline complet :

name: ci-cd-github

on:
  push:
    branches: [ 'main' ]
  pull_request:
    branches: [ 'main' ]

permissions:
  contents: read
  packages: write

env:
  IMAGE_NAME: ci-cd-demo
  REGISTRY: ghcr.io/jgak
  SHORT_SHA: ${{ github.sha }}

jobs:
  build_test_push:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build image
        working-directory: ./app
        run: |
          docker build -t $REGISTRY/$IMAGE_NAME:${SHORT_SHA} .

      - name: Smoke test
        run: |
          docker run -d -p 8080:80 --name ci-cd-test $REGISTRY/$IMAGE_NAME:${SHORT_SHA}
          sleep 3
          curl -f http://localhost:8080 | grep -E 'Bienvenue|Version|Deployee|Deploy'
          docker rm -f ci-cd-test

      - name: Push image to GHCR
        if: github.ref == 'refs/heads/main'
        run: |
          docker push $REGISTRY/$IMAGE_NAME:${SHORT_SHA}

  deploy:
    if: github.ref == 'refs/heads/main'
    needs: [ build_test_push ]
    runs-on: self-hosted

    steps:
      - name: Contexte Kubernetes
        run: |
          kubectl config current-context
          kubectl get nodes

      - name: Deploiement
        env:
          IMAGE_REF: ghcr.io/jgak/ci-cd-demo:${{ github.sha }}
        run: |
          kubectl set image deployment/ci-cd-demo ci-cd-demo=${IMAGE_REF}
          kubectl rollout status deployment/ci-cd-demo --timeout=120s

Commit et push :

git add .github/workflows/ci-cd.yml 
git commit -m 'Ajout workflow GitHub Actions : build/test/push GHCR + deploy' 
git push

Dès cet instant, le fichier du workflow est intégré au dépôt GitHub, et GitHub Actions le détecte automatiquement.

Vérifier l’exécution du workflow sur GitHub

  1. Ouvrons notre dépôt sur GitHub
  2. Cliquons sur l’onglet « Actions » (en haut du dépôt)
  3. Un workflow nommé ci-cd-github (nom défini dans le fichier YAML)
  4. GitHub affiche la liste des exécutions :
    Un premier run automatique se déclenche suite au git push
    Nous pouvons cliquer sur ce run pour suivre les logs détaillés en temps réel

Chaque étape du workflow s’affichera (checkout, build, test, push, deploy) avec un indicateur : réussie ou échouée.

Configuration d’un runner GitHub Actions

Les runners GitHub Actions sont les environnements qui exécutent réellement les étapes des workflows définis dans les fichiers .yml.
Lorsqu’un workflow se déclenche, GitHub envoie ses jobs vers un runner disponible.

Il en existe deux grandes catégories.

Les runners hébergés par GitHub (GitHub-hosted runners) : ce sont des machines virtuelles fournies et gérées par GitHub. Elles sont recréées à chaque exécution et disposent d’un environnement préconfiguré (Ubuntu, Windows ou macOS).

Avantages :

  • Aucune installation requise
  • Environnement propre et éphémère à chaque exécution
  • Idéal pour les étapes de build ou de test

Inconvénients :

  • Accès limité à notre réseau local
  • Impossible d’interagir directement avec notre cluster Kubernetes local (par exemple Minikube)
  • Nécessite de transmettre des secrets (KUBECONFIG_DATA) pour le déploiement à distance

Les runners auto-hébergés (self-hosted runners) : ce sont des machines que nous gérons nous-mêmes (un serveur, une VM…) reliées à GitHub via un petit agent. Elles exécutent les jobs directement sur notre propre infrastructure.

Avantages :

  • Contrôle total sur l’environnement (Docker, Minikube, kubectl, etc…)
  • Accès direct au cluster Kubernetes local

Inconvénients :

  • Nécessite une installation et une maintenance minimale
  • L’environnement n’est pas réinitialisé à chaque exécution (il faut gérer l’isolation)
  • Moins sécurisé sur un dépôt public

Nous utilisons une VM Debian 12 sur laquelle Docker et Minikube sont déjà installés et fonctionnels. Le but du pipeline est de déployer automatiquement sur ce cluster local, après avoir construit et poussé l’image sur GHCR.

Le runner auto-hébergé est donc le choix le plus simple et le plus cohérent :

  • Il a accès directement à Minikube
  • Il ne nécessite pas de secrets KUBECONFIG
  • Il rend la démonstration totalement autonome (tout se passe sur une seule machine)

Mise en place pas à pas du runner self-hosted

GitHub fournit un script complet d’installation et de configuration directement depuis l’interface du dépôt. Nous allons suivre la procédure indiquée par GitHub, sans être root ni utiliser sudo.

Cliquons sur « New self-hosted runner » :

Depuis /opt/ci-cd-demo, créons un répertoire pour le service :

mkdir actions-runner && cd actions-runner

Téléchargeons le binaire du runner :

curl -o actions-runner-linux-x64-2.329.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.329.0/actions-runner-linux-x64-2.329.0.tar.gz

Extraire le package :

tar xzf ./actions-runner-linux-x64-2.329.0.tar.gz

Ces commandes téléchargent et décompressent le binaire officiel du runner GitHub. Le répertoire actions-runner contient maintenant les fichiers nécessaires à la configuration.

Avant de configurer le runner, il faut s’assurer que le cluster Kubernetes local est prêt à recevoir le déploiement. Or, l’image Docker ci-cd-demo:1.0 doit être chargée dans le démon Docker interne de Minikube, sans quoi le déploiement échouera avec une erreur ErrImagePull.

Sous l’utilisateur root, chargeons d’abord l’image Docker :

minikube image load ci-cd-demo:1.0

Cette commande copie l’image construite sur le système dans le démon Docker interne de Minikube.

Depuis /opt/ci-cd/demo, créons ensuite les fichiers YAML de base pour Kubernetes :

mkdir kubernetes
cat > kubernetes/deployment.yaml <<'YAML'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ci-cd-demo
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app: ci-cd-demo
  template:
    metadata:
      labels:
        app: ci-cd-demo
    spec:
      containers:
      - name: ci-cd-demo
        image: ci-cd-demo:1.0
        ports:
        - containerPort: 80
YAML

cat > kubernetes/service.yaml <<'YAML'
apiVersion: v1
kind: Service
metadata:
  name: ci-cd-demo
spec:
  type: NodePort
  selector:
    app: ci-cd-demo
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080
YAML

Ce qui donne :

Ces deux fichiers définissent respectivement le Deployment (avec trois réplicas) et le Service d’exposition sur le port 30080.

Appliquons ensuite la configuration :

kubectl apply -f kubernetes/

Vérifions la bonne création des ressources :

kubectl get deploy,po,svc -o wide

Nous devrions voir le Deployment, trois Pods créés et un Service de type NodePort.

Une fois cette baseline validée, stoppons le cluster :

minikube stop

Il est essentiel que l’utilisateur debian, qui exécute le runner GitHub Actions, dispose lui aussi d’un contexte Minikube fonctionnel.

Relançons le cluster Minikube sous l’utilisateur debian

minikube start

Rechargeons l’image Docker dans le démon interne de Minikube (cette fois pour le contexte debian) :

minikube image load ci-cd-demo:1.0

Réappliquons le déploiement :

cd /opt/ci-cd-demo 
kubectl apply -f kubernetes/

Vérifions que tout fonctionne :

kubectl get deploy,po,svc -o wide

Tous les Pods doivent apparaître en statut Running, confirmant que le cluster est prêt pour l’automatisation du déploiement.

Depuis le répertoire /opt/ci-cd-demo/actions-runner :

./config.sh --url https://github.com/jgak/it-connect --token ABK4CDJVJJM2O4O6P5O3SC3I7FI4G

Remarque importante : le jeton affiché --token XXXXX… est temporaire et spécifique au dépôt.

Il est généré automatiquement par GitHub et expire après quelques minutes. Si nous refaisons la procédure plus tard, il faut cliquer à nouveau sur « New runner » dans les paramètres du dépôt pour obtenir un nouveau jeton.

Le script config.sh pose quelques questions :

  • Nom du runner (par défaut, le nom de la machine)
  • Étiquettes optionnelles (par exemple linux, deb12, minikube)
  • Répertoire de travail (par défaut _work)

Une fois terminé, GitHub affichera une confirmation du type :

√ Connected to GitHub 
√ Runner successfully added 
√ Runner connection is good

Pour un premier test :

./run.sh

Le terminal doit afficher :

√ Connected to GitHub
Current runner version: '2.329.0'
2025-10-22 20:55:08Z: Listening for Jobs

Cela signifie que la VM attend les instructions de GitHub et est prête à exécuter le job deploy. À ce stade, nous retournons dans l’onglet « Actions » → « ci-cd-github », et cliquons sur « deploy » pour déclencher le job.

Pour vérifier la configuration côté GitHub, nous ouvrons : « Settings » → « Actions » → « Runners » → « Self-hosted runners ».

Le workflow complet est maintenant opérationnel :

  1. Le job build_test_push s’exécute
  2. Le job deploy est transmis à notre runner self-hosted
  3. Ce dernier met à jour le déploiement Kubernetes local via :
kubectl set image deployment/ci-cd-demo ci-cd-demo=ghcr.io/jgak/ci-cd-demo:${GITHUB_SHA} 
kubectl rollout status deployment/ci-cd-demo --timeout=120s

Stratégies de déploiement en continu dans Kubernetes

Une fois le pipeline CI/CD opérationnel et la publication d’images automatisée, la question essentielle devient : comment déployer ces nouvelles versions dans Kubernetes sans interrompre le service ?

Kubernetes offre nativement plusieurs stratégies de déploiement continu, adaptées à différents besoins de stabilité, de performance et de gestion du risque.
Dans un contexte de production, ces stratégies sont souvent combinées et automatisées à l’aide d’outils tels que Helm, qui permet une gestion centralisée et déclarative des déploiements complexes.

Kubernetes repose sur le principe fondamental de déploiement déclaratif : l’administrateur décrit l’état souhaité (nombre de réplicas, image à utiliser, version, configuration, etc.), et le contrôleur de déploiement se charge de faire converger l’état actuel vers l’état cible, tout en respectant la stratégie choisie.
Deux des approches les plus courantes sont les rolling updates et les canary releases.

Les Rolling Updates

La stratégie Rolling Update (mise à jour progressive) est la méthode par défaut utilisée par Kubernetes lorsqu’un déploiement existant est modifié (nouvelle image Docker, nouvelle configuration, etc.).
Elle consiste à remplacer progressivement les anciens pods par les nouveaux, sans jamais interrompre totalement le service.

Principe

Lorsqu’une nouvelle image est spécifiée :

  1. Kubernetes crée un ou plusieurs nouveaux pods avec la nouvelle version
  2. Simultanément, il supprime progressivement les anciens pods
  3. Durant toute la transition, le nombre total de pods disponibles reste stable pour garantir la continuité du service

Ce processus est contrôlé par les paramètres maxSurge et maxUnavailable dans la section strategy du manifeste du déploiement.

Exemple concret :

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxUnavailable: 1
    maxSurge: 1
  • maxUnavailable: 1 → au maximum un pod peut être indisponible pendant le déploiement
  • maxSurge: 1 → Kubernetes peut créer un pod supplémentaire temporairement pour assurer le maintien de la capacité

Avantages :

  • Aucun temps d’arrêt (zero downtime)
  • Mise à jour contrôlée et réversible
  • Simple à automatiser dans un pipeline CI/CD

Inconvénients :

  • Tous les utilisateurs migrent progressivement vers la nouvelle version sans distinction : si la nouvelle version contient un bug critique, il faut effectuer un rollback global

Rollback

Kubernetes garde en mémoire les anciennes révisions de déploiement. En cas d’échec, on peut revenir à la version précédente :

kubectl rollout undo deployment/ci-cd-demo

Suivi du déploiement

Pour suivre l’avancement d’une mise à jour :

kubectl rollout status deployment/ci-cd-demo 
kubectl get replicaset

Kubernetes affiche le nombre de pods créés, mis à jour et disponibles en temps réel.

Les Canary Releases

La stratégie Canary Release consiste à déployer une nouvelle version de l’application sur un sous-ensemble restreint d’utilisateurs avant de généraliser la mise à jour à l’ensemble du trafic. Elle permet d’observer le comportement de la nouvelle version en conditions réelles, sans impacter la totalité des utilisateurs.

Principe

  1. On déploie une deuxième version du déploiement (par exemple ci-cd-demo-canary) avec la nouvelle image
  2. Le service Kubernetes est configuré pour distribuer une partie du trafic vers cette nouvelle version (ex. 5 à 10 %)
  3. Si les indicateurs de performance et de fiabilité sont bons, on augmente progressivement la part du trafic, jusqu’à ce que la version canary remplace totalement l’ancienne

Exemple de manifeste Canary :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ci-cd-demo-canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ci-cd-demo
      version: canary
  template:
    metadata:
      labels:
        app: ci-cd-demo
        version: canary
    spec:
      containers:
      - name: ci-cd-demo
        image: ghcr.io/jgak/ci-cd-demo:2.0
        ports:
        - containerPort: 80

Le Service correspondant peut alors router vers les deux versions grâce à une sélection d’étiquettes (version: stable et version: canary).

Exemple de Service associé :

apiVersion: v1
kind: Service£
metadata:
  name: ci-cd-demo
spec:
  selector:
    app: ci-cd-demo
  ports:
    - port: 80
      targetPort: 80

Dans un environnement avancé, la distribution du trafic peut être affinée via des outils de maillage de service comme Istio, Linkerd ou NGINX Ingress Controller, permettant un routage basé sur des pourcentages précis de requêtes.

Avantages :

  • Réduction drastique du risque en production
  • Détection rapide des anomalies sur un sous-ensemble d’utilisateurs
  • Possibilité de rollback immédiat sans impacter la majorité du trafic

Inconvénients :

  • Configuration plus complexe (surtout le routage du trafic
  • Gestion plus fine du monitoring et de l’observabilité nécessaire (métriques, logs, alertes)

Autres stratégies de déploiement

  • Blue-Green Deployment : deux environnements identiques coexistent (Blue = ancien, Green = nouveau), on redirige le trafic vers Green une fois les tests validés et le rollback instantané est possible
  • Recreate Strategy : supprime tous les anciens pods avant d’en créer de nouveaux, simple, mais provoque une indisponibilité temporaire

Gestion des déploiements en production avec Helm

Les fichiers YAML utilisés jusqu’ici (Deployment, Service, ConfigMap, etc.) fonctionnent très bien pour un projet unique. Mais dans un contexte réel, une application peut comporter des dizaines de manifests Kubernetes : front-end, back-end, base de données, secrets, règles d’accès, Ingress, etc.

C’est là qu’intervient Helm, le gestionnaire de paquets officiel de Kubernetes.

Qu’est-ce que Helm ?

Helm est souvent décrit comme le apt ou le yum de Kubernetes. Il permet de regrouper un ensemble de manifests Kubernetes dans une unité réutilisable appelée chart.

Un Helm chart contient :

  • Des fichiers YAML modèles (templates/)
  • Un fichier values.yaml centralisant les variables (version d’image, réplicas, ports, etc…)
  • Un fichier Chart.yaml décrivant le nom, la version et les dépendances.

Helm se charge ensuite de :

  • Rendre les fichiers dynamiques grâce à des variables et des templates Go
  • Installer, mettre à jour ou supprimer une application complète en une seule commande

Structure d’un chart Helm

Voici un exemple de structure d’un chart minimal :

Chart.yaml :
apiVersion: v2
name: ci-cd-demo
description: Chart Helm de démonstration CI/CD
version: 1.0.0
appVersion: "1.0"

values.yaml :
replicaCount: 3
image:
  repository: ghcr.io/jgak/ci-cd-demo
  tag: "latest
service:
  type: NodePort
  port: 80
  nodePort: 30080

Les fichiers dans templates/ utilisent des variables définies dans values.yaml :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: 80

Jusqu’à présent, nous avons réalisé le déploiement de notre application sur Kubernetes de manière classique, en appliquant manuellement des manifests YAML (deployment.yaml, service.yaml) grâce à la commande kubectl apply.

Cette approche fonctionne bien pour des environnements simples, mais dès que l’application se complexifie (plusieurs composants, environnements différents, variables de configuration, etc.), il devient difficile de maintenir et versionner proprement les fichiers.

C’est précisément là qu’intervient Helm, l’outil de packaging et de déploiement standard pour Kubernetes.

Nous allons transformer notre déploiement manuel en un chart Helm complet, déployé automatiquement dans notre cluster Minikube via le pipeline GitHub Actions.

Sur notre machine, le chart Helm sera appliqué directement dans le cluster Minikube local, à partir du runner self-hosted.

Le pipeline GitHub Actions se chargera d’actualiser automatiquement le déploiement à chaque git push sur la branche main.

Nous supposons qu’il est déjà installé et fonctionnel :

version.BuildInfo{Version:"v3.19.0", GitCommit:"3d8990f0836691f0229297773f3524598f46bda6", GitTreeState:"clean", GoVersion:"go1.24.7"}

Depuis le répertoire /opt/ci-cd-demo, générons un nouveau chart :

helm create ci-cd-demo-chart

Cette commande crée une arborescence complète. Le squelette créé par helm create suppose l'existence de nombreuses clés dans values.yaml.

Avant de déployer notre application via Helm, il est impératif de partir sur une base propre.

En effet, si une ancienne release Helm ou des ressources Kubernetes déjà existantes (Deployment, Service) subsistent, Helm refusera d’installer la nouvelle release avec une erreur du type :

Error: INSTALLATION FAILED: Unable to continue with install:
Service "ci-cd-demo" in namespace "default" exists and cannot be imported into the current release

Cette erreur provient du fait qu’Helm gère ses ressources avec des labels et annotations spécifiques (meta.helm.sh/release-name, app.kubernetes.io/managed-by, etc…).

Il ne peut donc pas « adopter » des objets créés manuellement avec kubectl apply.

Pour éviter ce problème, on procède en trois étapes de nettoyage :

  1. Si une release Helm du même nom (ci-cd-demo) existe déjà, on la désinstalle. Cette commande est silencieuse (2>/dev/null || true) si la release n’existe pas : helm uninstall ci-cd-demo 2>/dev/null || true
  2. Il se peut qu’un Deployment et un Service aient été créés précédemment à la main via kubectl apply. Helm ne peut pas les gérer sans conflit : on les supprime donc explicitement : kubectl delete deploy/ci-cd-demo svc/ci-cd-demo 2>/dev/null || true
  3. Avant de relancer une installation, on vérifie que plus aucun objet ne correspond à notre application :
    kubectl get deploy,svc | grep ci-cd-demo || echo "OK : rien à reprendre"
    Si la commande retourne « OK : rien à reprendre », cela signifie que le namespace default est propre et prêt pour une nouvelle installation.

Nous allons maintenant personnaliser ce chart pour qu’il déploie notre application ci-cd-demo.

Personnalisation du chart

Modifions le fichier Chart.yaml afin d'y insérer ce contenu :

apiVersion: v2
name: ci-cd-demo
description: Chart Helm pour le déploiement continu de l’application CI/CD Demo
version: 1.0.0
appVersion: "1.0"

Modifions le fichier values.yaml, ce fichier centralise les valeurs par défaut du déploiement :

replicaCount: 3

image:
  repository: ci-cd-demo
  tag: "1.0"
  pullPolicy: IfNotPresent

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  create: true
  automount: true
  annotations: {}
  name: ""

podAnnotations: {}
podLabels: {}

podSecurityContext: {}
securityContext: {}

service:
  type: NodePort
  port: 80
  nodePort: 30080

ingress:
  enabled: false
  className: ""
  annotations: {}
  hosts:
    - host: chart-example.local
      paths:
        - path: /
          pathType: Prefix
  tls: []

httpRoute:
  enabled: false
  parentRefs: []
  hostnames: []
  rules: []

resources: {}

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 3
  targetCPUUtilizationPercentage: 80
  targetMemoryUtilizationPercentage: 80

nodeSelector: {}
tolerations: []
affinity: {}

Adaptons le fichier templates/deployment.yaml :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}
spec:
  replicas: {{ .Values.replicaCount }}
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        ports:
        - containerPort: 80

Adaptons le fichier templates/service.yaml :

apiVersion: v1
kind: Service
metadata:
  name: {{ .Chart.Name }
spec:
  type: {{ .Values.service.type }}
  selector:
    app: {{ .Chart.Name }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: 80
      nodePort: {{ .Values.service.nodePort }}

Avant de déployer, assurons-nous que l’image est bien disponible dans le démon Docker interne de Minikube :

minikube image load ci-cd-demo:1.0

Puis installons le chart depuis son répertoire parent :

helm install ci-cd-demo ./ci-cd-demo-chart

Helm va créer le déploiement et le service.

Vérifions :

kubectl get all

Pour mettre à jour notre déploiement (par exemple après un nouveau build Docker) :

helm upgrade ci-cd-demo ./ci-cd-demo-chart --set image.tag=1.1

Intégration de Helm dans le pipeline GitHub Actions

Nous allons maintenant automatiser cette mise à jour Helm directement depuis le pipeline. Lorsque le job deploy s’exécutera sur le runner de notre machine, il utilisera Helm pour appliquer les modifications.

Version actualisée du fichier .github/workflows/ci-cd.yml :

name: ci-cd-github

on:
  push:
    branches: [ 'main' ]
  pull_request:
    branches: [ 'main' ]

permissions:
  contents: read
  packages: write

env:
  IMAGE_NAME: ci-cd-demo
  REGISTRY: ghcr.io/jgak
  GIT_SHA: ${{ github.sha }}

jobs:
  build_test_push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build image
        working-directory: ./app
        run: |
          docker build -t $REGISTRY/$IMAGE_NAME:${GIT_SHA} .

      - name: Smoke test
        run: |
          docker run -d -p 8080:80 --name ci-cd-test $REGISTRY/$IMAGE_NAME:${GIT_SHA}
          sleep 3
          curl -f http://localhost:8080 | grep -E 'Bienvenue|Version|Deployee|Deploy'
          docker rm -f ci-cd-test

      - name: Push image to GHCR
        if: github.ref == 'refs/heads/main'
        run: |
          docker push $REGISTRY/$IMAGE_NAME:${GIT_SHA}

  deploy:
    if: github.ref == 'refs/heads/main'
    needs: [ build_test_push ]
    runs-on: self-hosted
    steps:
      - name: Préparer le contexte Minikube
        run: |
          minikube update-context
          kubectl config use-context minikube
          kubectl get nodes

      - name: Déploiement Helm (upgrade/install)
        env:
          IMAGE_REPO: ghcr.io/jgak/ci-cd-demo
          IMAGE_TAG: ${{ github.sha }}
        run: |
          cd /opt/ci-cd-demo/ci-cd-demo-chart
          helm upgrade --install ci-cd-demo . \
            --set image.repository=${IMAGE_REPO} \
            --set image.tag=${IMAGE_TAG} \
            --set replicaCount=3 \
            --set service.type=NodePort \
            --set service.port=80 \
            --set service.nodePort=30080 \
            --set ingress.enabled=false \
            --set httpRoute.enabled=false
          helm history ci-cd-demo

      - name: Vérification du rollout
        run: |
          kubectl rollout status deployment/ci-cd-demo --timeout=180s
          kubectl get deploy,po,svc -o wide

Cet ensemble de commandes effectue notamment les actions suivantes :

  • Installe le chart Helm s’il n’existe pas encore, ou le met à jour s’il est déjà présent
  • Indique à Helm le dépôt d’images Docker à utiliser (ici ghcr.io/jgak/ci-cd-demo)
  • Définit le tag de l’image Docker à déployer.
  • Demande 3 réplicas du pod (rolling update en parallèle)
  • Expose le service en NodePort pour un accès depuis l’extérieur de Minikube
  • Définit le port interne du conteneur
  • Assigne le port d’accès externe à 30080

N’oublions pas de commit la modification et de push ce nouveau fichier sur le dépôt GitHub.

Validation du déploiement automatisé

Effectuons une modification du fichier app/index.html (pour visualiser facilement le passage sur la nouvelle version) :

<body style="background-color:#f0f8ff;text-align:center;padding:50px;font-family:sans-serif">
  <h1>CI/CD Demo – Version 2.0</h1>
  <h3>Déploiement réalisé le 1 octobre 2025</h3>
  <p>Cette version a été déployée automatiquement via GitHub Actions et Helm.</p>
</body>

Puis, on enchaine sur un commit et un push sur la branche main :

git add app/index.html 
git commit -m "Mise à jour interface web" 
git push

Sur GitHub, le workflow ci-cd.yml se déclenche automatiquement :

  • Le job build reconstruit l’image Docker
  • Le job deploy (exécuté par le runner self-hosted) met à jour Minikube via Helm

Ce qui donne en images :

Nous pouvons finalement accéder à la nouvelle version :


Nous voici arrivés au terme de ce chapitre, et d'une façon générale, au terme de ce cours complet dédié à la conteneurisation et à l'orchestration avec Docker, Kubernetes et un ensemble d'outils.

En partant des fondamentaux de Docker, vous avez appris à construire, sécuriser et partager vos applications sous forme d'images standardisées et optimisées. Vous avez ensuite fait vos premiers pas vers Kubernetes, maîtrisant l'art de déployer, scaler et exposer vos services au sein d'un cluster résilient. La gestion fine des ressources, le monitoring avancé avec Prometheus et Grafana, ainsi que la persistance des données sont des notions importantes évoquées dans ce cours.

Avec ce dernier module, vous avez bouclé la boucle en automatisant l'intégralité du cycle de vie logiciel grâce à un pipeline CI/CD piloté par Helm et associé à GitHub Actions. Désormais, à vous de jouer, de vous exercer, pour bâtir votre futur système à base de conteneurs !

author avatar
Jérémy GAK
Jérémy GAK est un ingénieur Linux doté de plus de 11 ans d'expérience dans le domaine. Sa carrière diversifiée s'étend de l'administration système et réseau à une expertise pointue en sécurité informatique. Consultant et ingénieur système de métier et actif dans la veille technologique, il est expert sur les technologies open source, l'automatisation des processus et la cybersécurité.
Partagez cet article Partager sur Twitter Partager sur Facebook Partager sur Linkedin Envoyer par mail

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.