Skip to content

Latest commit

 

History

History
2526 lines (2025 loc) · 68.4 KB

File metadata and controls

2526 lines (2025 loc) · 68.4 KB

🔝 Retour au Sommaire

19.1.4. Kubernetes (StatefulSets, Operators)

Introduction à Kubernetes

Qu'est-ce que Kubernetes ?

Kubernetes (souvent abrégé K8s) est une plateforme open source d'orchestration de conteneurs créée par Google en 2014. C'est devenu le standard de facto pour gérer des applications conteneurisées à grande échelle.

Analogie simple :

Imaginez un chef d'orchestre (Kubernetes) dirigeant un orchestre (vos applications) :

  • Le chef d'orchestre ne joue pas lui-même, il coordonne les musiciens
  • Il s'assure que chaque musicien (conteneur) joue au bon moment
  • Si un musicien est absent, il le remplace automatiquement
  • Il ajuste le volume (ressources) selon les besoins
  • Il maintient l'harmonie (état désiré) de l'ensemble

En termes techniques :

Kubernetes = Système qui automatise :

  • Le déploiement d'applications conteneurisées
  • La mise à l'échelle (scaling) automatique
  • La gestion du cycle de vie
  • La réparation automatique (self-healing)
  • L'équilibrage de charge (load balancing)
  • Le stockage persistant

Pourquoi "K8s" ?

K8s = Kubernetes

  • K = première lettre
  • 8 = nombre de lettres entre K et s (ubernete)
  • s = dernière lettre

C'est un numeronym, comme i18n (internationalization) ou l10n (localization).

Vocabulaire de Base Kubernetes

Terme Définition Analogie
Cluster Ensemble de machines (nodes) gérées par K8s Un datacenter complet
Node Machine (VM ou physique) dans le cluster Un serveur individuel
Pod Plus petite unité déployable (1+ conteneurs) Une boîte contenant applications
Deployment Gère des pods sans état (stateless) Recette pour apps web
StatefulSet Gère des pods avec état (stateful) Recette pour bases de données
Service Point d'accès réseau stable aux pods Adresse postale fixe
PersistentVolume (PV) Espace de stockage dans le cluster Disque dur physique
PersistentVolumeClaim (PVC) Demande de stockage par un pod Réservation d'espace disque
Namespace Isolation logique de ressources Appartement dans immeuble
Operator Extension K8s avec logique métier Administrateur DBA automatisé

Architecture Kubernetes : Vue d'Ensemble

┌────────────────────────────────────────────────────────────┐
│                    KUBERNETES CLUSTER                      │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  ┌────────────────────────────────────────────────────┐    │
│  │             CONTROL PLANE (Master)                 │    │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐          │    │
│  │  │ API      │  │Scheduler │  │Controller│          │    │
│  │  │ Server   │  │          │  │ Manager  │          │    │
│  │  └──────────┘  └──────────┘  └──────────┘          │    │
│  │  ┌──────────┐                                      │    │
│  │  │  etcd    │  ← État du cluster                   │    │
│  │  └──────────┘                                      │    │
│  └────────────────────────────────────────────────────┘    │
│                                                            │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │   Worker 1   │  │   Worker 2   │  │   Worker 3   │      │
│  │              │  │              │  │              │      │
│  │ ┌──────────┐ │  │ ┌──────────┐ │  │ ┌──────────┐ │      │
│  │ │  Pod     │ │  │ │  Pod     │ │  │ │  Pod     │ │      │
│  │ │ [PG-0]   │ │  │ │ [PG-1]   │ │  │ │ [PG-2]   │ │      │
│  │ └──────────┘ │  │ └──────────┘ │  │ └──────────┘ │      │
│  │ ┌──────────┐ │  │ ┌──────────┐ │  │ ┌──────────┐ │      │
│  │ │  Kubelet │ │  │ │  Kubelet │ │  │ │  Kubelet │ │      │
│  │ └──────────┘ │  │ └──────────┘ │  │ └──────────┘ │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                            │
└────────────────────────────────────────────────────────────┘

Composants :

Control Plane (Cerveau) :

  • API Server : Point d'entrée pour toutes commandes K8s
  • Scheduler : Décide où placer les pods
  • Controller Manager : Maintient l'état désiré
  • etcd : Base de données distribuée (état du cluster)

Worker Nodes (Muscles) :

  • Kubelet : Agent qui exécute les pods
  • Container Runtime : Docker, containerd, CRI-O
  • kube-proxy : Gère le réseau

PostgreSQL sur Kubernetes : Défis et Solutions

Pourquoi PostgreSQL sur K8s est Complexe ?

Les bases de données sont stateful (avec état), ce qui est contraire à la philosophie initiale de Kubernetes (conçu pour stateless).

Défis spécifiques :

Défi 1 : Persistance des Données
├─ Problème : Pods éphémères (peuvent être détruits/recréés)
└─ Solution : PersistentVolumes (PV/PVC)

Défi 2 : Identité Stable
├─ Problème : Pods ont noms aléatoires (web-7f8b9c-x2k4l)
└─ Solution : StatefulSets (noms prédictibles : postgres-0, postgres-1)

Défi 3 : Ordre de Démarrage
├─ Problème : Primary doit démarrer avant Standbys
└─ Solution : StatefulSets (démarrage séquentiel)

Défi 4 : Réseau Stable
├─ Problème : IP des pods change à chaque redémarrage
└─ Solution : Headless Services (DNS stable)

Défi 5 : Réplication et HA
├─ Problème : Gestion complexe Primary/Standby, failover
└─ Solution : Operators (Zalando, CloudNativePG)

Défi 6 : Backups et Restauration
├─ Problème : Pas de mécanisme natif K8s
└─ Solution : Operators avec backup intégré

Défi 7 : Mises à Jour
├─ Problème : Upgrade PostgreSQL sans perte données
└─ Solution : Operators avec gestion versions

Quand Utiliser PostgreSQL sur Kubernetes ?

✅ Cas d'Usage Idéaux :

  1. Applications Cloud-Native

    • Architecture microservices
    • Déploiements fréquents
    • Infrastructure as Code (GitOps)
  2. Multi-Environnements

    • Dev, Staging, Prod sur même cluster
    • Isolation par namespaces
    • Utilisation optimisée ressources
  3. Scaling Dynamique

    • Charge variable
    • Auto-scaling horizontal (read replicas)
    • Utilisation efficace du cluster
  4. Multi-Cloud / Hybrid Cloud

    • Portabilité entre clouds (AWS, GCP, Azure)
    • Infrastructure unifiée
    • Éviter vendor lock-in
  5. Équipe DevOps Mature

    • Expertise Kubernetes
    • Automatisation poussée
    • Culture GitOps

❌ Cas où Éviter K8s :

  1. Équipe sans Expertise K8s

    • Courbe d'apprentissage très raide
    • Complexité opérationnelle élevée
  2. Base de Données Unique Critique

    • VM ou bare metal plus simple
    • Moins de couches d'abstraction
    • Troubleshooting plus direct
  3. Performance Absolue Requise

    • Latence sub-milliseconde
    • Overhead K8s inacceptable
    • Bare metal préférable
  4. Réglementations Strictes

    • Certifications spécifiques
    • Audits complexes avec K8s

StatefulSets : Gérer PostgreSQL avec État

Qu'est-ce qu'un StatefulSet ?

Un StatefulSet est un contrôleur Kubernetes conçu pour gérer des applications avec état (stateful) comme les bases de données.

Différence avec Deployment :

Deployment (Stateless - Apps Web) :
├─ Pods interchangeables
├─ Noms aléatoires (web-7f8b9c-x2k4l)
├─ IP changeante
├─ Pas d'ordre démarrage
└─ Exemple : Frontend, API

StatefulSet (Stateful - Bases de Données) :
├─ Pods avec identité unique
├─ Noms ordonnés (postgres-0, postgres-1, postgres-2)
├─ DNS stable (postgres-0.postgres-headless.default.svc.cluster.local)
├─ Démarrage/arrêt séquentiel
├─ PersistentVolume par pod
└─ Exemple : PostgreSQL, MongoDB, Kafka

