I’ll never forget the panic of a 3 AM page for a Vault outage, fumbling for my unseal keys. Traditional Vault setups require a quorum of operators to manually unseal Vault after every restart. It’s a tedious and insecure process. That’s why I moved to an auto-unseal workflow using Google Cloud KMS. It completely automates the unsealing process, making my Vault deployment much more resilient. Here’s my guide to setting it up in Kubernetes.
My Architecture: Raft + KMS
My architecture for Vault on Kubernetes is designed for simplicity and high availability. I use Vault’s integrated Raft storage backend, which means I don’t need to manage a separate Consul cluster. For high availability, I run multiple Vault replicas. The magic is the GCP KMS Auto-Unseal: instead of splitting the master key among operators, Vault encrypts it with a key from GCP KMS and stores it in its own storage. On startup, Vault authenticates to GCP, decrypts its master key, and unseals itself automatically.
graph TB
subgraph GCP
KMS[GCP KMS<br/>Encryption Key]
end
subgraph Kubernetes Cluster
Vault0[Vault-0<br/>Leader]
Vault1[Vault-1<br/>Follower]
Vault0 -.->|Raft Replication| Vault1
end
Vault0 -->|Encrypt/Decrypt<br/>Master Key| KMS
Vault1 -->|Encrypt/Decrypt<br/>Master Key| KMS
Users[Users] --> LB[LoadBalancer]
LB --> Vault0
LB --> Vault1
style KMS fill:#4285f4,color:#fff
style Vault0 fill:#7c4dff,color:#fff
My Setup Process
Step 1: Create the GCP KMS Key and Service Account
First, I set up the GCP side. I create a KMS key ring and a crypto key that Vault will use to encrypt its master key.
gcloud kms keyrings create vault-unseal-kr --location=global
gcloud kms keys create vault-unseal-key --location=global --keyring=vault-unseal-kr --purpose=encryption
Next, I create a dedicated GCP Service Account. This is crucial. I grant this service account the roles/cloudkms.cryptoKeyEncrypterDecrypter role on the key I just created. This is the only permission it needs.
gcloud iam service-accounts create vault-kms-sa
gcloud kms keys add-iam-policy-binding vault-unseal-key ... --member="serviceAccount:vault-kms-sa@..."
Step 2: Create the Kubernetes Secret
I then download the JSON key for this service account and create a Kubernetes secret from it in my vault namespace. This is how the Vault pods will authenticate to GCP.
gcloud iam service-accounts keys create vault-kms-key.json --iam-account=vault-kms-sa@...
kubectl create namespace vault
kubectl create secret generic kms-vault-creds -n vault --from-file=kms-creds.json=./vault-kms-key.json
Step 3: Install Vault with Helm
Now for the main event: installing Vault with Helm. I use the official HashiCorp chart and a custom values-vault.yaml file. In this file, I configure the ha.raft backend, mount the GCP credentials secret as a volume, and most importantly, I add the seal "gcpckms" block, pointing it to my GCP project and KMS key.
# values-vault.yaml
server:
ha:
enabled: true
replicas: 2
raft:
enabled: true
config: |
ui = true
listener "tcp" {
tls_disable = 1
address = "[::]:8200"
cluster_address = "[::]:8201"
}
storage "raft" {
path = "/vault/data"
}
seal "gcpckms" {
project = "your-gcp-project-id"
region = "global"
key_ring = "vault-unseal-kr-production"
crypto_key = "vault-unseal-key-production"
}
# Mount the GCP credentials secret
extraEnvironmentVars:
GOOGLE_APPLICATION_CREDENTIALS: "/vault/userconfig/kms-vault-creds/kms-creds.json"
extraVolumes:
- type: secret
name: kms-vault-creds
I install the chart with helm upgrade -i vault hashicorp/vault -n vault -f values-vault.yaml.
Step 4: Initialize and Join the Cluster
After installing, I exec into the first pod (vault-0) and run vault operator init. This is the only time I have to deal with keys. I immediately save the initial root token and the recovery keys (which are different from unseal keys) in a secure location.
Then, I join the other Vault pods to the Raft cluster. I exec into vault-1 and run vault operator raft join http://vault-0.vault-internal:8200.
Step 5: Verify Auto-Unseal
The moment of truth is testing the auto-unseal. I just delete one of the Vault pods. As it comes back up, I can check its status and see Sealed: false without me having to do anything. It’s a beautiful thing. I also test HA by deleting the leader pod and watching the other pod take over leadership within seconds.
My Final Thoughts
My key takeaway is that combining Raft storage with GCP KMS auto-unseal is the simplest, most robust way to run HA Vault on Kubernetes. It eliminates the operational burden of both managing a separate storage backend and manually unsealing Vault. My production setup always includes at least 3 replicas, resource limits, audit logging, and a CronJob that regularly takes Raft snapshots and uploads them to GCS for disaster recovery. This setup has made our secrets management infrastructure both highly available and much easier to manage.
Related Articles
- GCP KMS Setup for Vault Auto-Unseal - Detailed guide on creating KMS resources
- Vault Operations Guide - Managing policies, secrets, and users
- Vault CRD: Kubernetes Secret Sync - Automate secret synchronization