HashiCorp Vault sur Kubernetes : guide complet d’installation en 2026

Guide installation · HA · 2026

HashiCorp Vault sur Kubernetes : guide complet d'installation en 2026

HashiCorp Vault est l’outil de référence pour la gestion des secrets dans les architectures Cloud Native. Couplé à Kubernetes, il permet de centraliser les mots de passe, certificats, clés API et tokens, et de les distribuer aux applications de façon dynamique, auditée et sécurisée. Ce guide complet vous montre, étape par étape, comment installer Vault en haute disponibilité sur Kubernetes en 2026 — avec backend Raft intégré, TLS, intégration native via le Vault Agent Injector, backups vers S3 et bonnes pratiques opérationnelles.

Temps de lecture estimé : 14 minutes — public cible : administrateurs Kubernetes / DevOps souhaitant déployer Vault en production.

Pourquoi HashiCorp Vault sur Kubernetes ?

Les Secrets Kubernetes natifs sont chiffrés au repos (depuis qu’on a activé encryption-config.yaml sur l’API server, ce qui n’est pas le cas par défaut sur tous les clusters) : c’est insuffisant en production. Vault apporte :

  • une centralisation des secrets pour Kubernetes et le reste du SI (CI/CD, applications legacy, bases de données) ;
  • la rotation dynamique des secrets (mots de passe DB générés à la volée, certificats PKI à courte durée de vie) ;
  • un audit log complet de tous les accès ;
  • un RBAC riche via les policies HCL ;
  • l’intégration native Kubernetes via auth method kubernetes, Vault Agent Injector et CSI Driver.

D’expérience, déployer Vault c’est aussi fixer une norme : les développeurs cessent de commiter des .env dans Git, et toute application reçoit ses secrets par injection au démarrage.

Prérequis

  • Cluster Kubernetes 1.28+ ou OpenShift 4.14+ avec 3 nœuds workers minimum (pour la HA Raft).
  • Helm 3.14+ installé localement.
  • Une StorageClass dynamique (NFS, NetApp, Longhorn, EBS, Ceph…) supportant des PVC ReadWriteOnce.
  • Un cert-manager ou un cluster Issuer pour générer les certificats TLS (sinon on auto-signe).
  • Un bucket S3 (ou compatible — MinIO, Scaleway Object Storage) pour les snapshots Raft.
  • La CLI vault (≥ 1.18) installée sur votre poste.

Architecture cible

L’architecture recommandée en production tient en quatre points :

  • 3 réplicas Vault distribués sur 3 nœuds (anti-affinity) pour tolérer la perte d’un nœud.
  • Backend Raft intégré (Integrated Storage) : pas besoin de Consul, le quorum est géré par Vault lui-même.
  • TLS bout-en-bout entre les pods Vault, géré par cert-manager.
  • Auto-unseal via une clé KMS (AWS KMS, GCP KMS) ou un Vault de transit, pour éviter le déverrouillage manuel après chaque redémarrage.
┌─────────────────────────────────────────────────────────────┐
    │                Cluster Kubernetes (3 workers)              │
    │                                                            │
    │   ┌─────────┐    ┌─────────┐    ┌─────────┐                │
    │   │ vault-0 │◄──►│ vault-1 │◄──►│ vault-2 │  (Raft quorum) │
    │   └────┬────┘    └────┬────┘    └────┬────┘                │
    │        │              │              │                     │
    │        ▼              ▼              ▼                     │
    │   ┌──────────────────────────────────────┐                 │
    │   │   PVC (StorageClass dynamique)       │                 │
    │   └──────────────────────────────────────┘                 │
    │                                                            │
    │   Vault Agent Injector  ──►  injecte secrets dans pods app │
    └─────────────────────────────────────────────────────────────┘
                      │
                      ▼  snapshots Raft (cron quotidien)
                ┌──────────┐
                │ Bucket S3│
                └──────────┘

Installation pas à pas avec Helm

1. Préparer le namespace

kubectl create namespace vault
    kubectl label namespace vault pod-security.kubernetes.io/enforce=restricted

    helm repo add hashicorp https://helm.releases.hashicorp.com
    helm repo update