Caractéristiques d'un StatefulSet

1. Identité Stable :

# StatefulSet avec 3 réplicas
apiVersion: apps/v1  
kind: StatefulSet  
metadata:  
  name: postgres
spec:
  replicas: 3
  serviceName: postgres-headless  # Service headless requis

# Crée :
# ├─ postgres-0  (Primary)
# ├─ postgres-1  (Standby)
# └─ postgres-2  (Standby)

# Noms toujours identiques après redémarrage

2. Démarrage Séquentiel :

Démarrage :
1. postgres-0 démarre et devient Ready
2. postgres-1 démarre (attend postgres-0 Ready)
3. postgres-2 démarre (attend postgres-1 Ready)

Arrêt (ordre inverse) :
1. postgres-2 s'arrête
2. postgres-1 s'arrête
3. postgres-0 s'arrête

3. Stockage Persistant Dédié :

Chaque pod a son PersistentVolume :
├─ postgres-0 → pvc-postgres-0 (100 GB)
├─ postgres-1 → pvc-postgres-1 (100 GB)
└─ postgres-2 → pvc-postgres-2 (100 GB)

Survit au redémarrage des pods !

4. DNS Prévisible :

DNS Pattern :
<pod-name>.<service-name>.<namespace>.svc.cluster.local

Exemples :
├─ postgres-0.postgres-headless.default.svc.cluster.local
├─ postgres-1.postgres-headless.default.svc.cluster.local
└─ postgres-2.postgres-headless.default.svc.cluster.local

Applications peuvent se connecter de manière stable

Exemple StatefulSet PostgreSQL Basique

Attention : Ceci est un exemple SIMPLE sans HA. Pour production, utiliser un Operator (voir plus loin).

# postgres-statefulset.yaml
apiVersion: v1  
kind: Service  
metadata:  
  name: postgres-headless
  labels:
    app: postgres
spec:
  ports:
  - port: 5432
    name: postgres
  clusterIP: None  # Headless service
  selector:
    app: postgres

---
apiVersion: apps/v1  
kind: StatefulSet  
metadata:  
  name: postgres
spec:
  serviceName: postgres-headless
  replicas: 1  # 1 seul pod (pas de HA dans cet exemple)
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:18
        ports:
        - containerPort: 5432
          name: postgres
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            memory: "2Gi"
            cpu: "1"
          limits:
            memory: "4Gi"
            cpu: "2"
        livenessProbe:
          exec:
            command:
            - /bin/sh
            - -c
            - pg_isready -U postgres
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          exec:
            command:
            - /bin/sh
            - -c
            - pg_isready -U postgres
          initialDelaySeconds: 5
          periodSeconds: 5
  volumeClaimTemplates:  # Template PVC pour chaque pod
  - metadata:
      name: postgres-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: fast-ssd  # Adapter à votre cluster
      resources:
        requests:
          storage: 100Gi

---
apiVersion: v1  
kind: Secret  
metadata:  
  name: postgres-secret
type: Opaque  
data:  
  password: U3VwZXJTZWNyZXQxMjM=  # Base64 de "SuperSecret123"

Déploiement :

# Créer le StatefulSet
kubectl apply -f postgres-statefulset.yaml

# Vérifier
kubectl get statefulset  
kubectl get pods  
kubectl get pvc  

# Se connecter
kubectl exec -it postgres-0 -- psql -U postgres

Limitations de cet exemple :

  • ⚠️ Pas de haute disponibilité (1 seul pod)
  • ⚠️ Pas de réplication automatique
  • ⚠️ Pas de failover automatique
  • ⚠️ Pas de backups automatisés
  • ⚠️ Gestion manuelle complexe

Solution : Utiliser un Operator !


Operators : Automatisation de PostgreSQL sur K8s

Qu'est-ce qu'un Operator ?

Un Operator est une extension Kubernetes qui encode la connaissance opérationnelle d'un humain expert (DBA) dans un logiciel.

Analogie :

Sans Operator :  
Vous = DBA qui doit :  
├─ Configurer réplication manuellement
├─ Surveiller Primary/Standby
├─ Détecter pannes
├─ Faire failover manuellement
├─ Lancer backups à la main
└─ Gérer mises à jour

Avec Operator :  
Operator = DBA Robot qui :  
├─ Configure réplication automatiquement
├─ Surveille 24/7
├─ Détecte pannes instantanément
├─ Fait failover en 10-30 secondes
├─ Lance backups planifiés
└─ Gère mises à jour sans perte données

Techniquement :

Un Operator = Custom Resource Definition (CRD) + Controller

CRD (Custom Resource Definition) :
└─ Étend l'API Kubernetes avec nouveaux types de ressources
   Exemple : PostgreSQL, PostgresCluster, Backup

Controller :
└─ Logique métier qui surveille CRDs et maintient état désiré
   Exemple : Si Primary down → Promouvoir Standby

Pattern Operator

┌───────────────────────────────────────────────────────┐
│                    Utilisateur                        │
└───────────────────┬───────────────────────────────────┘
                    │
                    │ kubectl apply -f postgres-cluster.yaml
                    ↓
┌───────────────────────────────────────────────────────┐
│              Kubernetes API Server                    │
└───────────────────┬───────────────────────────────────┘
                    │
                    │ Enregistre Custom Resource
                    ↓
┌───────────────────────────────────────────────────────┐
│           Operator Controller (Pod)                   │
│  ┌─────────────────────────────────────────────────┐  │
│  │  Watch : PostgresCluster CRD                    │  │
│  │  Reconcile Loop :                               │  │
│  │  1. Lire état actuel                            │  │
│  │  2. Comparer avec état désiré                   │  │
│  │  3. Actions pour atteindre état désiré          │  │
│  │     - Créer StatefulSet                         │  │
│  │     - Configurer réplication                    │  │
│  │     - Faire backups                             │  │
│  │     - Gérer failover                            │  │
│  └─────────────────────────────────────────────────┘  │
└───────────────────┬───────────────────────────────────┘
                    │
                    │ Crée et gère
                    ↓
┌──────────────────────────────────────────────────────┐
│         Resources Kubernetes (Pods, Services, etc.)  │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐            │
│  │ PG-0     │  │ PG-1     │  │ PG-2     │            │
│  │ Primary  │  │ Standby  │  │ Standby  │            │
│  └──────────┘  └──────────┘  └──────────┘            │
└──────────────────────────────────────────────────────┘

Operators PostgreSQL Populaires

Operator Créateur Maturité Complexité Recommandation
CloudNativePG EDB ⭐⭐⭐⭐⭐ Moyenne Recommandé Production
Zalando Zalando (Allemagne) ⭐⭐⭐⭐⭐ Élevée ✅ Excellent (mature)
Crunchy Crunchy Data ⭐⭐⭐⭐⭐ Moyenne-Élevée ✅ Enterprise
Stolon Sorint.lab ⭐⭐⭐ Moyenne ⚠️ Moins actif
KubeDB AppsCode ⭐⭐⭐⭐ Faible-Moyenne ✅ Multi-DB

Notre focus :

  1. CloudNativePG (recommandé débuter)
  2. Zalando Postgres Operator (très mature)

CloudNativePG : L'Operator Moderne

Présentation CloudNativePG

CloudNativePG (anciennement Cloud Native PostgreSQL) est développé par EnterpriseDB (EDB), leader PostgreSQL.

Points Forts :

  • Moderne : Conçu pour Kubernetes natif
  • Simple : API claire et intuitive
  • Complet : HA, Backups, Monitoring
  • Performant : Réplication streaming native
  • Open Source : Apache 2.0
  • Bien documenté : Documentation excellente
  • Actif : Développement très actif (2024-2025)

Fonctionnalités :

CloudNativePG offre :
├─ Déploiement PostgreSQL automatique (1 commande)
├─ Haute disponibilité (Primary + Standbys)
├─ Auto-failover (10-30 secondes)
├─ Réplication synchrone ou asynchrone
├─ Backups automatisés (pgBackRest intégré)
├─ Point-in-Time Recovery (PITR)
├─ Connection pooling (PgBouncer intégré)
├─ Monitoring (métriques Prometheus)
├─ TLS automatique
├─ Rolling updates sans downtime
└─ Support PostgreSQL 12 à 18

