Prérequis
Ce tutoriel suppose une connaissance solide de Kubernetes (Pods, Deployments, Services, RBAC, namespaces). Si vous débutez, consultez d'abord le
guide d'introduction à Kubernetes et le
guide Docker avancé. Un cluster fonctionnel (minikube, kind ou production) est nécessaire pour les exemples.
Pourquoi Kubernetes avancé ? Au-delà des Deployments basiques
Kubernetes réduit les Deployments, Services et ConfigMaps à leur minimum. La plupart des équipes s'y arrêtent, et s'arrêter là crée des angles morts : des opérations répétitives faites manuellement, des secrets exposés dans Git, des déploiements non reproductibles, des clusters sans politique de sécurité cohérente, et un monitoring qui disparaît dès qu'un service est redéployé.
Les patterns avancés de Kubernetes adressent chacun de ces problèmes de manière systématique :
- Operators : automatiser les opérations stateful complexes (bases de données, messaging) via le pattern Controller
- Helm Security : gérer les secrets sans les exposer en Git, signer les charts, appliquer le RBAC
- GitOps avec ArgoCD : Git comme source unique de vérité, déploiements déclaratifs et auto-réconciliés
- Hardening cluster : PodSecurityAdmission, NetworkPolicies, OPA/Gatekeeper pour zéro-trust
- Prometheus Operator : monitoring auto-découvrant, alerting déclaratif par namespace
Ce guide est conçu pour être appliqué séquentiellement sur un cluster réel. Chaque section est indépendante et peut être adoptée progressivement.
1. Kubernetes Operators : automatiser l'opérationnel
Le pattern Controller et les CRD
Kubernetes repose sur la boucle de réconciliation : un contrôleur observe l'état courant du cluster, le compare à l'état désiré défini dans les objets API, et agit pour les faire converger. Les Custom Resource Definitions (CRD) étendent l'API Kubernetes avec de nouveaux types d'objets. Un Operator combine ces deux concepts : il définit un CRD pour son domaine applicatif et un contrôleur qui sait réconcilier ces ressources personnalisées.
# CRD : définir un nouveau type de ressource "PostgreSQLCluster"
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: postgresqlclusters.db.example.com
spec:
group: db.example.com
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [replicas, version]
properties:
replicas:
type: integer
minimum: 1
maximum: 7
version:
type: string
pattern: '^\d+\.\d+$'
storage:
type: string
default: "10Gi"
backupSchedule:
type: string
description: "Cron expression pour les sauvegardes automatiques"
status:
type: object
properties:
phase:
type: string
enum: [Pending, Running, Degraded, Failed]
readyReplicas:
type: integer
primaryEndpoint:
type: string
scope: Namespaced
names:
plural: postgresqlclusters
singular: postgresqlcluster
kind: PostgreSQLCluster
shortNames: [pgc]
# Utilisation de la ressource personnalisée
apiVersion: db.example.com/v1alpha1
kind: PostgreSQLCluster
metadata:
name: mon-cluster-postgres
namespace: production
spec:
replicas: 3
version: "16.1"
storage: "50Gi"
backupSchedule: "0 2 * * *" # Sauvegarde à 2h du matin
Contrôleur Operator en Go : structure de base
Le framework Kubebuilder est le standard pour créer des Operators Go. Il génère le boilerplate (scaffolding) et intègre controller-runtime, la bibliothèque de reconciliation de Kubernetes.
// controllers/postgresqlcluster_controller.go
package controllers
import (
"context"
"fmt"
"time"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
dbv1alpha1 "github.com/example/pg-operator/api/v1alpha1"
)
// PostgreSQLClusterReconciler implémente la boucle de réconciliation
type PostgreSQLClusterReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// Reconcile est appelé à chaque changement sur les PostgreSQLCluster
// ou sur les ressources qu'il gère (StatefulSet, Service, etc.)
func (r *PostgreSQLClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// 1. Récupérer la ressource PostgreSQLCluster
pgc := &dbv1alpha1.PostgreSQLCluster{}
if err := r.Get(ctx, req.NamespacedName, pgc); err != nil {
if errors.IsNotFound(err) {
// La ressource a été supprimée, rien à faire
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
}
// 2. Vérifier si le StatefulSet existe déjà
existingSts := &appsv1.StatefulSet{}
err := r.Get(ctx, req.NamespacedName, existingSts)
if errors.IsNotFound(err) {
// 3. Créer le StatefulSet s'il n'existe pas
sts := r.buildStatefulSet(pgc)
if err := r.Create(ctx, sts); err != nil {
logger.Error(err, "Impossible de créer le StatefulSet")
return ctrl.Result{}, err
}
logger.Info("StatefulSet créé", "name", pgc.Name)
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
} else if err != nil {
return ctrl.Result{}, err
}
// 4. Réconcilier : mettre à jour si les specs ont changé
if *existingSts.Spec.Replicas != int32(pgc.Spec.Replicas) {
replicas := int32(pgc.Spec.Replicas)
existingSts.Spec.Replicas = &replicas
if err := r.Update(ctx, existingSts); err != nil {
return ctrl.Result{}, err
}
logger.Info("Replicas mis à jour", "replicas", replicas)
}
// 5. Mettre à jour le statut de la ressource
pgc.Status.Phase = "Running"
pgc.Status.ReadyReplicas = int(existingSts.Status.ReadyReplicas)
if err := r.Status().Update(ctx, pgc); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}
// buildStatefulSet construit le StatefulSet pour PostgreSQL
func (r *PostgreSQLClusterReconciler) buildStatefulSet(pgc *dbv1alpha1.PostgreSQLCluster) *appsv1.StatefulSet {
replicas := int32(pgc.Spec.Replicas)
labels := map[string]string{
"app": "postgresql",
"controller": pgc.Name,
}
return &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: pgc.Name,
Namespace: pgc.Namespace,
// OwnerReference : si le PostgreSQLCluster est supprimé,
// le StatefulSet l'est aussi automatiquement (garbage collection)
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(pgc, dbv1alpha1.GroupVersion.WithKind("PostgreSQLCluster")),
},
},
Spec: appsv1.StatefulSetSpec{
Replicas: &replicas,
ServiceName: pgc.Name + "-headless",
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: labels},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "postgresql",
Image: fmt.Sprintf("postgres:%s-alpine", pgc.Spec.Version),
Ports: []corev1.ContainerPort{{ContainerPort: 5432}},
Env: []corev1.EnvVar{
{Name: "POSTGRES_PASSWORD", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: pgc.Name + "-credentials"},
Key: "password",
},
}},
},
},
},
},
},
},
}
}
// SetupWithManager enregistre le contrôleur et déclare les ressources à surveiller
func (r *PostgreSQLClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&dbv1alpha1.PostgreSQLCluster{}). // Surveiller les PostgreSQLCluster
Owns(&appsv1.StatefulSet{}). // Et les StatefulSets qu'il crée
Owns(&corev1.Service{}).
Complete(r)
}
Operators existants : CloudNativePG, Strimzi, Cert-Manager
Dans la pratique, il est rarement nécessaire d'écrire un Operator from scratch. L'écosystème CNCF propose des Operators de production maintenus par des équipes spécialisées :
- CloudNativePG : PostgreSQL en haute disponibilité avec failover automatique, streaming replication et backup vers S3
- Strimzi : Kafka sur Kubernetes avec topic management, user management et mirror maker
- Cert-Manager : émission et renouvellement automatique de certificats TLS (Let's Encrypt, Vault, auto-signé)
- Prometheus Operator : déploiement et configuration déclarative de Prometheus (couvert en section 6)
# Installer CloudNativePG via kubectl
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.23/releases/cnpg-1.23.0.yaml
# Créer un cluster PostgreSQL 3 nœuds avec backup S3
cat <<EOF | kubectl apply -f -
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgres-prod
namespace: database
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.2
storage:
size: 50Gi
storageClass: fast-ssd
backup:
barmanObjectStore:
destinationPath: s3://mon-bucket/postgres-backups
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: SECRET_ACCESS_KEY
retentionPolicy: "30d"
postgresql:
parameters:
max_connections: "200"
shared_buffers: "256MB"
EOF
# Vérifier l'état du cluster
kubectl get cluster postgres-prod -n database
kubectl get pods -n database -l cnpg.io/cluster=postgres-prod
Commentaires