Guide Technique — DevOps

Déployer en production
sur Kubernetes

De la conteneurisation Docker à un cluster K8s sécurisé, scalable et monitoré — un guide complet pour vos applications en production.

AuteurSOL-IT Solutions Informatiques
Version1.0 — 2026
NiveauIntermédiaire / Avancé
Durée estimée3–5 heures

Table des matières

  1. Prérequis et architecture cible Outils, cluster, namespace
  2. Conteneurisation avec Docker Dockerfile, optimisation, registry
  3. Déploiement Kubernetes Deployment, Service, Ingress
  4. Gestion des secrets et ConfigMaps Vault, Secrets K8s
  5. Scaling et haute disponibilité HPA, PodDisruptionBudget
  6. Monitoring avec Prometheus & Grafana Stack complète
  7. Pipeline CI/CD GitHub Actions → K8s
  8. Checklist de mise en production Points de contrôle finaux
Chapitre 1

Prérequis et architecture cible

Outils requis

Avant de commencer, assurez-vous d'avoir installé et configuré les outils suivants :

kubectl ≥ 1.28 Docker ≥ 24 Helm ≥ 3.12 k9s (optionnel) kubeconfig configuré

Architecture cible

Le schéma ci-dessous représente l'architecture que nous allons construire :

# Architecture cible

Internet
    │
    ▼
[ LoadBalancer / Ingress Controller (Nginx) ]
    │
    ├── /api    → Service: app-service → Pod(s): app-deployment
    ├── /       → Service: front-service → Pod(s): front-deployment
    │
[ PersistentVolumeClaim ] → PostgreSQL StatefulSet
[ ConfigMap + Secret ]    → variables d'environnement
[ HPA ]                   → scaling automatique des pods
[ Prometheus + Grafana ]  → monitoring

Initialisation du namespace

# Créer un namespace dédié à votre application
kubectl create namespace mon-app

# Définir ce namespace par défaut
kubectl config set-context --current --namespace=mon-app
Chapitre 2

Conteneurisation avec Docker

Dockerfile optimisé (multi-stage)

Utilisez un build multi-stage pour réduire la taille de l'image finale et éviter d'inclure les outils de build en production.

# ── Stage 1 : build ──────────────────────────────
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# ── Stage 2 : production ─────────────────────────
FROM node:20-alpine AS production
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
USER node
CMD ["node", "dist/main.js"]
💡 Bonne pratique : Toujours spécifier USER node pour ne pas exécuter le container en root.

Build et push vers le registry

# Build l'image
docker build -t registry.example.com/mon-app:v1.0.0 .

# Push vers votre registry (Docker Hub, GHCR, ECR...)
docker push registry.example.com/mon-app:v1.0.0

.dockerignore

node_modules
.git
.env
.env.*
*.log
dist
coverage
README.md
Chapitre 3

Déploiement Kubernetes

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mon-app
  namespace: mon-app
  labels:
    app: mon-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mon-app
  template:
    metadata:
      labels:
        app: mon-app
    spec:
      containers:
        - name: mon-app
          image: registry.example.com/mon-app:v1.0.0
          ports:
            - containerPort: 3000
          envFrom:
            - configMapRef:
                name: mon-app-config
            - secretRef:
                name: mon-app-secrets
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /ready
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10

Service et Ingress

--- Service ---
apiVersion: v1
kind: Service
metadata:
  name: mon-app-service
spec:
  selector:
    app: mon-app
  ports:
    - port: 80
      targetPort: 3000
---
--- Ingress (Nginx Controller) ---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mon-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
    - hosts: [app.example.com]
      secretName: mon-app-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: mon-app-service
                port:
                  number: 80

Appliquer les manifests

# Appliquer tous les fichiers YAML
kubectl apply -f k8s/

# Vérifier le déploiement
kubectl get pods -n mon-app
kubectl get ingress -n mon-app
kubectl describe deployment mon-app -n mon-app
Chapitre 4

Gestion des secrets et ConfigMaps

ConfigMap (variables non sensibles)

apiVersion: v1
kind: ConfigMap
metadata:
  name: mon-app-config
data:
  APP_ENV: "production"
  APP_PORT: "3000"
  DB_HOST: "postgres-service"
  REDIS_HOST: "redis-service"