Installation CloudNativePG

Prérequis :

  • Cluster Kubernetes 1.25+
  • kubectl configuré
  • Helm 3+ (optionnel, mais recommandé)

Installation via Manifest :

# Installer l'operator (une seule fois par cluster)
kubectl apply -f \
  https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.22/releases/cnpg-1.22.0.yaml

# Vérifier installation
kubectl get deployment -n cnpg-system cnpg-controller-manager

# Logs operator
kubectl logs -n cnpg-system \
  deployment/cnpg-controller-manager -f

Installation via Helm :

# Ajouter repo Helm
helm repo add cnpg https://cloudnative-pg.github.io/charts  
helm repo update  

# Installer
helm install cnpg \
  --namespace cnpg-system \
  --create-namespace \
  cnpg/cloudnative-pg

# Vérifier
helm list -n cnpg-system  
kubectl get pods -n cnpg-system  

Créer un Cluster PostgreSQL Simple

cluster-simple.yaml :

apiVersion: postgresql.cnpg.io/v1  
kind: Cluster  
metadata:  
  name: postgres-cluster
  namespace: default
spec:
  # Nombre d'instances (1 Primary + N Standbys)
  instances: 3

  # Image PostgreSQL
  imageName: ghcr.io/cloudnative-pg/postgresql:18

  # Stockage
  storage:
    storageClass: fast-ssd  # Adapter à votre cluster
    size: 100Gi

  # Bootstrap (initialisation)
  bootstrap:
    initdb:
      database: app
      owner: app
      secret:
        name: app-secret

  # PostgreSQL Configuration
  postgresql:
    parameters:
      shared_buffers: "2GB"
      max_connections: "200"
      work_mem: "64MB"
      maintenance_work_mem: "1GB"
      # PostgreSQL 18 spécifique
      wal_compression: "zstd"
      io_method: "worker"

  # Ressources
  resources:
    requests:
      memory: "4Gi"
      cpu: "2"
    limits:
      memory: "8Gi"
      cpu: "4"

  # Monitoring
  monitoring:
    enablePodMonitor: true

Secret pour base de données :

# app-secret.yaml
apiVersion: v1  
kind: Secret  
metadata:  
  name: app-secret
  namespace: default
type: kubernetes.io/basic-auth  
data:  
  username: YXBw        # base64("app")
  password: U2VjcmV0MTIz  # base64("Secret123")

Déploiement :

# Créer secret
kubectl apply -f app-secret.yaml

# Créer cluster PostgreSQL
kubectl apply -f cluster-simple.yaml

# Suivre déploiement
kubectl get cluster -w

# Détails cluster
kubectl describe cluster postgres-cluster

# Pods créés
kubectl get pods -l cnpg.io/cluster=postgres-cluster

# Exemple de sortie :
# NAME                  READY   STATUS    RESTARTS   AGE
# postgres-cluster-1    1/1     Running   0          2m  ← Primary
# postgres-cluster-2    1/1     Running   0          1m  ← Standby
# postgres-cluster-3    1/1     Running   0          30s ← Standby

Ce qui a été créé automatiquement :

CloudNativePG a créé :
├─ StatefulSet (3 pods : postgres-cluster-1, -2, -3)
├─ Services :
│   ├─ postgres-cluster-rw  (Read/Write → Primary)
│   ├─ postgres-cluster-ro  (Read-Only → Standbys)
│   └─ postgres-cluster-r   (Read → Tous)
├─ PersistentVolumeClaims (1 par pod)
├─ Secrets (certificats TLS, mots de passe)
├─ ConfigMaps (configuration PostgreSQL)
└─ Réplication streaming configurée automatiquement

Se Connecter au Cluster

Depuis un autre pod dans K8s :

# Service Read/Write (Primary)
psql postgresql://app:Secret123@postgres-cluster-rw:5432/app

# Service Read-Only (Standbys)
psql postgresql://app:Secret123@postgres-cluster-ro:5432/app

Depuis l'extérieur (kubectl port-forward) :

# Forward port local → Service K8s
kubectl port-forward \
  service/postgres-cluster-rw 5432:5432

# Dans autre terminal
psql postgresql://app:Secret123@localhost:5432/app

Via LoadBalancer (si cloud provider) :

# loadbalancer-service.yaml
apiVersion: v1  
kind: Service  
metadata:  
  name: postgres-external
spec:
  type: LoadBalancer
  selector:
    cnpg.io/cluster: postgres-cluster
    role: primary
  ports:
  - port: 5432
    targetPort: 5432

Backups Automatisés

Configuration backup S3-compatible :

apiVersion: postgresql.cnpg.io/v1  
kind: Cluster  
metadata:  
  name: postgres-cluster
spec:
  instances: 3
  imageName: ghcr.io/cloudnative-pg/postgresql:18

  storage:
    size: 100Gi

  # Configuration Backup
  backup:
    # Rétention backups
    retentionPolicy: "30d"

    # Backup vers S3 (ou compatible : MinIO, etc.)
    barmanObjectStore:
      destinationPath: s3://my-bucket/postgres-backups/
      endpointURL: https://s3.amazonaws.com  # ou MinIO URL

      # Credentials S3
      s3Credentials:
        accessKeyId:
          name: s3-credentials
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: s3-credentials
          key: SECRET_ACCESS_KEY

      # Compression
      wal:
        compression: gzip

      # Encryption
      data:
        encryption: AES256

  # Schedule backups (CronJob)
  backup:
    volumeSnapshot:
      online: true
    target: primary
    method: barmanObjectStore

Créer backup manuel :

# backup-now.yaml
apiVersion: postgresql.cnpg.io/v1  
kind: Backup  
metadata:  
  name: backup-manual-20250101
spec:
  cluster:
    name: postgres-cluster
  method: barmanObjectStore
# Lancer backup
kubectl apply -f backup-now.yaml

# Lister backups
kubectl get backups

# Détails backup
kubectl describe backup backup-manual-20250101

Scheduled Backups (automatique) :

apiVersion: postgresql.cnpg.io/v1  
kind: ScheduledBackup  
metadata:  
  name: postgres-daily-backup
spec:
  # Cron schedule (tous les jours à 2h)
  schedule: "0 2 * * *"

  # Politique suspension (si maintenance)
  suspend: false

  # Cluster cible
  cluster:
    name: postgres-cluster

  # Méthode backup
  method: barmanObjectStore

Restauration depuis Backup (PITR) :

# cluster-restore.yaml
apiVersion: postgresql.cnpg.io/v1  
kind: Cluster  
metadata:  
  name: postgres-restored
spec:
  instances: 3
  imageName: ghcr.io/cloudnative-pg/postgresql:18

  # Bootstrap depuis backup
  bootstrap:
    recovery:
      source: postgres-cluster  # Cluster source

      # Point-in-Time (optionnel)
      recoveryTarget:
        targetTime: "2025-01-01 14:30:00.00000+00"

      # Ou backup spécifique
      backup:
        name: backup-manual-20250101

  # Référence au backup source
  externalClusters:
  - name: postgres-cluster
    barmanObjectStore:
      destinationPath: s3://my-bucket/postgres-backups/
      s3Credentials:
        accessKeyId:
          name: s3-credentials
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: s3-credentials
          key: SECRET_ACCESS_KEY

Haute Disponibilité et Failover

Fonctionnement du Failover :

État Normal :
┌────────────────┐    Replication    ┌────────────────┐
│  postgres-1    │   Streaming       │  postgres-2    │
│  (Primary)     │ ═════════════>    │  (Standby)     │
│  Read/Write    │                   │  Read-Only     │
└────────────────┘                   └────────────────┘
        ↓
    Service RW
    (postgres-cluster-rw)

Panne Primary :
┌────────────────┐                   ┌────────────────┐
│  postgres-1    │                   │  postgres-2    │
│  (DOWN) ❌     │                   │  (Standby)     │
└────────────────┘                   └────────────────┘
                                             │