2. Générer les certificats TLS avec cert-manager

# vault-tls.yaml
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: vault-tls
      namespace: vault
    spec:
      secretName: vault-tls
      duration: 8760h          # 1 an
      renewBefore: 720h         # renouvelle 30 j avant expiration
      commonName: vault.vault.svc.cluster.local
      dnsNames:
        - vault
        - vault.vault
        - vault.vault.svc
        - vault.vault.svc.cluster.local
        - "*.vault-internal.vault.svc.cluster.local"
      issuerRef:
        name: vault-ca-issuer
        kind: ClusterIssuer
kubectl apply -f vault-tls.yaml
    kubectl -n vault get secret vault-tls   # NAME: vault-tls TYPE: kubernetes.io/tls

3. Préparer le values.yaml Vault

# values-vault.yaml
    global:
      enabled: true
      tlsDisable: false

    injector:
      enabled: true
      replicas: 2
      resources:
        requests: { cpu: 50m, memory: 64Mi }
        limits:   { cpu: 250m, memory: 256Mi }

    server:
      image:
        repository: hashicorp/vault
        tag: "1.18.3"

      resources:
        requests: { cpu: 250m, memory: 512Mi }
        limits:   { cpu: "1",  memory: "1Gi" }

      ha:
        enabled: true
        replicas: 3
        raft:
          enabled: true
          setNodeId: true
          config: |
            ui = true
            listener "tcp" {
              tls_disable     = false
              address         = "[::]:8200"
              cluster_address = "[::]:8201"
              tls_cert_file   = "/vault/userconfig/vault-tls/tls.crt"
              tls_key_file    = "/vault/userconfig/vault-tls/tls.key"
              tls_client_ca_file = "/vault/userconfig/vault-tls/ca.crt"
            }
            storage "raft" {
              path = "/vault/data"
            }
            service_registration "kubernetes" {}
            seal "awskms" {
              region     = "eu-west-3"
              kms_key_id = "alias/vault-unseal"
            }

      extraEnvironmentVars:
        VAULT_CACERT: /vault/userconfig/vault-tls/ca.crt

      volumes:
        - name: vault-tls
          secret:
            secretName: vault-tls
      volumeMounts:
        - name: vault-tls
          mountPath: /vault/userconfig/vault-tls
          readOnly: true

      dataStorage:
        enabled: true
        size: 20Gi
        storageClass: "standard-rwo"

      affinity: |
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app.kubernetes.io/name: {{ template "vault.name" . }}
                  app.kubernetes.io/instance: "{{ .Release.Name }}"
              topologyKey: kubernetes.io/hostname

    ui:
      enabled: true
      serviceType: ClusterIP

Quelques points clés :

  • Auto-unseal AWS KMS : si vous n’avez pas KMS, retirez la stanza seal et déverrouillez manuellement (cf. plus bas).
  • Anti-affinity : indispensable pour ne pas se retrouver avec les 3 réplicas sur le même nœud.
  • Resources : ces valeurs sont un plancher. À ajuster sous charge.

4. Installer Vault

helm install vault hashicorp/vault \
      -n vault \
      -f values-vault.yaml

    # Vérifier
    kubectl -n vault get pods -w
    # vault-0  0/1 Running   (sealed)
    # vault-1  0/1 Running   (sealed)
    # vault-2  0/1 Running   (sealed)
    # vault-agent-injector-xxx  1/1 Running

Les pods Vault sont en 0/1 Running tant que le cluster n’a pas été initialisé : c’est normal.

Initialisation et déverrouillage du cluster

