🔝 Retour au Sommaire
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
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).
| 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é |
┌────────────────────────────────────────────────────────────┐
│ 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
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
✅ Cas d'Usage Idéaux :
-
Applications Cloud-Native
- Architecture microservices
- Déploiements fréquents
- Infrastructure as Code (GitOps)
-
Multi-Environnements
- Dev, Staging, Prod sur même cluster
- Isolation par namespaces
- Utilisation optimisée ressources
-
Scaling Dynamique
- Charge variable
- Auto-scaling horizontal (read replicas)
- Utilisation efficace du cluster
-
Multi-Cloud / Hybrid Cloud
- Portabilité entre clouds (AWS, GCP, Azure)
- Infrastructure unifiée
- Éviter vendor lock-in
-
Équipe DevOps Mature
- Expertise Kubernetes
- Automatisation poussée
- Culture GitOps
❌ Cas où Éviter K8s :
-
Équipe sans Expertise K8s
- Courbe d'apprentissage très raide
- Complexité opérationnelle élevée
-
Base de Données Unique Critique
- VM ou bare metal plus simple
- Moins de couches d'abstraction
- Troubleshooting plus direct
-
Performance Absolue Requise
- Latence sub-milliseconde
- Overhead K8s inacceptable
- Bare metal préférable
-
Réglementations Strictes
- Certifications spécifiques
- Audits complexes avec K8s
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
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émarrage2. 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
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 postgresLimitations 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 !
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
┌───────────────────────────────────────────────────────┐
│ 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 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────────┘
| 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 | |
| KubeDB | AppsCode | ⭐⭐⭐⭐ | Faible-Moyenne | ✅ Multi-DB |
Notre focus :
- CloudNativePG (recommandé débuter)
- Zalando Postgres Operator (très mature)
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
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 -fInstallation 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 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: trueSecret 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 ← StandbyCe 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
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/appDepuis 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/appVia 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: 5432Configuration 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: barmanObjectStoreCré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-20250101Scheduled 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: barmanObjectStoreRestauration 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_KEYFonctionnement 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: 2Explication :
- 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 StandbyCloudNativePG 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/17068Alerting (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 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
# 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-operatorOu 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-operatorminimal-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 ← PgBouncerServices 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 ← PgBouncerapiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
name: acid-cluster
spec:
# ... autres configs ...
# Backups logiques automatiques
enableLogicalBackup: true
logicalBackupSchedule: "30 2 * * *" # 2h30 tous les joursStockage 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" 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_adminspec:
# 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/DBConnexion via pooler :
# Via service pooler
psql postgresql://app_user:password@acid-minimal-cluster-pooler:5432/app_dbClone 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# 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:00Processus upgrade (automatique) :
- Clone du cluster avec nouvelle version
- Tests automatiques
- Basculement pendant fenêtre maintenance
- Rollback automatique si échec
# 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:8081Fonctionnalités UI :
- Visualisation clusters
- Monitoring en temps réel
- Logs des opérations
- Gestion utilisateurs/bases
- Pas d'édition YAML (lecture seule)
| 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 | ✅ 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 |
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
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)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: 100GiAccess 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) |
# 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-1Restauration 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: 100Gispec:
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)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/hostnameAvantages :
- 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
- nvmeDédier nodes à PostgreSQL :
# Taint un node (uniquement PostgreSQL)
kubectl taint nodes node-1 workload=database:NoSchedule
# PostgreSQL tolère ce taintspec:
tolerations:
- key: "workload"
operator: "Equal"
value: "database"
effect: "NoSchedule"# 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-databaseAvantage : En cas de ressources limitées, PostgreSQL préempte autres pods.
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: 443Garantir 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-clusterEffet :
- Empêche drain/éviction simultanée de trop de pods
- Garantit 2 pods minimum pendant mises à jour cluster
- Assure continuité service
❌ 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: passwordArchitecture 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.) │ │
│ └──────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
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=falseComposants installés :
- Prometheus Operator
- Prometheus Server
- AlertManager
- Grafana
- Node Exporter
- Kube State Metrics
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 PodMonitorPodMonitor 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: /metricsImporter 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 : 13209Dashboard 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": [...]
}
}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%"Symptôme :
kubectl get pods
# NAME READY STATUS RESTARTS
# postgres-cluster-1 0/1 CrashLoopBackOff 5Diagnostic :
# 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 configurationSolutions :
# 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.parametersSymptôme :
kubectl get pvc
# NAME STATUS VOLUME CAPACITY
# postgres-data-cluster-1 PendingDiagnostic :
# É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ésSolutions :
# Vérifier StorageClass
kubectl get storageclass
# Créer si manquant
kubectl apply -f storageclass.yaml
# Vérifier quotas namespace
kubectl describe resourcequota -n defaultSymptôme :
psql -h postgres-cluster-rw -U app
# connection refusedDiagnostic :
# 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 networkpolicySolutions :
# 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)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-2Diagnostic :
# 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-ssdInfrastructure 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
# 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# 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 K8sPré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
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 :
- Courbe apprentissage K8s + PostgreSQL
- Debugging multi-couches
- Expertise requise
- Performance ~95% du bare metal
- Latence réseau supplémentaire
- Couches d'abstraction
- K8s initialement conçu pour stateless
- Gestion état complexe
- PersistentVolumes critiques
| Critère | Bare Metal | VM | Conteneurs | Kubernetes |
|---|---|---|---|---|
| Performance | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Simplicité | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
| HA Automatique | ❌ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| Scalabilité | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Portabilité | ⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Coût Initial | €€€€€ | €€€€ | €€ | €€€ |
| Maintenance | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Maturité | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
✅ Kubernetes est IDÉAL pour :
-
Organisations Cloud-Native
- Architecture microservices
- CI/CD mature
- Culture DevOps établie
-
Multi-Environnements
- Dev, staging, prod sur même infrastructure
- Isolation namespace
- Utilisation efficace ressources
-
Croissance Rapide
- Scaling horizontal (read replicas)
- Ajout environnements fréquent
- Besoin agilité
-
Multi-Cloud / Portabilité
- Éviter vendor lock-in
- Stratégie multi-cloud
- Migrations cloud facilitées
-
Équipe DevOps Mature
- Expertise Kubernetes
- Automatisation poussée
- GitOps / IaC
❌ Éviter Kubernetes si :
-
Équipe sans Expertise K8s
- Courbe apprentissage trop raide
- Risque opérationnel élevé
- → Préférer VM avec Patroni
-
Base de Données Unique Critique
- Simplicité préférable
- VM ou bare metal plus direct
- Moins de points de défaillance
-
Performance Extrême Requise
- Latence sub-milliseconde
- Trading haute fréquence
- → Bare metal optimisé
-
Budget / Ressources Limités
- Overhead infrastructure K8s
- Ressources cluster management
- → VMs plus économiques
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)
Tendances 2025+ :
-
Operators de Plus en Plus Intelligents
- Auto-tuning IA
- Prédiction pannes
- Optimisation automatique
-
Intégration Cloud Native
- Service Mesh (Istio, Linkerd)
- GitOps natif (ArgoCD, Flux)
- Observability renforcée (OpenTelemetry)
-
Serverless PostgreSQL sur K8s
- Scale-to-zero
- Pay-per-query
- Cold starts optimisés
-
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 :
- ✅ Installer cluster K8s de test (Minikube, kind, k3s)
- ✅ Déployer CloudNativePG (plus simple)
- ✅ Expérimenter failover, backups, scaling
- ✅ Migrer environnement dev/staging
- ✅ 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)
-
- Drivers et Connexion Applicative
-
- Haute Disponibilité et Réplication (approfondissement)