CloudNativePG détecte panne (10-15s)        │
                                             ↓
                                     Promotion automatique

Après Failover (10-30s) :
┌────────────────┐                   ┌────────────────┐
│  postgres-1    │   Replication     │  postgres-2    │
│  (Standby)     │ <═════════════    │  (Primary) ✅  │
│  Read-Only     │                   │  Read/Write    │
└────────────────┘                   └────────────────┘
                                             ↑
                                         Service RW
                                    (bascule automatiquement)

Configuration Réplication Synchrone :

apiVersion: postgresql.cnpg.io/v1  
kind: Cluster  
metadata:  
  name: postgres-cluster
spec:
  instances: 3

  # Réplication synchrone
  postgresql:
    synchronous:
      # Nombre de standbys synchrones
      number: 1

      # Méthode : 'any' ou 'first'
      method: any

  # Quorum : au moins 1 standby sync doit confirmer
  minSyncReplicas: 1
  maxSyncReplicas: 2

Explication :

  • Synchrone : Transaction committée seulement après réplication sur standby(s)
  • Avantage : Zéro perte de données (RPO = 0)
  • Inconvénient : Latence légèrement supérieure

Test de Failover Manuel :

# Identifier le Primary
kubectl get cluster postgres-cluster -o jsonpath='{.status.currentPrimary}'
# Output : postgres-cluster-1

# Simuler panne : supprimer le pod Primary
kubectl delete pod postgres-cluster-1

# Observer failover automatique (10-30s)
kubectl get cluster postgres-cluster -w

# Vérifier nouveau Primary
kubectl get cluster postgres-cluster -o jsonpath='{.status.currentPrimary}'
# Output : postgres-cluster-2 (ou 3)

# L'ancien Primary redémarre automatiquement en Standby

Monitoring et Observabilité

CloudNativePG expose métriques Prometheus :

apiVersion: postgresql.cnpg.io/v1  
kind: Cluster  
metadata:  
  name: postgres-cluster
spec:
  instances: 3

  # Activer monitoring
  monitoring:
    enablePodMonitor: true

    # Custom queries
    customQueries:
    - name: "active_connections"
      query: |
        SELECT count(*) as active_connections
        FROM pg_stat_activity
        WHERE state = 'active'
      metrics:
      - active_connections:
          usage: "GAUGE"
          description: "Number of active connections"

Métriques disponibles :

Métriques exposées (port 9187) :
├─ cnpg_pg_replication_lag_bytes
├─ cnpg_pg_wal_archive_status
├─ cnpg_pg_stat_database_*
├─ cnpg_pg_stat_replication_*
├─ cnpg_backends_waiting_total
├─ cnpg_pg_postmaster_start_time
└─ Toutes métriques pg_stat_* standard

Dashboard Grafana (pré-configuré) :

# Importer dashboard CloudNativePG officiel
# Grafana ID : 17068
# URL : https://grafana.com/grafana/dashboards/17068

Alerting (Prometheus) :

# prometheus-rules.yaml
apiVersion: monitoring.coreos.com/v1  
kind: PrometheusRule  
metadata:  
  name: postgres-alerts
spec:
  groups:
  - name: postgresql
    interval: 30s
    rules:
    - alert: PostgreSQLDown
      expr: up{job="postgres-cluster"} == 0
      for: 1m
      labels:
        severity: critical
      annotations:
        summary: "PostgreSQL instance down"

    - alert: PostgreSQLReplicationLag
      expr: cnpg_pg_replication_lag_bytes > 100000000  # 100 MB
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "Replication lag > 100 MB"

    - alert: PostgreSQLTooManyConnections
      expr: |
        cnpg_backends_total /
        cnpg_pg_settings_max_connections > 0.8
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "PostgreSQL connections > 80%"

Zalando Postgres Operator

Présentation Zalando Operator

Zalando Postgres Operator est l'un des operators PostgreSQL les plus matures, créé par Zalando (e-commerce allemand) en 2016.

Points Forts :

  • Très mature : 8+ ans de production
  • Battle-tested : Utilisé par Zalando à grande échelle
  • Complet : HA, Pooling, Monitoring
  • Patroni intégré : Gestion HA robuste
  • UI Web : Interface graphique (optionnelle)
  • Flexible : Nombreuses options configuration

Différences avec CloudNativePG :

Critère CloudNativePG Zalando
Simplicité ⭐⭐⭐⭐⭐ Plus simple ⭐⭐⭐⭐ Plus complexe
Maturité ⭐⭐⭐⭐ (récent mais actif) ⭐⭐⭐⭐⭐ (très mature)
HA Backend Réplication native Patroni
Backups Intégré (pgBackRest) WAL-E/WAL-G (externe)
Connection Pooling PgBouncer intégré PgBouncer intégré
UI CLI seulement UI Web disponible
Courbe apprentissage Faible Moyenne

Recommandation :

  • Débutants / Simplicité : CloudNativePG
  • Production à grande échelle / Patroni déjà utilisé : Zalando

Installation Zalando Operator

# Cloner repo
git clone https://github.com/zalando/postgres-operator.git  
cd postgres-operator  

# Installer via Helm
helm install postgres-operator \
  ./charts/postgres-operator \
  --namespace postgres-operator \
  --create-namespace

# Vérifier
kubectl get pods -n postgres-operator

Ou via manifest :

# Installer CRDs
kubectl apply -f manifests/configmap.yaml  
kubectl apply -f manifests/operator-service-account-rbac.yaml  
kubectl apply -f manifests/postgres-operator.yaml  

# Vérifier
kubectl get deployment -n postgres-operator

Créer un Cluster PostgreSQL avec Zalando

minimal-postgres-manifest.yaml :

apiVersion: "acid.zalan.do/v1"  
kind: postgresql  
metadata:  
  name: acid-minimal-cluster
  namespace: default
spec:
  # Version PostgreSQL
  dockerImage: ghcr.io/zalando/spilo-18:3.2-p1

  # Team ID (namespace logique)
  teamId: "myteam"

  # Nombre d'instances
  numberOfInstances: 3

  # Utilisateurs
  users:
    app_user:  # Base de données créée automatiquement
    - superuser
    - createdb

  # Bases de données
  databases:
    app_db: app_user  # db: owner

  # Volumes
  volume:
    size: 100Gi
    storageClass: fast-ssd

  # Ressources
  resources:
    requests:
      cpu: "2"
      memory: 4Gi
    limits:
      cpu: "4"
      memory: 8Gi

  # PostgreSQL Configuration
  postgresql:
    version: "18"
    parameters:
      shared_buffers: "2GB"
      max_connections: "200"
      work_mem: "64MB"
      # PostgreSQL 18
      wal_compression: "zstd"

  # Connection Pooler (PgBouncer)
  enableConnectionPooler: true
  connectionPooler:
    numberOfInstances: 2
    mode: "transaction"
    schema: "pooler"
    user: "pooler"
    resources:
      requests:
        cpu: "500m"
        memory: "256Mi"
      limits:
        cpu: "1"
        memory: "512Mi"

Déploiement :

# Créer cluster
kubectl apply -f minimal-postgres-manifest.yaml

# Suivre création
kubectl get postgresql acid-minimal-cluster -w

# Pods créés
kubectl get pods -l cluster-name=acid-minimal-cluster

# Exemple sortie :
# NAME                           READY   STATUS    AGE
# acid-minimal-cluster-0         1/1     Running   2m  ← Primary
# acid-minimal-cluster-1         1/1     Running   1m  ← Standby
# acid-minimal-cluster-2         1/1     Running   30s ← Standby
# acid-minimal-cluster-pooler-0  1/1     Running   1m  ← PgBouncer
# acid-minimal-cluster-pooler-1  1/1     Running   1m  ← PgBouncer

Services créés :

kubectl get svc -l cluster-name=acid-minimal-cluster

# Output :
# NAME                          TYPE        CLUSTER-IP      PORT(S)
# acid-minimal-cluster          ClusterIP   10.96.100.50    5432  ← Primary
# acid-minimal-cluster-repl     ClusterIP   10.96.100.51    5432  ← Replicas
# acid-minimal-cluster-config   ClusterIP   None            ...   ← Headless
# acid-minimal-cluster-pooler   ClusterIP   10.96.100.52    5432  ← PgBouncer

