Vault Secrets Operator (VSO)는 Vault의 Secret을 Kubernetes Secret으로 동기화하는 공식 Kubernetes Operator입니다.
VSO는 Vault와 Kubernetes를 연결하는 다리 역할을 하며, 다음과 같은 기능을 제공합니다:
| 기능 | VSO | Vault Agent Injector |
|---|---|---|
| 배포 방식 | Operator | Sidecar/Init Container |
| Secret 저장 | Kubernetes Secret | 파일 시스템 |
| 동기화 | 자동 동기화 | Pod 재시작 필요 |
| Dynamic Secrets | ✅ 지원 | ✅ 지원 |
| 리소스 사용 | 낮음 (중앙 집중식) | 높음 (Pod마다 Sidecar) |
| 기존 앱 통합 | 쉬움 | 중간 |
[!TIP] VSO는 기존 애플리케이션을 수정하지 않고 Kubernetes Secret을 사용하는 방식으로 Vault를 도입할 수 있어 마이그레이션이 쉽습니다.
graph TB
subgraph "Kubernetes Cluster"
VSO[Vault Secrets<br/>Operator]
CRD1[VaultStaticSecret<br/>CR]
CRD2[VaultDynamicSecret<br/>CR]
K8sSecret[Kubernetes<br/>Secret]
Pod[Application<br/>Pod]
end
subgraph "Vault"
KV[KV Secret<br/>Engine]
DB[Database Secret<br/>Engine]
end
CRD1 --> VSO
CRD2 --> VSO
VSO -->|인증| Vault
VSO -->|Static Secret 읽기| KV
VSO -->|Dynamic Secret 생성| DB
VSO -->|생성/업데이트| K8sSecret
K8sSecret -->|마운트| Pod
VaultStaticSecret 또는 VaultDynamicSecret CR을 생성# Helm 저장소 추가 (이미 있다면 생략)
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
# VSO 설치
helm install vault-secrets-operator hashicorp/vault-secrets-operator \
--namespace vault-secrets-operator-system \
--create-namespace
# 설치 확인
kubectl get pods -n vault-secrets-operator-system
# 최신 릴리스 다운로드
kubectl apply -f https://github.com/hashicorp/vault-secrets-operator/releases/latest/download/vault-secrets-operator.yaml
# 설치 확인
kubectl get deployment -n vault-secrets-operator-system
VSO가 Vault에 연결하려면 VaultConnection 리소스를 생성해야 합니다.
# examples/vault-connection.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
name: default
namespace: default
spec:
# Vault 주소
address: http://vault.vault.svc.cluster.local:8200
# Skip TLS verification (개발 환경용)
skipTLSVerify: true
kubectl apply -f examples/vault-connection.yaml
VSO가 Vault에 인증하는 방법을 정의합니다.
# examples/vault-auth.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: default
namespace: default
spec:
# VaultConnection 참조
vaultConnectionRef: default
# Kubernetes 인증 방법 사용
method: kubernetes
mount: kubernetes
kubernetes:
role: myapp-role
serviceAccount: myapp-sa
kubectl apply -f examples/vault-auth.yaml
정적 Secret (KV Secret Engine)을 동기화합니다.
# examples/static-secret-example.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: myapp-database-secret
namespace: default
spec:
# VaultAuth 참조
vaultAuthRef: default
# Vault의 KV 경로
mount: secret
type: kv-v2
path: myapp/database
# 동기화할 Kubernetes Secret
destination:
name: myapp-database
create: true
# 갱신 간격 (기본값: 60초)
refreshAfter: 30s
kubectl apply -f examples/static-secret-example.yaml
# 생성된 Kubernetes Secret 확인
kubectl get secret myapp-database -o yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: myapp-api-keys
namespace: default
spec:
vaultAuthRef: default
mount: secret
type: kv-v2
path: myapp/api-keys
destination:
name: myapp-api-keys
create: true
# 특정 필드만 동기화
hmacSecretData: false
# 필드 변환
destination:
name: api-keys
create: true
transformation:
excludes:
- internal_key
includes:
- public_key
- private_key
apiVersion: v1
kind: Pod
metadata:
name: myapp
namespace: default
spec:
serviceAccountName: myapp-sa
containers:
- name: app
image: nginx:latest
env:
# 환경 변수로 사용
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: myapp-database
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-database
key: password
# 또는 볼륨으로 마운트
volumeMounts:
- name: db-creds
mountPath: /etc/secrets
readOnly: true
volumes:
- name: db-creds
secret:
secretName: myapp-database
동적 Secret (Database Secret Engine 등)을 관리합니다.
먼저 Vault에서 Database Secret Engine을 설정해야 합니다:
# Vault Pod에 접속
kubectl exec -n vault -it vault-0 -- sh
# Database Secret Engine 활성화
vault secrets enable database
# PostgreSQL 연결 설정
vault write database/config/my-postgres \
plugin_name=postgresql-database-plugin \
allowed_roles="myapp-db-role" \
connection_url="postgresql://:@postgres.default.svc.cluster.local:5432/mydb?sslmode=disable" \
username="vaultadmin" \
password="vaultpassword"
# Role 생성 (동적 자격 증명 정의)
vault write database/roles/myapp-db-role \
db_name=my-postgres \
creation_statements="CREATE ROLE \"\" WITH LOGIN PASSWORD '' VALID UNTIL ''; \
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"\";" \
default_ttl="1h" \
max_ttl="24h"
# 정책 생성
vault policy write myapp-db-policy - <<EOF
path "database/creds/myapp-db-role" {
capabilities = ["read"]
}
EOF
# Kubernetes Role 업데이트
vault write auth/kubernetes/role/myapp-role \
bound_service_account_names=myapp-sa \
bound_service_account_namespaces=default \
policies=myapp-policy,myapp-db-policy \
ttl=24h
# examples/dynamic-secret-example.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
name: myapp-db-creds
namespace: default
spec:
vaultAuthRef: default
# Database Secret Engine 경로
mount: database
path: creds/myapp-db-role
# 동기화할 Kubernetes Secret
destination:
name: myapp-db-dynamic
create: true
# Lease 설정
renewalPercent: 67 # TTL의 67%가 지나면 갱신
revoke: true # CR 삭제 시 Vault에서 Secret 폐기
# 롤백 설정
rolloutRestartTargets:
- kind: Deployment
name: myapp
주요 설정:
renewalPercent: Secret을 갱신할 시점 (TTL의 퍼센트)revoke: CR 삭제 시 Vault에서 Secret 폐기 여부rolloutRestartTargets: Secret 갱신 시 자동으로 재시작할 리소스kubectl apply -f examples/dynamic-secret-example.yaml
# 생성된 Secret 확인
kubectl get secret myapp-db-dynamic -o jsonpath='{.data.username}' | base64 -d
kubectl get secret myapp-db-dynamic -o jsonpath='{.data.password}' | base64 -d
# VaultDynamicSecret 상태 확인
kubectl describe vaultdynamicsecret myapp-db-creds
# Lease 정보 확인
kubectl get vaultdynamicsecret myapp-db-creds -o jsonpath='{.status.secretLease}'
# Secret 자동 갱신 모니터링
kubectl get secret myapp-db-dynamic -w
# postgres-deployment.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-init
data:
init.sql: |
CREATE USER vaultadmin WITH PASSWORD 'vaultpassword' SUPERUSER;
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
env:
- name: POSTGRES_DB
value: mydb
- name: POSTGRES_PASSWORD
value: rootpassword
ports:
- containerPort: 5432
volumeMounts:
- name: init-script
mountPath: /docker-entrypoint-initdb.d
volumes:
- name: init-script
configMap:
name: postgres-init
---
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
kubectl apply -f postgres-deployment.yaml
kubectl apply -f examples/vault-connection.yaml
kubectl apply -f examples/vault-auth.yaml
kubectl apply -f examples/dynamic-secret-example.yaml
# myapp-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
serviceAccountName: myapp-sa
containers:
- name: app
image: postgres:15-alpine
command: ['sh', '-c', 'while true; do psql postgresql://$DB_USERNAME:$DB_PASSWORD@postgres:5432/mydb -c "SELECT version();"; sleep 60; done']
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: myapp-db-dynamic
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-db-dynamic
key: password
kubectl apply -f myapp-deployment.yaml
# 로그에서 데이터베이스 연결 확인
kubectl logs -f deployment/myapp
# 현재 username 확인
kubectl get secret myapp-db-dynamic -o jsonpath='{.data.username}' | base64 -d
# 1시간 후 (또는 설정한 renewalPercent 시점) username이 변경되는지 확인
# VSO가 자동으로 Secret을 갱신하고 Pod를 재시작합니다
# VSO Operator 로그 확인
kubectl logs -n vault-secrets-operator-system deployment/vault-secrets-operator-controller-manager
# VaultConnection 상태 확인
kubectl describe vaultconnection default
# VaultAuth 상태 확인
kubectl describe vaultauth default
# VaultStaticSecret 또는 VaultDynamicSecret 상태 확인
kubectl describe vaultstaticsecret myapp-database-secret
# ServiceAccount 확인
kubectl get serviceaccount myapp-sa
# Vault에서 Role 확인
kubectl exec -n vault vault-0 -- vault read auth/kubernetes/role/myapp-role
# Policy 확인
kubectl exec -n vault vault-0 -- vault policy read myapp-policy
# Lease 정보 확인
kubectl get vaultdynamicsecret myapp-db-creds -o yaml
# Vault에서 Lease 확인
kubectl exec -n vault vault-0 -- vault list sys/leases/lookup/database/creds/myapp-db-role
환경별로 Namespace를 분리하고 각각 별도의 VaultAuth를 사용하세요:
# dev namespace
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: default
namespace: dev
spec:
kubernetes:
role: dev-role
serviceAccount: dev-sa
각 애플리케이션에 필요한 최소한의 Secret만 접근할 수 있도록 정책을 설정하세요.
Dynamic Secrets를 사용하여 자격 증명을 자동으로 순환하세요.
VSO 메트릭을 모니터링하여 동기화 실패를 감지하세요:
# Prometheus 메트릭 확인
kubectl port-forward -n vault-secrets-operator-system svc/vault-secrets-operator-controller-manager-metrics-service 8443:8443
curl -k https://localhost:8443/metrics
VSO를 통해 Vault와 Kubernetes를 성공적으로 통합했습니다. 다음 문서로 진행하세요: