- Streamlined README focused on quick start - Complete examples for all major use cases - Decision tree for choosing right pattern - Comprehensive troubleshooting guide
287 lines
7.5 KiB
YAML
287 lines
7.5 KiB
YAML
# Example 2: Container Registry with Rotation + Reloader
|
|
#
|
|
# Use case: Docker registry with basic auth that rotates every 90 days
|
|
#
|
|
# Characteristics:
|
|
# - Password rotates every 90 days
|
|
# - Registry doesn't need old password to accept new one
|
|
# - InitContainer regenerates htpasswd on pod restart
|
|
# - Reloader automatically restarts pod when secret changes
|
|
# - No keepPreviousVersion needed
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: registry
|
|
|
|
---
|
|
# ManagedSecret generates and rotates registry credentials
|
|
apiVersion: secrets.c5ai.ch/v1alpha1
|
|
kind: ManagedSecret
|
|
metadata:
|
|
name: registry-auth
|
|
namespace: registry
|
|
spec:
|
|
vault:
|
|
address: "http://openbao.openbao.svc.cluster.local:8200"
|
|
authMethod: kubernetes
|
|
role: managedsecret-operator
|
|
kvVersion: v2
|
|
mount: secret
|
|
path: registry/auth
|
|
|
|
fields:
|
|
- name: username
|
|
type: static
|
|
value: "admin"
|
|
|
|
- name: password
|
|
type: generated
|
|
generator:
|
|
type: password
|
|
length: 32
|
|
minDigits: 5
|
|
minSymbols: 5
|
|
minLowercase: 5
|
|
minUppercase: 5
|
|
symbolCharacters: "!@#$%^&*"
|
|
allowRepeat: false
|
|
|
|
destination:
|
|
name: registry-credentials
|
|
type: Opaque
|
|
# No need for previous version - htpasswd is regenerated fresh
|
|
|
|
rotation:
|
|
enabled: true
|
|
schedule: 2160h # 90 days
|
|
rotateGeneratedOnly: true # Only rotate password, keep username
|
|
|
|
---
|
|
# PersistentVolumeClaim for registry storage
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: registry-pvc
|
|
namespace: registry
|
|
spec:
|
|
accessModes:
|
|
- ReadWriteOnce
|
|
resources:
|
|
requests:
|
|
storage: 10Gi
|
|
storageClassName: longhorn
|
|
|
|
---
|
|
# Registry Deployment with Reloader integration
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: registry
|
|
namespace: registry
|
|
labels:
|
|
app: registry
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: registry
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: registry
|
|
annotations:
|
|
# Reloader watches registry-credentials and restarts pod on change
|
|
reloader.stakater.com/auto: "true"
|
|
spec:
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 65534
|
|
fsGroup: 65534
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
|
|
initContainers:
|
|
# Generate htpasswd file from current credentials at pod startup
|
|
- name: generate-htpasswd
|
|
image: httpd:2.4-alpine
|
|
command:
|
|
- sh
|
|
- -c
|
|
- |
|
|
USERNAME=$(cat /credentials/username)
|
|
PASSWORD=$(cat /credentials/password)
|
|
htpasswd -Bbn "$USERNAME" "$PASSWORD" > /auth/htpasswd
|
|
chmod 644 /auth/htpasswd
|
|
echo "htpasswd generated with user: $USERNAME"
|
|
volumeMounts:
|
|
- name: credentials
|
|
mountPath: /credentials
|
|
readOnly: true
|
|
- name: auth
|
|
mountPath: /auth
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsNonRoot: true
|
|
runAsUser: 65534
|
|
capabilities:
|
|
drop:
|
|
- ALL
|
|
|
|
containers:
|
|
- name: registry
|
|
image: registry:2
|
|
ports:
|
|
- containerPort: 5000
|
|
name: http
|
|
env:
|
|
- name: REGISTRY_STORAGE_DELETE_ENABLED
|
|
value: "true"
|
|
- name: REGISTRY_HTTP_ADDR
|
|
value: "0.0.0.0:5000"
|
|
# Enable HTTP basic auth
|
|
- name: REGISTRY_AUTH
|
|
value: "htpasswd"
|
|
- name: REGISTRY_AUTH_HTPASSWD_REALM
|
|
value: "Registry Realm"
|
|
- name: REGISTRY_AUTH_HTPASSWD_PATH
|
|
value: "/auth/htpasswd"
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsNonRoot: true
|
|
runAsUser: 65534
|
|
capabilities:
|
|
drop:
|
|
- ALL
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- name: registry-storage
|
|
mountPath: /var/lib/registry
|
|
- name: auth
|
|
mountPath: /auth
|
|
readOnly: true
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /
|
|
port: 5000
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /
|
|
port: 5000
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 128Mi
|
|
limits:
|
|
cpu: 500m
|
|
memory: 512Mi
|
|
|
|
volumes:
|
|
- name: registry-storage
|
|
persistentVolumeClaim:
|
|
claimName: registry-pvc
|
|
- name: credentials
|
|
secret:
|
|
secretName: registry-credentials
|
|
- name: auth
|
|
emptyDir: {}
|
|
|
|
---
|
|
# Service for internal access
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: registry
|
|
namespace: registry
|
|
spec:
|
|
type: ClusterIP
|
|
selector:
|
|
app: registry
|
|
ports:
|
|
- port: 5000
|
|
targetPort: 5000
|
|
protocol: TCP
|
|
name: http
|
|
|
|
---
|
|
# Ingress for external access with TLS
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: registry
|
|
namespace: registry
|
|
annotations:
|
|
cert-manager.io/cluster-issuer: letsencrypt-production
|
|
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
|
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
|
|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
|
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
|
|
spec:
|
|
ingressClassName: nginx
|
|
tls:
|
|
- hosts:
|
|
- registry.c5ai.ch
|
|
secretName: registry-ingress-tls
|
|
rules:
|
|
- host: registry.c5ai.ch
|
|
http:
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: registry
|
|
port:
|
|
number: 5000
|
|
|
|
---
|
|
# How the rotation flow works:
|
|
#
|
|
# 1. After 90 days, ManagedSecret operator:
|
|
# - Generates new password
|
|
# - Updates Vault
|
|
# - Updates registry-credentials Secret
|
|
#
|
|
# 2. Reloader detects Secret change:
|
|
# - Sees resourceVersion changed
|
|
# - Triggers rolling restart of registry Deployment
|
|
#
|
|
# 3. Pod restart with new credentials:
|
|
# - InitContainer runs
|
|
# - Reads NEW password from registry-credentials
|
|
# - Generates fresh htpasswd file
|
|
# - Registry container starts with new htpasswd
|
|
#
|
|
# 4. Result:
|
|
# - Old clients with old password: FAIL (401 Unauthorized)
|
|
# - New clients with new password: SUCCESS
|
|
# - Downtime: ~1-2 minutes during rolling restart
|
|
|
|
---
|
|
# Testing commands:
|
|
#
|
|
# # Get current credentials
|
|
# kubectl get secret registry-credentials -n registry -o jsonpath='{.data.username}' | base64 -d
|
|
# kubectl get secret registry-credentials -n registry -o jsonpath='{.data.password}' | base64 -d
|
|
#
|
|
# # Test without auth (should fail)
|
|
# curl -i https://registry.c5ai.ch/v2/
|
|
#
|
|
# # Test with auth (should succeed)
|
|
# REGISTRY_USER=$(kubectl get secret registry-credentials -n registry -o jsonpath='{.data.username}' | base64 -d)
|
|
# REGISTRY_PASS=$(kubectl get secret registry-credentials -n registry -o jsonpath='{.data.password}' | base64 -d)
|
|
# curl -u $REGISTRY_USER:$REGISTRY_PASS https://registry.c5ai.ch/v2/_catalog
|
|
#
|
|
# # Docker login
|
|
# docker login registry.c5ai.ch -u $REGISTRY_USER -p $REGISTRY_PASS
|
|
#
|
|
# # Force rotation test
|
|
# kubectl annotate managedsecret registry-auth -n registry reconcile="$(date +%s)" --overwrite
|
|
# kubectl get pods -n registry -w # Watch pod restart |