Fonctionnalités Avancées Zalando

1. Logical Backups (Backups Logiques)

apiVersion: "acid.zalan.do/v1"  
kind: postgresql  
metadata:  
  name: acid-cluster
spec:
  # ... autres configs ...

  # Backups logiques automatiques
  enableLogicalBackup: true
  logicalBackupSchedule: "30 2 * * *"  # 2h30 tous les jours

Stockage backups (ConfigMap operator) :

# Dans ConfigMap postgres-operator
logical_backup_s3_bucket: "my-postgres-backups"  
logical_backup_s3_region: "eu-west-1"  
logical_backup_s3_endpoint: ""  
logical_backup_s3_sse: "AES256"  

2. Custom Users et Bases

spec:
  users:
    # Utilisateur avec privilèges spécifiques
    app_readonly:
      - login
      - inherit

    app_admin:
      - superuser
      - createdb
      - createrole

  databases:
    production: app_admin
    staging: app_admin
    analytics: app_readonly

  # Permissions custom après création
  preparedDatabases:
    production:
      defaultUsers: true
      extensions:
        pg_stat_statements: public
        pgcrypto: public
      schemas:
        app: app_admin
        audit: app_admin

3. Connection Pooling Avancé

spec:
  # PgBouncer configuration
  enableConnectionPooler: true
  connectionPooler:
    numberOfInstances: 3  # HA pooler

    # Mode pooling
    mode: "transaction"  # ou "session" ou "statement"

    # Ressources
    resources:
      requests:
        cpu: "1"
        memory: "512Mi"
      limits:
        cpu: "2"
        memory: "1Gi"

    # Configuration PgBouncer custom
    maxDBConnections: 60  # Max connexions par DB
    defaultPoolSize: 25   # Pool size par user/DB

Connexion via pooler :

# Via service pooler
psql postgresql://app_user:password@acid-minimal-cluster-pooler:5432/app_db

4. Clones et Standby Clusters

Clone depuis cluster existant :

apiVersion: "acid.zalan.do/v1"  
kind: postgresql  
metadata:  
  name: acid-clone
spec:
  # Clone depuis cluster source
  clone:
    cluster: "acid-minimal-cluster"
    # Ou depuis backup S3
    # s3_wal_path: "s3://bucket/path"

  numberOfInstances: 2
  # ... autres configs ...

Standby Cluster (DR) :

apiVersion: "acid.zalan.do/v1"  
kind: postgresql  
metadata:  
  name: acid-standby-cluster
spec:
  # Standby depuis cluster distant
  standby:
    s3_wal_path: "s3://my-backups/acid-minimal-cluster"

  numberOfInstances: 2
  # Configuration identique au primary

5. Major Version Upgrades

# Upgrade PostgreSQL 17 → 18
apiVersion: "acid.zalan.do/v1"  
kind: postgresql  
metadata:  
  name: acid-cluster
spec:
  dockerImage: ghcr.io/zalando/spilo-18:3.2-p1  # Nouvelle image
  postgresql:
    version: "18"  # Nouvelle version

  # Stratégie upgrade
  maintenanceWindows:
  - Mon:01:00-06:00
  - Sat:00:00-06:00

Processus upgrade (automatique) :

  1. Clone du cluster avec nouvelle version
  2. Tests automatiques
  3. Basculement pendant fenêtre maintenance
  4. Rollback automatique si échec

UI Web Zalando (Optionnelle)

# Installer UI
helm install postgres-operator-ui \
  ./charts/postgres-operator-ui \
  --namespace postgres-operator

# Port-forward
kubectl port-forward -n postgres-operator \
  svc/postgres-operator-ui 8081:80

# Accès : http://localhost:8081

Fonctionnalités UI :

  • Visualisation clusters
  • Monitoring en temps réel
  • Logs des opérations
  • Gestion utilisateurs/bases
  • Pas d'édition YAML (lecture seule)

Comparaison CloudNativePG vs Zalando

Tableau Comparatif Détaillé

Fonctionnalité CloudNativePG Zalando Gagnant
Simplicité ⭐⭐⭐⭐⭐ API simple ⭐⭐⭐⭐ Plus complexe CloudNativePG
Maturité ⭐⭐⭐⭐ 3 ans ⭐⭐⭐⭐⭐ 8+ ans Zalando
Documentation ⭐⭐⭐⭐⭐ Excellente ⭐⭐⭐⭐ Bonne CloudNativePG
Backend HA Native streaming Patroni Égalité
Backups pgBackRest intégré WAL-E/G externe CloudNativePG
PITR ✅ Intégré ✅ Via WAL-G Égalité
Connection Pooling ✅ PgBouncer ✅ PgBouncer Égalité
Monitoring ✅ Prometheus ✅ Prometheus Égalité
Rolling Updates ✅ Sans downtime ✅ Sans downtime Égalité
UI Web ❌ CLI seulement ✅ UI disponible Zalando
Logical Backups ⚠️ Manuel ✅ Automatique Zalando
Clone Clusters ✅ Via backup ✅ Natif Zalando
Standby Clusters Égalité
TLS Auto Égalité
Multi-tenant ✅ Via namespaces ✅ Via teamId Égalité
Communauté ⭐⭐⭐⭐ Active ⭐⭐⭐⭐⭐ Très active Zalando
Support commercial EDB ❌ Community CloudNativePG

Recommandations par Cas d'Usage

Choisir CloudNativePG si :

  • ✅ Débuter avec PostgreSQL sur K8s
  • ✅ API simple et moderne préférée
  • ✅ Backups intégrés souhaités (S3)
  • ✅ Support commercial EDB nécessaire
  • ✅ Documentation exhaustive appréciée
  • ✅ Équipe DevOps petite/moyenne

Choisir Zalando si :

  • ✅ Production à très grande échelle (100+ clusters)
  • ✅ Déjà familier avec Patroni
  • ✅ UI Web souhaitée
  • ✅ Logical backups automatiques requis
  • ✅ Clonage fréquent de clusters
  • ✅ Équipe DevOps expérimentée K8s

Notre recommandation générale :

Débutants / PME :
└─ CloudNativePG (simplicité, documentation)

Grandes entreprises / Scale :
└─ Zalando (maturité, fonctionnalités avancées)

Besoin support commercial :
└─ CloudNativePG (EDB) ou Crunchy Operator

Stockage sur Kubernetes

StorageClass : Définir le Type de Stockage

Un StorageClass définit le "type" de stockage disponible dans le cluster.

Exemple StorageClass :

# fast-ssd-storage-class.yaml
apiVersion: storage.k8s.io/v1  
kind: StorageClass  
metadata:  
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs  # ou gce-pd, azure-disk, etc.  
parameters:  
  type: gp3        # AWS : gp3 SSD
  iops: "16000"    # 16 000 IOPS
  throughput: "1000"  # 1000 MB/s
  encrypted: "true"
volumeBindingMode: WaitForFirstConsumer  # Création retardée  
allowVolumeExpansion: true  # Resize possible  
reclaimPolicy: Retain  # ou Delete  

Provisioners courants :

Cloud / Storage Provisioner Type
AWS EBS ebs.csi.aws.com gp3, io2, etc.
Google PD pd.csi.storage.gke.io pd-ssd, pd-balanced
Azure Disk disk.csi.azure.com Premium_LRS
Ceph RBD rbd.csi.ceph.com RBD
NFS nfs.csi.k8s.io NFS
Local Path rancher.io/local-path Disque local node

Recommandations PostgreSQL :

# Production : SSD haute performance
fast-ssd:
  type: gp3 (AWS) ou pd-ssd (GCP)
  iops: 10000+
  throughput: 500+ MB/s

# Développement : Standard
standard:
  type: gp2 (AWS) ou pd-standard (GCP)

PersistentVolume (PV) et PersistentVolumeClaim (PVC)

Schéma conceptuel :