# Initialiser sur le pod vault-0
    kubectl -n vault exec -it vault-0 -- vault operator init \
      -key-shares=5 -key-threshold=3 -format=json > vault-init.json

    # vault-init.json contient les 5 unseal keys et le root token
    # →→→ STOCKEZ-LE DANS UN COFFRE OFFLINE OU UN AUTRE VAULT ←←←

    # Si pas d'auto-unseal KMS, déverrouiller manuellement (3 clés sur 5)
    for K in $(jq -r '.unseal_keys_b64[0,1,2]' vault-init.json); do
      kubectl -n vault exec vault-0 -- vault operator unseal "$K"
    done

    # Joindre vault-1 et vault-2 au cluster Raft
    for POD in vault-1 vault-2; do
      kubectl -n vault exec $POD -- \
        vault operator raft join \
          -address=https://$POD.vault-internal:8200 \
          -leader-ca-cert="$(cat ca.crt)" \
          https://vault-0.vault-internal:8200
      for K in $(jq -r '.unseal_keys_b64[0,1,2]' vault-init.json); do
        kubectl -n vault exec $POD -- vault operator unseal "$K"
      done
    done

    # Vérifier le quorum
    kubectl -n vault exec vault-0 -- vault operator raft list-peers
    # 3 nœuds attendus, l'un d'entre eux en leader

Vous pouvez maintenant ouvrir l’UI : kubectl -n vault port-forward svc/vault 8200:8200 puis https://localhost:8200, et vous connecter avec le root token de vault-init.json.

Configurer l’authentification Kubernetes

L’objectif : permettre à un pod (via son ServiceAccount) de s’authentifier auprès de Vault sans mot de passe partagé.

