Accueil

Kubernetes Avancé : Operators, Helm Security et GitOps avec ArgoCD

Devops
Difficulte: Advanced
21 min de lecture

Maîtrisez Kubernetes avancé : Operators et CRD, sécurité Helm, GitOps avec ArgoCD, hardening cluster, Prometheus Operator. Guide complet DevSecOps 2026.

Retour aux tutoriels

L'essentiel

  • Un Operator = une CRD plus une boucle de réconciliation. On n'en écrit un que pour du stateful à vraie logique opérationnelle (failover automatique, réplication, backup planifié) ; sinon un chart Helm suffit. CloudNativePG, Strimzi et cert-manager couvrent déjà l'essentiel des besoins de production.
  • Secrets et Git : SOPS/Age chiffre les fichiers values pour pouvoir les commiter (helm secrets upgrade --install), mais le modèle GitOps strict ne stocke que des références et laisse External Secrets Operator synchroniser depuis AWS Secrets Manager ou Vault (refreshInterval: 1h).
  • Dans ArgoCD, syncPolicy.automated avec prune: true supprime du cluster ce qui disparaît de Git et selfHeal: true annule toute modification manuelle ; ServerSideApply=true est nécessaire pour les CRD volumineuses, et le pattern App of Apps (ou ApplicationSet) pilote un cluster entier depuis un seul dépôt.
  • Hardening en deux couches : une NetworkPolicy default-deny par namespace — en réautorisant explicitement le DNS vers CoreDNS en UDP et TCP 53, sinon plus rien ne résout — et le Pod Security Admission (successeur de PodSecurityPolicy, supprimée en 1.25) dont le niveau restricted impose runAsNonRoot, allowPrivilegeEscalation: false, capabilities.drop: ["ALL"] et seccompProfile: RuntimeDefault.
  • Avec le Prometheus Operator, un ServiceMonitor n'est collecté que si son label correspond au sélecteur de Prometheus (release: kube-prometheus-stack) ; les alertes se déclarent en YAML via PrometheusRule — taux de 5xx supérieur à 5 % pendant 5 minutes, P99 au-delà de 2 secondes, plus de 3 redémarrages de pod en 1 heure.
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

Contenu Premium

Ce tutoriel avancé est réservé aux membres premium.

9,90€ / mois
  • Tous les tutoriels avancés
  • Nouveaux contenus chaque semaine
  • Suivi de progression
  • Annulation à tout moment
MR

Écrit par

Morgann Riu

Expert en cybersécurité et administration Linux. Je partage mes connaissances à travers des tutoriels gratuits et des formations pour aider les administrateurs systèmes et développeurs à sécuriser leurs infrastructures.

Questions fréquentes

