Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ helm/argo-stack/charts/external-secrets-*.tgz
.coverage
htmlcov/
.pytest_cache/

# Vault cluster keys created from `make vault-init`
secrets/
cluster-keys.json
19 changes: 13 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ show-limits:


kind:
kind delete cluster || true
# kind delete cluster || true
envsubst < kind-config.yaml | kind create cluster --config -

minio:
Expand Down Expand Up @@ -385,19 +385,25 @@ help:
# Vault Development Targets (Helm-based in-cluster deployment)
# ============================================================================

vault-init:
@echo "🌱 Initializing Vault..."
@mkdir -p secrets
@kubectl exec -n vault vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > secrets/cluster-keys.json
@echo "🔓 Unsealing Vault..."
@kubectl exec -n vault vault-0 -- vault operator unseal $$(jq -r ".unseal_keys_b64[0]" secrets/cluster-keys.json)
@echo "🔑 Root Token: $$(jq -r ".root_token" cluster-keys.json)"

vault-dev:
@echo "🔐 Installing Vault dev server in Kubernetes cluster..."
@helm repo add hashicorp https://helm.releases.hashicorp.com 2>/dev/null || true
@helm repo update hashicorp

@kubectl create namespace vault 2>/dev/null || true
@helm upgrade --install vault hashicorp/vault \
--namespace vault \
--set server.dev.enabled=true \
--set server.dev.devRootToken=$(VAULT_TOKEN) \
--set injector.enabled=false \
--set ui.enabled=true \
--set server.dataStorage.enabled=false \
--values vault/values.yaml \
--wait --timeout 2m

@echo "⏳ Waiting for Vault to be ready..."
@kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=vault -n vault --timeout=120s
@echo "✅ Vault dev server running in cluster"
Expand Down Expand Up @@ -526,6 +532,7 @@ vault-auth:
ttl=1h
@kubectl exec -n vault vault-0 -- vault read auth/kubernetes/role/argo-stack
@echo "✅ Service account to Vault dev server added"

vault-shell:
@echo "🐚 Opening shell in Vault pod..."
@kubectl exec -it -n vault vault-0 -- /bin/sh
Expand Down
48 changes: 48 additions & 0 deletions docs/persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Persistence

> [!IMPORTANT]
> This deployment uses **Vault Integrated Storage (Raft)** with Kubernetes PersistentVolumeClaims (PVCs)
> to persist Vault data across pod restarts and node failures. Read this document carefully before
> enabling persistence in a shared or production cluster.

## 1. How Vault persistence works with Raft integrated storage

This setup runs Vault using [Integrated Storage](https://developer.hashicorp.com/vault/docs/configuration/storage/raft),
backed by persistent volumes:

- Each Vault server pod in the StatefulSet mounts a **PersistentVolumeClaim**.
- The Raft storage backend writes:
- The encrypted Vault data (key/value secrets, auth backends, leases, etc.).
- Raft logs and snapshots used for replication and recovery.
- As long as the underlying PVCs remain intact, Vault’s data survives:
- Pod restarts
- Node drains or re-scheduling
- Helm upgrades of the chart

In Raft mode:

- One Vault node is the **leader**, handling reads and writes.
- Other nodes are **followers**, replicating the Raft log.
- If the leader fails, Raft elects a new leader from the remaining healthy nodes.

The persistence behavior therefore depends on the lifecycle of the **PVCs**:

- If pods are deleted but PVCs remain, **all Vault data is preserved**.
- If PVCs are deleted or the underlying storage is recreated from scratch, Vault behaves like a **new,
empty cluster** and must be re-initialized.

## 2. Initializing and unsealing Vault with persistent storage

The first time you deploy Vault with persistent storage, the Raft backend is empty and Vault starts in a
**sealed** and **uninitialized** state.

### 2.1 One-time initialization

Run these steps **once per new Raft storage** (per new set of PVCs). Do **not** re-run `vault operator init`
against an already-initialized storage backend.

1. Port-forward or otherwise access the active Vault pod:

```bash
kubectl port-forward -n <namespace> \
svc/<vault-service-name> 8200:8200
38 changes: 38 additions & 0 deletions vault/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
injector:
enabled: false

ui:
enabled: true

server:
dev:
enabled: false

ha:
enabled: true
replicas: 3

raft:
enabled: true
setNodeId: true
config: |
ui = true
listener "tcp" {
# TLS is disabled here to simplify local development and testing only.
# Do NOT use this configuration as-is in production environments.
# For production, you should enable TLS by setting `tls_disable = 0`
# and configuring `tls_cert_file` and `tls_key_file` (and related options)
# as documented in the Vault listener TCP configuration:
# https://developer.hashicorp.com/vault/docs/configuration/listener/tcp
tls_disable = 1
address = "[::]:8200"
cluster_address = "[::]:8201"
}
storage "raft" {
path = "/vault/data"
}

dataStorage:
enabled: true
size: 10Gi
mountPath: "/vault/data"
File renamed without changes.
File renamed without changes.