# Activer la méthode kubernetes
    vault auth enable kubernetes

    # Configurer Vault pour valider les tokens auprès de l'API server
    TOKEN_REVIEW_JWT=$(kubectl -n vault create token vault \
      --duration=8760h --audience=https://kubernetes.default.svc)

    vault write auth/kubernetes/config \
      token_reviewer_jwt="$TOKEN_REVIEW_JWT" \
      kubernetes_host="https://kubernetes.default.svc" \
      kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
      disable_iss_validation=true

    # Créer un secret KV pour l'application "demo"
    vault secrets enable -path=secret kv-v2
    vault kv put secret/demo/config api_token="changeme123"

    # Policy donnant accès en lecture à secret/demo/*
    vault policy write demo-read - <<EOF
    path "secret/data/demo/*" {
      capabilities = ["read"]
    }
    EOF

    # Mapper ServiceAccount → policy
    vault write auth/kubernetes/role/demo \
      bound_service_account_names=demo \
      bound_service_account_namespaces=demo \
      policies=demo-read \
      ttl=24h

Injecter des secrets dans un pod (Vault Agent Injector)

# demo-app.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: demo
      namespace: demo
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: demo
      namespace: demo
    spec:
      replicas: 1
      selector:
        matchLabels: { app: demo }
      template:
        metadata:
          labels: { app: demo }
          annotations:
            vault.hashicorp.com/agent-inject: "true"
            vault.hashicorp.com/role: "demo"
            vault.hashicorp.com/agent-inject-secret-config.env: "secret/data/demo/config"
            vault.hashicorp.com/agent-inject-template-config.env: |
              {{- with secret "secret/data/demo/config" -}}
              API_TOKEN={{ .Data.data.api_token }}
              {{- end -}}
        spec:
          serviceAccountName: demo
          containers:
            - name: app
              image: ghcr.io/example/demo:1.0
              command: ["/bin/sh", "-c", "source /vault/secrets/config.env && exec /app"]

L’injecteur démarre un sidecar vault-agent, s’authentifie auprès de Vault via le token du ServiceAccount, récupère le secret et le matérialise dans /vault/secrets/config.env. Pas de variable secrète dans le manifeste, pas de kubectl create secret à faire.

Alternative : le Secrets Store CSI Driver

Le Secrets Store CSI Driver est plus moderne : il monte les secrets sous forme de volumes (et optionnellement les synchronise comme Secret Kubernetes). Plus de sidecar, mais une dépendance au CSI Driver et au Vault Provider.

helm repo add secrets-store-csi-driver \
      https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
    helm install csi -n kube-system secrets-store-csi-driver/secrets-store-csi-driver \
      --set syncSecret.enabled=true

    helm install vault hashicorp/vault \
      -n vault --reuse-values \
      --set csi.enabled=true

Le CSI Driver est particulièrement adapté quand vos applications attendent un fichier monté (TLS cert, kubeconfig, Java keystore) plutôt qu’une variable d’environnement.

Sauvegarde et restauration

Vault Raft offre un mécanisme de snapshot natif. La bonne pratique : un cron quotidien qui pousse le snapshot vers S3.

apiVersion: batch/v1
    kind: CronJob
    metadata:
      name: vault-snapshot
      namespace: vault
    spec:
      schedule: "0 2 * * *"
      jobTemplate:
        spec:
          template:
            spec:
              restartPolicy: OnFailure
              serviceAccountName: vault-snapshot
              containers:
                - name: snapshot
                  image: hashicorp/vault:1.18.3
                  env:
                    - { name: VAULT_ADDR,  value: https://vault.vault.svc:8200 }
                    - { name: VAULT_CACERT, value: /tls/ca.crt }
                    - name: VAULT_TOKEN
                      valueFrom: { secretKeyRef: { name: vault-snapshot-token, key: token } }
                  command:
                    - /bin/sh
                    - -c
                    - |
                      vault operator raft snapshot save /tmp/snap.db
                      aws s3 cp /tmp/snap.db s3://my-bucket/vault/$(date +%Y%m%d).db
                  volumeMounts:
                    - { name: tls, mountPath: /tls, readOnly: true }
              volumes:
                - { name: tls, secret: { secretName: vault-tls } }

Pour restaurer, c’est l’inverse :

kubectl -n vault cp snap.db vault-0:/tmp/snap.db
    kubectl -n vault exec vault-0 -- vault operator raft snapshot restore /tmp/snap.db

Testez vos restaurations au moins une fois par trimestre sur un cluster de pré-production. Un backup non testé n’est pas un backup.

Et OpenBao, l’alternative open source ?

Depuis la bascule de Vault sous licence Business Source License (BSL) en 2023, la Linux Foundation a forké le projet sous le nom OpenBao. La 1.0 est sortie en 2024 et le projet est compatible quasi 1-pour-1 avec Vault.

  • Le chart Helm est openbao/openbao.
  • Les commandes CLI sont identiques : bao est un alias de vault.
  • Le format des policies, les auth methods et les secrets engines sont les mêmes.

OpenBao est un excellent choix si vous voulez rester en 100% open source ou éviter la BSL. C’est la voie que je recommande aujourd’hui pour la majorité des nouvelles installations chez mes clients qui n’ont pas de souscription HashiCorp existante.

FAQ

Faut-il vraiment 3 réplicas Vault ?

Pour le quorum Raft, oui. Avec 1 réplica, vous perdez Vault dès que le pod redémarre. Avec 3, vous tolérez la perte d’un pod ou d’un nœud sans interruption.

Vault Enterprise ou Vault Community ?

La version Community couvre la plupart des besoins (auth methods, KV, PKI, audit). Vault Enterprise apporte la réplication multi-datacenter (DR + Performance), les namespaces et le HSM. Pour un site unique, la version Community suffit.

Comment auditer les accès aux secrets ?

Activez un audit device Vault, par exemple vault audit enable file file_path=/vault/audit/log ou vault audit enable socket address=splunk:9000 socket_type=tcp. Tous les appels API y sont tracés (et les valeurs sont hashées).

Combien de temps pour mettre Vault en production ?

D’expérience, comptez 2 à 3 jours pour un déploiement HA propre (TLS, auto-unseal, monitoring, backup), puis 1 à 2 semaines pour migrer les premières applications. La gouvernance des policies et la formation des équipes prennent souvent plus de temps que l’install elle-même.


Besoin d’aide pour déployer Vault chez vous ?

Architecture HA, intégration Kubernetes, PKI interne, formation des équipes — c’est l’un de mes domaines de prédilection. Décrivez-moi votre contexte et je vous propose un plan d’attaque sous 24 heures.

Pour aller plus loin : Kubernetes vs OpenShift, quelle différence en 2026 ?

Laisser un commentaire

Your email address will not be published. Required fields are marked *

Retour en haut