Secret (variables sensibles)

⚠️ Ne jamais committer les Secrets en clair dans votre dépôt Git. Utilisez Sealed Secrets ou Vault.
# Créer un secret via kubectl (valeurs en base64 automatique)
kubectl create secret generic mon-app-secrets \
  --from-literal=DB_PASSWORD='monMotDePasse' \
  --from-literal=APP_KEY='base64:xxx...' \
  --from-literal=JWT_SECRET='monSecretJWT' \
  -n mon-app

# Vérifier
kubectl get secret mon-app-secrets -o yaml -n mon-app

Sealed Secrets (recommandé en production)

# Installation de Sealed Secrets Controller
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system

# Créer un SealedSecret (chiffré, safe to commit)
kubectl create secret generic mon-secret --dry-run=client \
  --from-literal=DB_PASSWORD='monMotDePasse' -o yaml | \
  kubeseal --format yaml > k8s/sealed-secret.yaml
Chapitre 5

Scaling et haute disponibilité

Horizontal Pod Autoscaler (HPA)

Le HPA ajuste automatiquement le nombre de pods en fonction de la charge CPU/mémoire.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: mon-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: mon-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

PodDisruptionBudget

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: mon-app-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: mon-app
💡 Le PDB garantit qu'au moins 2 pods restent disponibles lors d'une mise à jour ou maintenance du cluster.
Chapitre 6

Monitoring avec Prometheus & Grafana

Installation via Helm (kube-prometheus-stack)

# Ajouter le repo
helm repo add prometheus-community \
  https://prometheus-community.github.io/helm-charts
helm repo update

# Installer la stack complète (Prometheus + Grafana + AlertManager)
helm install kube-prometheus prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --set grafana.adminPassword='VotreMotDePasse' \
  --set prometheus.prometheusSpec.retention=15d

Accéder à Grafana

# Port-forward (développement)
kubectl port-forward svc/kube-prometheus-grafana 3000:80 -n monitoring

# Identifiants par défaut : admin / VotreMotDePasse
# Dashboard recommandé : ID 15661 (Kubernetes Cluster)

ServiceMonitor pour scraper votre application

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: mon-app-monitor
  labels:
    release: kube-prometheus
spec:
  selector:
    matchLabels:
      app: mon-app
  endpoints:
    - port: http
      path: /metrics
      interval: 30s
Chapitre 7

Pipeline CI/CD — GitHub Actions vers Kubernetes

Workflow complet (.github/workflows/deploy.yml)

name: Build & Deploy to Kubernetes

on:
  push:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure kubectl
        uses: azure/k8s-set-context@v3
        with:
          kubeconfig: ${{ secrets.KUBECONFIG }}

      - name: Update image tag & apply
        run: |
          sed -i "s|IMAGE_TAG|${{ github.sha }}|g" k8s/deployment.yaml
          kubectl apply -f k8s/ -n mon-app
          kubectl rollout status deployment/mon-app -n mon-app
💡 Stockez votre KUBECONFIG comme secret GitHub chiffré. Ne le committez jamais.
Chapitre 8

Checklist de mise en production

Avant de déployer

  • Image Docker construite en multi-stage, tag spécifique (pas :latest)
  • Variables d'environnement dans ConfigMap/Secret — jamais en dur dans l'image
  • requests et limits définis sur tous les containers
  • livenessProbe et readinessProbe configurés
  • Secrets gérés via Sealed Secrets ou Vault
  • TLS activé via cert-manager (Let's Encrypt)
  • HPA configuré avec min ≥ 2 replicas
  • PodDisruptionBudget créé
  • Monitoring Prometheus + alertes configurées
  • Rollback testé : kubectl rollout undo deployment/mon-app

Commandes de vérification post-déploiement

# État des pods
kubectl get pods -n mon-app -w

# Logs en temps réel
kubectl logs -f deployment/mon-app -n mon-app

# Événements du cluster
kubectl get events -n mon-app --sort-by='.lastTimestamp'

# Rollback si problème
kubectl rollout undo deployment/mon-app -n mon-app

# Vérifier le HPA
kubectl get hpa -n mon-app