┌─────────────────────────────────────────────────────┐
│                  StorageClass                       │
│  "fast-ssd" : AWS EBS gp3, 10000 IOPS               │
└────────────────────┬────────────────────────────────┘
                     │
                     │ Provisionner dynamiquement
                     ↓
┌─────────────────────────────────────────────────────┐
│            PersistentVolume (PV)                    │
│  Volume physique : /dev/xvdf (100 GB)               │
│  Créé automatiquement par StorageClass              │
└────────────────────┬────────────────────────────────┘
                     │
                     │ Binding (lien)
                     ↓
┌─────────────────────────────────────────────────────┐
│       PersistentVolumeClaim (PVC)                   │
│  Demande de stockage par PostgreSQL Pod             │
│  "Je veux 100 GB de fast-ssd"                       │
└────────────────────┬────────────────────────────────┘
                     │
                     │ Monte dans
                     ↓
┌─────────────────────────────────────────────────────┐
│               PostgreSQL Pod                        │
│  Volume monté : /var/lib/postgresql/data            │
└─────────────────────────────────────────────────────┘

Exemple PVC manuel (avec StatefulSet) :

# Les StatefulSets créent automatiquement PVC via volumeClaimTemplates
# Mais on peut aussi créer PVC manuellement :

apiVersion: v1  
kind: PersistentVolumeClaim  
metadata:  
  name: postgres-data
spec:
  accessModes:
  - ReadWriteOnce  # RWO : 1 pod à la fois
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 100Gi

Access Modes :

Mode Abréviation Description Usage PostgreSQL
ReadWriteOnce RWO 1 pod en RW ✅ Recommandé
ReadOnlyMany ROX N pods en RO ❌ Pas utile
ReadWriteMany RWX N pods en RW ❌ Dangereux (corruption)

⚠️ Important : PostgreSQL nécessite ReadWriteOnce (RWO). Jamais ReadWriteMany !

Snapshots de Volumes

# volumesnapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1  
kind: VolumeSnapshot  
metadata:  
  name: postgres-snapshot-20250101
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: postgres-data-postgres-cluster-1

Restauration depuis snapshot :

apiVersion: v1  
kind: PersistentVolumeClaim  
metadata:  
  name: postgres-data-restored
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: fast-ssd
  dataSource:
    name: postgres-snapshot-20250101
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  resources:
    requests:
      storage: 100Gi

Best Practices PostgreSQL sur Kubernetes

1. Ressources (Requests/Limits)

spec:
  containers:
  - name: postgres
    resources:
      # Requests : Minimum garanti
      requests:
        memory: "4Gi"
        cpu: "2"

      # Limits : Maximum autorisé
      limits:
        memory: "8Gi"
        cpu: "4"

Règles :

Memory :
├─ requests = Mémoire minimum (shared_buffers + overhead)
├─ limits = 2× requests (marge pour work_mem)
└─ Toujours définir les deux (éviter OOM)

CPU :
├─ requests = Usage moyen
├─ limits = 2-3× requests (pics de charge)
└─ Ne pas trop limiter (throttling)

Exemple calcul :

# PostgreSQL avec shared_buffers=4GB
requests:
  memory: "6Gi"  # 4GB + 2GB overhead OS/PostgreSQL
  cpu: "2"       # 2 cores minimum

limits:
  memory: "12Gi"  # 2× requests (pics work_mem)
  cpu: "4"        # 2× requests (pics requêtes)

2. Affinity et Anti-Affinity

Distribuer pods sur nodes différents :

apiVersion: postgresql.cnpg.io/v1  
kind: Cluster  
metadata:  
  name: postgres-cluster
spec:
  instances: 3

  # Anti-affinity : pas 2 pods sur même node
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            cnpg.io/cluster: postgres-cluster
        topologyKey: kubernetes.io/hostname

Avantages :

  • Tolérance panne node (Primary et Standby sur nodes différents)
  • Meilleure distribution ressources
  • HA renforcée

Node Affinity (préférer nodes SSD) :

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: storage-type
          operator: In
          values:
          - ssd
          - nvme

3. Taints et Tolerations

Dédier nodes à PostgreSQL :

# Taint un node (uniquement PostgreSQL)
kubectl taint nodes node-1 workload=database:NoSchedule

# PostgreSQL tolère ce taint
spec:
  tolerations:
  - key: "workload"
    operator: "Equal"
    value: "database"
    effect: "NoSchedule"

4. Priority Classes

# priorityclass.yaml
apiVersion: scheduling.k8s.io/v1  
kind: PriorityClass  
metadata:  
  name: high-priority-database
value: 1000000  # Plus élevé = plus prioritaire  
globalDefault: false  
description: "High priority for databases"  
# Utiliser dans Cluster
spec:
  priorityClassName: high-priority-database

Avantage : En cas de ressources limitées, PostgreSQL préempte autres pods.

5. Network Policies

Isoler PostgreSQL :

apiVersion: networking.k8s.io/v1  
kind: NetworkPolicy  
metadata:  
  name: postgres-network-policy
spec:
  podSelector:
    matchLabels:
      cnpg.io/cluster: postgres-cluster

  policyTypes:
  - Ingress
  - Egress

  # Ingress : Qui peut se connecter à PostgreSQL
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: app-namespace
    - podSelector:
        matchLabels:
          app: myapp
    ports:
    - protocol: TCP
      port: 5432

  # Egress : Où PostgreSQL peut se connecter
  egress:
  - to:
    - podSelector:
        matchLabels:
          cnpg.io/cluster: postgres-cluster
    ports:
    - protocol: TCP
      port: 5432
  - to:  # S3 pour backups
    - namespaceSelector: {}
    ports:
    - protocol: TCP
      port: 443

6. Pod Disruption Budgets (PDB)

Garantir disponibilité pendant maintenances :

apiVersion: policy/v1  
kind: PodDisruptionBudget  
metadata:  
  name: postgres-pdb
spec:
  minAvailable: 2  # Minimum 2 pods disponibles
  selector:
    matchLabels:
      cnpg.io/cluster: postgres-cluster

Effet :

  • Empêche drain/éviction simultanée de trop de pods
  • Garantit 2 pods minimum pendant mises à jour cluster
  • Assure continuité service

7. Secrets Management

❌ Mauvaise pratique :

env:
- name: POSTGRES_PASSWORD
  value: "SuperSecret123"  # En clair dans YAML !

✅ Bonne pratique : Kubernetes Secrets

# secret.yaml
apiVersion: v1  
kind: Secret  
metadata:  
  name: postgres-secret
type: Opaque  
data:  
  password: U3VwZXJTZWNyZXQxMjM=  # base64
# Utiliser dans pod
env:
- name: POSTGRES_PASSWORD
  valueFrom:
    secretKeyRef:
      name: postgres-secret
      key: password

✅ Meilleure pratique : External Secrets (Vault, AWS Secrets Manager)

# externalsecret.yaml
apiVersion: external-secrets.io/v1beta1  
kind: ExternalSecret  
metadata:  
  name: postgres-secret
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: postgres-secret
    creationPolicy: Owner
  data:
  - secretKey: password
    remoteRef:
      key: database/postgres
      property: password

Monitoring et Observabilité sur K8s

Stack Monitoring Complète

Architecture recommandée :

┌────────────────────────────────────────────────────┐
│              PostgreSQL Cluster                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│  │ PG-0     │  │ PG-1     │  │ PG-2     │          │
│  │ :9187    │  │ :9187    │  │ :9187    │          │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘          │
│       │             │             │                │
│       └─────────────┴─────────────┘                │
│                     │ Scrape métriques             │
│                     ↓                              │
│  ┌──────────────────────────────────────────┐      │
│  │          Prometheus                      │      │
│  │  - Collecte métriques                    │      │
│  │  - Stockage time-series                  │      │
│  │  - Alerting rules                        │      │
│  └───────────────────┬──────────────────────┘      │
│                      │                             │
│                      ↓                             │
│  ┌──────────────────────────────────────────┐      │
│  │          Grafana                         │      │
│  │  - Dashboards                            │      │
│  │  - Visualisations                        │      │
│  └──────────────────────────────────────────┘      │
│                                                    │
│  ┌──────────────────────────────────────────┐      │
│  │        AlertManager                      │      │
│  │  - Routage alertes                       │      │
│  │  - Notifications (Email, Slack, etc.)    │      │
│  └──────────────────────────────────────────┘      │
└────────────────────────────────────────────────────┘