Qu'est-ce qu'un Kubernetes Operator et quand faut-il en créer un ?
Un Kubernetes Operator est un contrôleur personnalisé qui étend l'API Kubernetes avec des Custom Resource Definitions (CRD) pour automatiser la gestion d'applications complexes (bases de données, messaging, etc.). Il encode la connaissance opérationnelle d'un expert humain : déploiement, scaling, sauvegarde, failover, mise à jour sans interruption. Il faut créer un Operator quand une application stateful nécessite une logique de gestion spécifique que les objets Kubernetes natifs (Deployment, StatefulSet) ne peuvent pas couvrir seuls. Pour des applications simples ou stateless, un Helm chart suffit généralement. Le seuil de déclenchement est la présence d'opérations opérationnelles complexes (failover automatique, réplication, backup planifié) qui devraient être automatisées.
Comment sécuriser des secrets dans un chart Helm sans les exposer dans le dépôt Git ?
Plusieurs solutions complémentaires existent. Helm Secrets (plugin basé sur Mozilla SOPS) chiffre les fichiers values avec une clé GPG, AWS KMS, GCP KMS ou Azure Key Vault : les fichiers chiffrés peuvent être commités en Git en toute sécurité. External Secrets Operator synchronise des secrets depuis AWS Secrets Manager, HashiCorp Vault ou Azure Key Vault vers des Kubernetes Secrets natifs. Sealed Secrets (Bitnami) permet de chiffrer des Secrets Kubernetes avec la clé publique du cluster et de les commiter. En GitOps avec ArgoCD, la recommandation est d'utiliser External Secrets Operator ou Vault Agent Injector : le dépôt Git ne contient jamais de secrets en clair, uniquement des références vers le gestionnaire de secrets externe.
Quelle différence entre ArgoCD et Flux CD pour le GitOps ?
ArgoCD et Flux sont les deux solutions GitOps de référence mais avec des philosophies différentes. ArgoCD est centré sur une interface graphique riche avec visualisation de l'état du cluster en temps réel, gestion multi-cluster depuis une interface centrale, et RBAC intégré. Il suit un modèle "pull avec UI de supervision". Flux est plus minimaliste, entièrement CLI-driven, et s'intègre nativement avec les outils CNCF comme SOPS, Kustomize et les image automation controllers. Il suit strictement le principe GitOps "Git comme source unique de vérité" sans état centralisé. En pratique : ArgoCD est préféré pour les équipes qui veulent une supervision visuelle et du multi-cluster. Flux est préféré pour les équipes platform qui veulent du GitOps pur sans interface graphique. Les deux supportent Helm, Kustomize et les CRD.
Comment fonctionnent les NetworkPolicies Kubernetes et pourquoi sont-elles indispensables ?
Par défaut, Kubernetes permet à tous les pods de communiquer entre eux sans restriction (modèle "tout est autorisé"). Les NetworkPolicies sont des règles de firewall au niveau pod qui définissent explicitement quels pods peuvent communiquer avec quels autres pods, sur quels ports, via quels protocoles. Elles nécessitent un CNI (Container Network Interface) qui les prend en charge : Calico, Cilium, Weave ou Antrea (Flannel ne les prend pas en charge). La bonne pratique est d'appliquer une politique "default deny" sur tous les namespaces puis d'ouvrir uniquement les flux nécessaires. Sans NetworkPolicies, un pod compromis peut latéralement accéder à tous les autres pods du cluster, y compris aux bases de données et aux services de secrets. Avec Cilium, les NetworkPolicies peuvent être étendues à la couche 7 (HTTP, DNS) avec des règles basées sur les méthodes HTTP et les chemins d'URL.
Qu'est-ce que le Pod Security Admission et comment remplace-t-il PodSecurityPolicy ?
PodSecurityPolicy (PSP) a été déprécié dans Kubernetes 1.21 et supprimé en 1.25. Son remplaçant natif est le Pod Security Admission (PSA), disponible en stable depuis Kubernetes 1.25. PSA définit trois niveaux de sécurité standardisés appliqués par namespace : privileged (aucune restriction), baseline (prévient les élévations de privilèges évidentes) et restricted (hardening complet selon les bonnes pratiques). L'application se fait via des labels sur les namespaces : enforce bloque les pods non conformes, warn les accepte avec un avertissement, audit les consigne dans les journaux. Pour des politiques plus granulaires et flexibles que n'offre PSA, OPA Gatekeeper ou Kyverno permettent de définir des politiques personnalisées via des CRD : interdire les images depuis des registries non approuvés, imposer des limites de ressources, forcer des labels spécifiques, etc.
Comment le Prometheus Operator simplifie-t-il le monitoring Kubernetes ?
Le Prometheus Operator introduit des CRD Kubernetes qui permettent de configurer Prometheus de manière déclarative, sans modifier directement ses fichiers de configuration. Les ServiceMonitor et PodMonitor définissent quels services superviser et comment : le Prometheus Operator traduit ces ressources en configuration Prometheus automatiquement. Quand un nouveau service est déployé avec les bons labels, il est automatiquement découvert et collecté sans intervention manuelle. PrometheusRule permet de définir des règles d'alerting en YAML Kubernetes qui sont automatiquement chargées. AlertmanagerConfig configure le routage des alertes par namespace. L'ensemble est déployé facilement via le kube-prometheus-stack Helm chart qui inclut Prometheus, Alertmanager, Grafana et une centaine de dashboards et d'alertes préconfigurées pour le cluster et les workloads Kubernetes.
Quel est le pattern App of Apps dans ArgoCD et pourquoi l'utiliser ?
L'App of Apps est un pattern ArgoCD où une Application ArgoCD "racine" gère elle-même un ensemble d'autres Applications ArgoCD. Le dépôt Git contient un chart Helm ou des manifestes Kustomize qui définissent les objets Application ArgoCD. Quand la racine se synchronise, elle crée automatiquement toutes les Applications enfants qui se synchronisent à leur tour. Ce pattern résout le problème du bootstrap : comment déployer ArgoCD et ses applications depuis Git sans problème d'amorçage circulaire. Il permet aussi de gérer des dizaines d'applications depuis un seul point d'entrée, d'appliquer des politiques de sync uniformes, et de structurer le cluster en tiers (infrastructure, platform, applications). L'alternative moderne recommandée par ArgoCD est ApplicationSet, qui offre plus de flexibilité avec des générateurs (Git, cluster, matrix) pour créer des Applications dynamiquement selon les dépôts, branches ou clusters disponibles.

Partager ce tutoriel

Cet article vous a plu ?

Cet article vous a-t-il été utile ?

Merci pour votre retour !

Commentaires

Recommandé pour vous

Article de fond sur le sujet

Checklist Sécurité Linux

30 points essentiels pour sécuriser un serveur Linux. Recevez aussi les nouveaux tutoriels par email.

Pas de spam. Désabonnement en 1 clic.