- Streamlined README focused on quick start - Complete examples for all major use cases - Decision tree for choosing right pattern - Comprehensive troubleshooting guide
572 lines
16 KiB
YAML
572 lines
16 KiB
YAML
# Example 6: Comprehensive Multi-Service Deployment
|
|
#
|
|
# This example demonstrates a complete application stack with:
|
|
# - PostgreSQL (rotating with previous version)
|
|
# - Redis (rotating without previous version)
|
|
# - External API key (no rotation)
|
|
# - TLS certificates (no rotation)
|
|
# - Application credentials (rotating with Reloader)
|
|
#
|
|
# Shows different rotation strategies working together
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: myapp
|
|
|
|
---
|
|
# ============================================================================
|
|
# 1. PostgreSQL Credentials - Rotates with previous version support
|
|
# ============================================================================
|
|
apiVersion: secrets.c5ai.ch/v1alpha1
|
|
kind: ManagedSecret
|
|
metadata:
|
|
name: postgres-credentials
|
|
namespace: myapp
|
|
spec:
|
|
vault:
|
|
address: "http://openbao.openbao.svc.cluster.local:8200"
|
|
authMethod: kubernetes
|
|
role: managedsecret-operator
|
|
kvVersion: v2
|
|
mount: secret
|
|
path: myapp/postgres
|
|
fields:
|
|
- name: username
|
|
type: static
|
|
value: "myapp_user"
|
|
- name: password
|
|
type: generated
|
|
generator:
|
|
type: password
|
|
length: 32
|
|
minDigits: 6
|
|
minSymbols: 6
|
|
- name: host
|
|
type: static
|
|
value: "postgres.database.svc.cluster.local"
|
|
- name: port
|
|
type: static
|
|
value: "5432"
|
|
- name: database
|
|
type: static
|
|
value: "myapp_db"
|
|
destination:
|
|
name: postgres-creds
|
|
type: Opaque
|
|
keepPreviousVersion: true # Needs old password for rotation
|
|
previousVersionTTL: 1h
|
|
rotation:
|
|
enabled: true
|
|
schedule: 2160h # 90 days
|
|
|
|
---
|
|
# ============================================================================
|
|
# 2. Redis Password - Rotates without previous version (CONFIG SET)
|
|
# ============================================================================
|
|
apiVersion: secrets.c5ai.ch/v1alpha1
|
|
kind: ManagedSecret
|
|
metadata:
|
|
name: redis-credentials
|
|
namespace: myapp
|
|
spec:
|
|
vault:
|
|
address: "http://openbao.openbao.svc.cluster.local:8200"
|
|
authMethod: kubernetes
|
|
role: managedsecret-operator
|
|
kvVersion: v2
|
|
mount: secret
|
|
path: myapp/redis
|
|
fields:
|
|
- name: password
|
|
type: generated
|
|
generator:
|
|
type: password
|
|
length: 40
|
|
minDigits: 8
|
|
minSymbols: 0 # Redis doesn't like special chars
|
|
symbolCharacters: ""
|
|
- name: host
|
|
type: static
|
|
value: "redis.myapp.svc.cluster.local"
|
|
- name: port
|
|
type: static
|
|
value: "6379"
|
|
destination:
|
|
name: redis-creds
|
|
type: Opaque
|
|
# No previous version needed - Redis CONFIG SET doesn't require auth
|
|
rotation:
|
|
enabled: true
|
|
schedule: 720h # 30 days
|
|
|
|
---
|
|
# ============================================================================
|
|
# 3. External API Keys - No rotation
|
|
# ============================================================================
|
|
apiVersion: secrets.c5ai.ch/v1alpha1
|
|
kind: ManagedSecret
|
|
metadata:
|
|
name: external-api-keys
|
|
namespace: myapp
|
|
spec:
|
|
vault:
|
|
address: "http://openbao.openbao.svc.cluster.local:8200"
|
|
authMethod: kubernetes
|
|
role: managedsecret-operator
|
|
kvVersion: v2
|
|
mount: secret
|
|
path: myapp/api-keys
|
|
fields:
|
|
- name: stripe-api-key
|
|
type: generated
|
|
generator:
|
|
type: password
|
|
length: 64
|
|
minDigits: 0
|
|
minSymbols: 0
|
|
- name: sendgrid-api-key
|
|
type: generated
|
|
generator:
|
|
type: password
|
|
length: 64
|
|
minDigits: 0
|
|
minSymbols: 0
|
|
destination:
|
|
name: api-keys
|
|
type: Opaque
|
|
rotation:
|
|
enabled: false # Managed externally
|
|
|
|
---
|
|
# ============================================================================
|
|
# 4. TLS Certificate - No rotation (managed by cert-manager)
|
|
# ============================================================================
|
|
apiVersion: secrets.c5ai.ch/v1alpha1
|
|
kind: ManagedSecret
|
|
metadata:
|
|
name: app-tls-cert
|
|
namespace: myapp
|
|
spec:
|
|
vault:
|
|
address: "http://openbao.openbao.svc.cluster.local:8200"
|
|
authMethod: kubernetes
|
|
role: managedsecret-operator
|
|
kvVersion: v2
|
|
mount: secret
|
|
path: myapp/tls
|
|
fields:
|
|
- name: tls.crt
|
|
type: static
|
|
value: |
|
|
-----BEGIN CERTIFICATE-----
|
|
# Your certificate here
|
|
-----END CERTIFICATE-----
|
|
- name: tls.key
|
|
type: static
|
|
value: |
|
|
-----BEGIN PRIVATE KEY-----
|
|
# Your private key here
|
|
-----END PRIVATE KEY-----
|
|
destination:
|
|
name: app-tls
|
|
type: kubernetes.io/tls
|
|
rotation:
|
|
enabled: false
|
|
|
|
---
|
|
# ============================================================================
|
|
# 5. Application JWT Secret - Rotates with Reloader
|
|
# ============================================================================
|
|
apiVersion: secrets.c5ai.ch/v1alpha1
|
|
kind: ManagedSecret
|
|
metadata:
|
|
name: app-jwt-secret
|
|
namespace: myapp
|
|
spec:
|
|
vault:
|
|
address: "http://openbao.openbao.svc.cluster.local:8200"
|
|
authMethod: kubernetes
|
|
role: managedsecret-operator
|
|
kvVersion: v2
|
|
mount: secret
|
|
path: myapp/jwt
|
|
fields:
|
|
- name: jwt-secret
|
|
type: generated
|
|
generator:
|
|
type: password
|
|
length: 64
|
|
minDigits: 16
|
|
minSymbols: 0
|
|
symbolCharacters: ""
|
|
destination:
|
|
name: jwt-secret
|
|
type: Opaque
|
|
rotation:
|
|
enabled: true
|
|
schedule: 4320h # 180 days
|
|
|
|
---
|
|
# ============================================================================
|
|
# PostgreSQL Password Rotation Job
|
|
# ============================================================================
|
|
apiVersion: batch/v1
|
|
kind: CronJob
|
|
metadata:
|
|
name: postgres-password-rotator
|
|
namespace: myapp
|
|
spec:
|
|
schedule: "*/5 * * * *"
|
|
jobTemplate:
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: OnFailure
|
|
containers:
|
|
- name: rotate
|
|
image: postgres:15-alpine
|
|
env:
|
|
- name: PGUSER
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres-creds
|
|
key: username
|
|
- name: NEW_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres-creds
|
|
key: password
|
|
- name: OLD_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres-creds-previous
|
|
key: password
|
|
optional: true
|
|
- name: PGHOST
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres-creds
|
|
key: host
|
|
command:
|
|
- sh
|
|
- -c
|
|
- |
|
|
if [ -n "$OLD_PASSWORD" ] && [ "$OLD_PASSWORD" != "$NEW_PASSWORD" ]; then
|
|
echo "Rotating PostgreSQL password..."
|
|
PGPASSWORD=$OLD_PASSWORD psql -c "ALTER USER $PGUSER PASSWORD '$NEW_PASSWORD';"
|
|
echo "Rotation complete!"
|
|
fi
|
|
|
|
---
|
|
# ============================================================================
|
|
# Redis Password Rotation Job
|
|
# ============================================================================
|
|
apiVersion: batch/v1
|
|
kind: CronJob
|
|
metadata:
|
|
name: redis-password-rotator
|
|
namespace: myapp
|
|
spec:
|
|
schedule: "*/5 * * * *"
|
|
jobTemplate:
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: OnFailure
|
|
containers:
|
|
- name: rotate
|
|
image: redis:7-alpine
|
|
env:
|
|
- name: REDIS_HOST
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: redis-creds
|
|
key: host
|
|
- name: REDIS_PORT
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: redis-creds
|
|
key: port
|
|
- name: REDIS_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: redis-creds
|
|
key: password
|
|
command:
|
|
- sh
|
|
- -c
|
|
- |
|
|
# Redis CONFIG SET doesn't require current password
|
|
redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG SET requirepass "$REDIS_PASSWORD"
|
|
echo "Redis password updated"
|
|
|
|
---
|
|
# ============================================================================
|
|
# Main Application Deployment
|
|
# ============================================================================
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: myapp
|
|
namespace: myapp
|
|
spec:
|
|
replicas: 3
|
|
selector:
|
|
matchLabels:
|
|
app: myapp
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: myapp
|
|
annotations:
|
|
# Reloader restarts pods when ANY of these secrets change
|
|
reloader.stakater.com/auto: "true"
|
|
spec:
|
|
containers:
|
|
- name: app
|
|
image: myapp:latest
|
|
ports:
|
|
- containerPort: 8080
|
|
name: http
|
|
env:
|
|
# PostgreSQL connection
|
|
- name: POSTGRES_HOST
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres-creds
|
|
key: host
|
|
- name: POSTGRES_PORT
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres-creds
|
|
key: port
|
|
- name: POSTGRES_DB
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres-creds
|
|
key: database
|
|
- name: POSTGRES_USER
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres-creds
|
|
key: username
|
|
- name: POSTGRES_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres-creds
|
|
key: password
|
|
|
|
# Redis connection
|
|
- name: REDIS_HOST
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: redis-creds
|
|
key: host
|
|
- name: REDIS_PORT
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: redis-creds
|
|
key: port
|
|
- name: REDIS_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: redis-creds
|
|
key: password
|
|
|
|
# API Keys
|
|
- name: STRIPE_API_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: api-keys
|
|
key: stripe-api-key
|
|
- name: SENDGRID_API_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: api-keys
|
|
key: sendgrid-api-key
|
|
|
|
# JWT Secret
|
|
- name: JWT_SECRET
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: jwt-secret
|
|
key: jwt-secret
|
|
|
|
volumeMounts:
|
|
- name: tls
|
|
mountPath: /etc/tls
|
|
readOnly: true
|
|
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /health
|
|
port: 8080
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /ready
|
|
port: 8080
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 128Mi
|
|
limits:
|
|
cpu: 500m
|
|
memory: 512Mi
|
|
|
|
volumes:
|
|
- name: tls
|
|
secret:
|
|
secretName: app-tls
|
|
|
|
---
|
|
# ============================================================================
|
|
# Service
|
|
# ============================================================================
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: myapp
|
|
namespace: myapp
|
|
spec:
|
|
selector:
|
|
app: myapp
|
|
ports:
|
|
- port: 80
|
|
targetPort: 8080
|
|
name: http
|
|
|
|
---
|
|
# ============================================================================
|
|
# Ingress with TLS
|
|
# ============================================================================
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: myapp
|
|
namespace: myapp
|
|
annotations:
|
|
cert-manager.io/cluster-issuer: letsencrypt-production
|
|
spec:
|
|
ingressClassName: nginx
|
|
tls:
|
|
- hosts:
|
|
- myapp.c5ai.ch
|
|
secretName: myapp-tls-ingress
|
|
rules:
|
|
- host: myapp.c5ai.ch
|
|
http:
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: myapp
|
|
port:
|
|
number: 80
|
|
|
|
---
|
|
# ============================================================================
|
|
# Summary of Rotation Strategies
|
|
# ============================================================================
|
|
#
|
|
# 1. PostgreSQL (postgres-creds):
|
|
# - Rotates every 90 days
|
|
# - keepPreviousVersion: true
|
|
# - CronJob uses old password to authenticate and change
|
|
# - Reloader restarts app pods after rotation
|
|
#
|
|
# 2. Redis (redis-creds):
|
|
# - Rotates every 30 days
|
|
# - No previous version needed (CONFIG SET doesn't require auth)
|
|
# - CronJob updates password directly
|
|
# - Reloader restarts app pods after rotation
|
|
#
|
|
# 3. API Keys (api-keys):
|
|
# - No automatic rotation
|
|
# - Generated once, managed externally
|
|
# - Update via kubectl or Vault UI when provider rotates keys
|
|
#
|
|
# 4. TLS Certificates (app-tls):
|
|
# - No rotation
|
|
# - Managed by cert-manager or external process
|
|
# - Static content stored in Vault
|
|
#
|
|
# 5. JWT Secret (jwt-secret):
|
|
# - Rotates every 180 days
|
|
# - Simple rotation - just restart with new secret
|
|
# - Reloader handles pod restart
|
|
#
|
|
# ============================================================================
|
|
# Timeline Example: What Happens on Day 90
|
|
# ============================================================================
|
|
#
|
|
# T+0: ManagedSecret operator triggers rotation
|
|
# - Generates new passwords for postgres-creds and redis-creds
|
|
# - Updates Vault
|
|
# - Creates postgres-creds-previous
|
|
# - Updates postgres-creds
|
|
# - Updates redis-creds (no previous needed)
|
|
#
|
|
# T+1min: CronJobs detect changes
|
|
# - Postgres rotator: Uses old password to set new password in DB
|
|
# - Redis rotator: Sets new password via CONFIG SET
|
|
#
|
|
# T+2min: Reloader detects secret changes
|
|
# - Triggers rolling restart of myapp Deployment
|
|
# - Pods start with new credentials
|
|
#
|
|
# T+1hour: Cleanup
|
|
# - postgres-creds-previous automatically deleted
|
|
# - Old credentials no longer accessible
|
|
#
|
|
# ============================================================================
|
|
# Monitoring Commands
|
|
# ============================================================================
|
|
#
|
|
# # Watch all secrets
|
|
# kubectl get secrets -n myapp -w
|
|
#
|
|
# # Check rotation status
|
|
# kubectl get managedsecrets -n myapp
|
|
#
|
|
# # View rotation logs
|
|
# kubectl logs -n myapp -l job-name=postgres-password-rotator-XXXXX
|
|
# kubectl logs -n myapp -l job-name=redis-password-rotator-XXXXX
|
|
#
|
|
# # Check Reloader activity
|
|
# kubectl logs -n reloader -l app=reloader | grep myapp
|
|
#
|
|
# # Verify app pods restarted
|
|
# kubectl get pods -n myapp -o wide
|
|
#
|
|
# # Test connections with new credentials
|
|
# kubectl exec -it -n myapp deployment/myapp -- sh
|
|
# # psql -h $POSTGRES_HOST -U $POSTGRES_USER -d $POSTGRES_DB
|
|
# # redis-cli -h $REDIS_HOST -a $REDIS_PASSWORD ping
|
|
#
|
|
# ============================================================================
|
|
# Force Rotation for Testing
|
|
# ============================================================================
|
|
#
|
|
# # Force PostgreSQL rotation
|
|
# kubectl annotate managedsecret postgres-credentials -n myapp \
|
|
# reconcile="$(date +%s)" --overwrite
|
|
#
|
|
# # Force Redis rotation
|
|
# kubectl annotate managedsecret redis-credentials -n myapp \
|
|
# reconcile="$(date +%s)" --overwrite
|
|
#
|
|
# # Force all rotations
|
|
# kubectl annotate managedsecrets -n myapp --all \
|
|
# reconcile="$(date +%s)" --overwrite
|
|
#
|
|
# # Watch the cascade of events
|
|
# kubectl get events -n myapp --sort-by='.lastTimestamp' -w |