Installation Stack Prometheus

Via kube-prometheus-stack (Helm) :

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

# Installer stack complète
helm install kube-prometheus-stack \
  prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false

Composants installés :

  • Prometheus Operator
  • Prometheus Server
  • AlertManager
  • Grafana
  • Node Exporter
  • Kube State Metrics

ServiceMonitor CloudNativePG

CloudNativePG expose automatiquement métriques si activé :

apiVersion: postgresql.cnpg.io/v1  
kind: Cluster  
metadata:  
  name: postgres-cluster
spec:
  instances: 3
  monitoring:
    enablePodMonitor: true  # Active PodMonitor

PodMonitor créé automatiquement :

# Vérifié automatiquement par operator
apiVersion: monitoring.coreos.com/v1  
kind: PodMonitor  
metadata:  
  name: postgres-cluster
spec:
  selector:
    matchLabels:
      cnpg.io/cluster: postgres-cluster
  podMetricsEndpoints:
  - port: metrics
    path: /metrics

Dashboards Grafana

Importer dashboards PostgreSQL :

# Accéder Grafana
kubectl port-forward -n monitoring \
  svc/kube-prometheus-stack-grafana 3000:80

# Credentials par défaut
# User: admin
# Password: prom-operator (ou récupérer dans secret)

# Dashboards recommandés (Grafana.com) :
# - CloudNativePG : 17068
# - PostgreSQL Database : 9628
# - PgBouncer : 13209

Dashboard custom :

# configmap-dashboard.yaml
apiVersion: v1  
kind: ConfigMap  
metadata:  
  name: postgres-dashboard
  namespace: monitoring
  labels:
    grafana_dashboard: "1"
data:
  postgres-dashboard.json: |
    {
      "dashboard": {
        "title": "PostgreSQL Custom",
        "panels": [...]
      }
    }

Alertes Critiques

apiVersion: monitoring.coreos.com/v1  
kind: PrometheusRule  
metadata:  
  name: postgresql-alerts
  namespace: monitoring
spec:
  groups:
  - name: postgresql.rules
    interval: 30s
    rules:
    # Alerte : Instance Down
    - alert: PostgreSQLInstanceDown
      expr: up{job=~".*postgres.*"} == 0
      for: 1m
      labels:
        severity: critical
      annotations:
        summary: "PostgreSQL instance {{ $labels.pod }} is down"
        description: "Pod {{ $labels.pod }} in namespace {{ $labels.namespace }} is down for more than 1 minute"

    # Alerte : Replication Lag
    - alert: PostgreSQLReplicationLag
      expr: |
        (pg_replication_lag{application_name!=""} / 1024 / 1024) > 100
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "PostgreSQL replication lag > 100 MB"
        description: "Replication lag on {{ $labels.pod }} is {{ $value | humanize }} MB"

    # Alerte : Connexions élevées
    - alert: PostgreSQLTooManyConnections
      expr: |
        sum(pg_stat_activity_count) by (pod)
        /
        max(pg_settings_max_connections) by (pod)
        > 0.8
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "PostgreSQL connections > 80%"
        description: "Pod {{ $labels.pod }} has {{ $value | humanizePercentage }} connections used"

    # Alerte : Deadlocks
    - alert: PostgreSQLDeadlocks
      expr: rate(pg_stat_database_deadlocks[5m]) > 0
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "PostgreSQL deadlocks detected"
        description: "Database {{ $labels.datname }} has {{ $value }} deadlocks/s"

    # Alerte : Disk Space
    - alert: PostgreSQLDiskSpaceLow
      expr: |
        (1 - (node_filesystem_avail_bytes{mountpoint="/var/lib/postgresql/data"}
        / node_filesystem_size_bytes{mountpoint="/var/lib/postgresql/data"})) > 0.85
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "PostgreSQL disk space > 85%"

Troubleshooting Kubernetes

Problème 1 : Pods en CrashLoopBackOff

Symptôme :

kubectl get pods
# NAME                  READY   STATUS             RESTARTS
# postgres-cluster-1    0/1     CrashLoopBackOff   5

Diagnostic :

# Logs du pod
kubectl logs postgres-cluster-1

# Logs précédent crash
kubectl logs postgres-cluster-1 --previous

# Événements
kubectl describe pod postgres-cluster-1

# Erreurs communes :
# - Permission denied /var/lib/postgresql/data
# - Insufficient memory (OOMKilled)
# - Invalid configuration

Solutions :

# 1. Permissions PVC
kubectl exec postgres-cluster-1 -- ls -la /var/lib/postgresql/data
# Corriger : chown -R 999:999 dans initContainer

# 2. Mémoire insuffisante
kubectl describe pod postgres-cluster-1 | grep -i oom
# Augmenter limits.memory

# 3. Configuration invalide
kubectl logs postgres-cluster-1 | grep FATAL
# Corriger postgresql.parameters

Problème 2 : PVC en Pending

Symptôme :

kubectl get pvc
# NAME                     STATUS    VOLUME   CAPACITY
# postgres-data-cluster-1  Pending

Diagnostic :

# Événements PVC
kubectl describe pvc postgres-data-cluster-1

# Erreurs communes :
# - "no persistent volumes available" → Créer PV ou dynamic provisioning
# - "storageclass not found" → Vérifier StorageClass existe
# - "insufficient capacity" → Quotas dépassés

Solutions :

# Vérifier StorageClass
kubectl get storageclass

# Créer si manquant
kubectl apply -f storageclass.yaml

# Vérifier quotas namespace
kubectl describe resourcequota -n default

Problème 3 : Connexion Refusée

Symptôme :

psql -h postgres-cluster-rw -U app
# connection refused

Diagnostic :

# Service existe ?
kubectl get svc postgres-cluster-rw

# Endpoints configurés ?
kubectl get endpoints postgres-cluster-rw

# Pod Ready ?
kubectl get pods -l cnpg.io/cluster=postgres-cluster

# Network Policy bloque ?
kubectl get networkpolicy

Solutions :

# Tester depuis pod dans cluster
kubectl run -it --rm debug --image=postgres:18 -- \
  psql postgresql://app:password@postgres-cluster-rw:5432/app

# Si ça marche → Problème NetworkPolicy ou DNS externe
# Si ça marche pas → Problème PostgreSQL (pg_hba.conf)

Problème 4 : Failover ne Fonctionne Pas

Symptôme :

Primary down, standby ne se promeut pas.

Diagnostic :

# Status cluster
kubectl get cluster postgres-cluster -o yaml

# Vérifier leader election
kubectl logs -n cnpg-system deployment/cnpg-controller-manager

# Vérifier réplication
kubectl exec postgres-cluster-1 -- \
  psql -U postgres -c "SELECT * FROM pg_stat_replication;"

Solutions :

# Forcer failover manuel
kubectl cnpg promote postgres-cluster postgres-cluster-2

# Ou via annotation
kubectl annotate cluster postgres-cluster \
  cnpg.io/forcedFailover=postgres-cluster-2

Problème 5 : Performance Dégradée

Diagnostic :

# Métriques ressources
kubectl top pods -l cnpg.io/cluster=postgres-cluster

# Logs slow queries
kubectl logs postgres-cluster-1 | grep "duration:"

# Vérifier I/O wait
kubectl exec postgres-cluster-1 -- iostat -x 1 5

# Connexions actives
kubectl exec postgres-cluster-1 -- \
  psql -U postgres -c "SELECT count(*) FROM pg_stat_activity WHERE state='active';"

Solutions :

# 1. Augmenter ressources
kubectl edit cluster postgres-cluster
# → Modifier resources.limits

# 2. Tuning PostgreSQL
kubectl edit cluster postgres-cluster
# → Ajuster postgresql.parameters

# 3. Vérifier StorageClass IOPS
kubectl describe storageclass fast-ssd

Migration vers Kubernetes

Stratégies de Migration

1. Blue/Green Deployment

Infrastructure Actuelle (Blue) :
├─ PostgreSQL VM/Bare Metal (production)
└─ Applications connectées

Migration :
1. Déployer PostgreSQL sur K8s (Green)
2. Configurer réplication Blue → Green
3. Synchronisation complète
4. Basculer applications vers Green
5. Valider quelques jours
6. Désactiver Blue si OK

Avantages :

  • Rollback facile
  • Validation complète avant bascule
  • Risque minimal

2. Réplication Logique

# Sur PostgreSQL source (VM/Bare Metal)
CREATE PUBLICATION migration_pub FOR ALL TABLES;

# Sur PostgreSQL K8s
CREATE SUBSCRIPTION migration_sub
  CONNECTION 'host=old-postgres.example.com port=5432 dbname=production user=repl password=pass'
  PUBLICATION migration_pub;

# Synchronisation continue jusqu'à bascule

3. Dump/Restore (Downtime)

# 1. Arrêter applications
# 2. pg_dump depuis source
pg_dump -h old-postgres -U postgres -Fc production > backup.dump

# 3. Restore dans K8s
kubectl cp backup.dump postgres-cluster-1:/tmp/  
kubectl exec postgres-cluster-1 -- \  
  pg_restore -U postgres -d production /tmp/backup.dump

# 4. Valider données
# 5. Rediriger applications vers K8s

Checklist Migration

Préparation :
☐ Inventaire bases/utilisateurs/extensions
☐ Dimensionnement K8s (CPU/RAM/Storage)
☐ Choix Operator (CloudNativePG vs Zalando)
☐ Configuration StorageClass performante
☐ Tests performances K8s

Migration :
☐ Déploiement PostgreSQL K8s (environnement test)
☐ Tests fonctionnels application
☐ Configuration réplication (si Blue/Green)
☐ Monitoring configuré (Prometheus/Grafana)
☐ Runbook failback préparé
☐ Validation équipe

Bascule :
☐ Fenêtre de maintenance planifiée
☐ Backup final source
☐ Synchronisation complète
☐ Bascule DNS/Configuration app
☐ Validation fonctionnelle
☐ Monitoring intensif 24-48h

Post-Migration :
☐ Désactivation source (après validation)
☐ Documentation à jour
☐ Formation équipe K8s
☐ Rétrospective migration

Conclusion : PostgreSQL sur Kubernetes

Récapitulatif

Kubernetes transforme l'opération de PostgreSQL :

Automatisation poussée

  • Déploiement 1 commande
  • HA automatique avec failover
  • Backups planifiés
  • Rolling updates sans downtime

Scalabilité

  • Read replicas à la demande
  • Utilisation optimale ressources cluster
  • Multi-environnements (dev/staging/prod)

Portabilité

  • Multi-cloud (AWS, GCP, Azure)
  • Éviter vendor lock-in
  • Infrastructure as Code (GitOps)

Résilience

  • Self-healing automatique
  • Distribution anti-affinity
  • Pod Disruption Budgets

Mais avec des défis :

⚠️ Complexité

  • Courbe apprentissage K8s + PostgreSQL
  • Debugging multi-couches
  • Expertise requise

⚠️ Overhead

  • Performance ~95% du bare metal
  • Latence réseau supplémentaire
  • Couches d'abstraction

⚠️ Stateful sur Stateless

  • K8s initialement conçu pour stateless
  • Gestion état complexe
  • PersistentVolumes critiques

Comparaison Finale : Tous les Déploiements

Critère Bare Metal VM Conteneurs Kubernetes
Performance ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐
Simplicité ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐
HA Automatique ⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐
Scalabilité ⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
Portabilité ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
Coût Initial €€€€€ €€€€ €€ €€€
Maintenance ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐
Maturité ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐

Quand Utiliser Kubernetes pour PostgreSQL ?

✅ Kubernetes est IDÉAL pour :

  1. Organisations Cloud-Native

    • Architecture microservices
    • CI/CD mature
    • Culture DevOps établie
  2. Multi-Environnements

    • Dev, staging, prod sur même infrastructure
    • Isolation namespace
    • Utilisation efficace ressources
  3. Croissance Rapide

    • Scaling horizontal (read replicas)
    • Ajout environnements fréquent
    • Besoin agilité
  4. Multi-Cloud / Portabilité

    • Éviter vendor lock-in
    • Stratégie multi-cloud
    • Migrations cloud facilitées
  5. Équipe DevOps Mature

    • Expertise Kubernetes
    • Automatisation poussée
    • GitOps / IaC

❌ Éviter Kubernetes si :

  1. Équipe sans Expertise K8s

    • Courbe apprentissage trop raide
    • Risque opérationnel élevé
    • → Préférer VM avec Patroni
  2. Base de Données Unique Critique

    • Simplicité préférable
    • VM ou bare metal plus direct
    • Moins de points de défaillance
  3. Performance Extrême Requise

    • Latence sub-milliseconde
    • Trading haute fréquence
    • → Bare metal optimisé
  4. Budget / Ressources Limités

    • Overhead infrastructure K8s
    • Ressources cluster management
    • → VMs plus économiques

Recommendations Finales

Par Taille d'Organisation :

Startup / Petite Équipe (1-10 personnes) :
└─ Managed DBaaS (RDS, Cloud SQL, Azure Database)
   Ou : Conteneurs simples (Docker Compose)

PME (10-50 personnes, quelques bases) :
└─ VM avec Patroni/Réplication
   Ou : CloudNativePG si expertise K8s existante

Moyenne Entreprise (50-200 personnes, 10-50 bases) :
└─ Kubernetes avec CloudNativePG (simplicité)
   Monitoring centralisé (Prometheus/Grafana)

Grande Entreprise (200+ personnes, 50+ bases) :
└─ Kubernetes avec Zalando Operator (scale)
   Platform Engineering team dédiée

Par Type d'Application :

Application Web Moderne (SaaS) :
└─ ✅ Kubernetes + CloudNativePG

Microservices :
└─ ✅ Kubernetes + Operator

Application Legacy Monolithique :
└─ ⚠️ VM (migration progressive vers K8s)

Application Haute Fréquence (Finance) :
└─ ❌ Bare Metal (performance critique)

Plateforme Multi-Tenant :
└─ ✅ Kubernetes (isolation, scaling)

L'Avenir : PostgreSQL et Kubernetes

Tendances 2025+ :

  1. Operators de Plus en Plus Intelligents

    • Auto-tuning IA
    • Prédiction pannes
    • Optimisation automatique
  2. Intégration Cloud Native

    • Service Mesh (Istio, Linkerd)
    • GitOps natif (ArgoCD, Flux)
    • Observability renforcée (OpenTelemetry)
  3. Serverless PostgreSQL sur K8s

    • Scale-to-zero
    • Pay-per-query
    • Cold starts optimisés
  4. Edge Computing

    • PostgreSQL sur edge K8s
    • Synchronisation multi-sites
    • IoT et données locales

PostgreSQL 18 sur Kubernetes :

  • I/O asynchrone bénéficie même sur K8s
  • Colonnes virtuelles simplifient schémas
  • OAuth integration pour architectures modernes
  • Compression zstd réduit trafic réseau

Conclusion Ultime :

Kubernetes et PostgreSQL forment une combinaison puissante pour les organisations modernes. Avec les bons outils (Operators), la complexité est maîtrisable et les bénéfices (HA, scalabilité, portabilité) sont considérables.

Pour débuter :

  1. ✅ Installer cluster K8s de test (Minikube, kind, k3s)
  2. ✅ Déployer CloudNativePG (plus simple)
  3. ✅ Expérimenter failover, backups, scaling
  4. ✅ Migrer environnement dev/staging
  5. ✅ Production quand équipe à l'aise

La clé du succès : Formation, pratique, patience. Kubernetes est un investissement qui paie sur le long terme.


Prochaines étapes suggérées :

  • 19.2. PostgreSQL dans le Cloud (AWS RDS, Google Cloud SQL, Azure)
    1. Drivers et Connexion Applicative
    1. Haute Disponibilité et Réplication (approfondissement)

⏭️ PostgreSQL dans le Cloud