Operations Runbook
Health checks
Shell — quick status# All VisionPush pods kubectl get pods -n visionpush # Recent pod events kubectl get events -n visionpush --sort-by='.lastTimestamp' | tail -20 # API health curl https://api.yourdomain.com/healthz # Worker health (in-cluster) kubectl exec -n visionpush deploy/visionpush-worker -- \ wget -qO- http://localhost:9090/healthz # NATS JetStream lag kubectl exec -n visionpush deploy/visionpush-worker -- \ wget -qO- http://visionpush-nats:8222/jsz | jq '.streams[] | {name, messages}'
Open the VisionPush Grafana dashboard and check: Worker pods available (green ≥ 2), NATS consumer lag (near zero outside campaigns), 5xx error rate (green < 1%), Postgres up (UP).
Scaling
API — HPA
Shellhelm upgrade visionpush ./infra/k8s/charts/visionpush -n visionpush \ --set api.hpa.maxReplicas=20 \ --set api.hpa.targetCPUUtilizationPercentage=60
Worker — KEDA
For large campaigns (e.g. 10 million devices), temporarily raise the max replicas:
Shellhelm upgrade visionpush ./infra/k8s/charts/visionpush -n visionpush \ --set keda.worker.maxReplicas=50 \ --set keda.worker.lagThreshold="200"
PostgreSQL — add read replica
Shellhelm upgrade visionpush ./infra/k8s/charts/visionpush -n visionpush \ --set postgres.instances=2
CloudNativePG handles failover automatically. Primary: visionpush-postgres-rw, replicas: visionpush-postgres-r.
Rollout and rollback
Shell — deploy via git tag (recommended)git tag v1.2.3 && git push origin v1.2.3 # ArgoCD Image Updater commits the tag and ArgoCD syncs automatically
Shell — rollbackhelm history visionpush -n visionpush helm rollback visionpush -n visionpush # previous release helm rollback visionpush 3 -n visionpush # specific revision
Shell — force pod restartkubectl rollout restart \ deployment/visionpush-api \ deployment/visionpush-worker \ -n visionpush
Database operations
Manual migration
Shellkubectl run -it --rm migrate \ --image=ghcr.io/visionlab-de/visionpush:latest \ --env="DATABASE_URL=$(kubectl get secret visionpush-secrets -n visionpush \ -o jsonpath='{.data.DATABASE_URL}' | base64 -d)" \ --env="MIGRATE_ONLY=true" \ --restart=Never \ -n visionpush
Connect to the database
Shell# Direct psql inside the pod kubectl exec -it visionpush-postgres-1 -n visionpush \ -- psql -U visionpush -d visionpush # Via port-forward from local machine kubectl port-forward svc/visionpush-postgres-rw 5432:5432 -n visionpush psql "postgres://visionpush:PASSWORD@localhost:5432/visionpush"
Manual backup
Shellkubectl annotate cluster visionpush-postgres \ cnpg.io/immediateBackup=true -n visionpush # Check status kubectl get backup -n visionpush
Point-in-time restore
restore-cluster.yamlapiVersion: postgresql.cnpg.io/v1 kind: Cluster metadata: name: visionpush-postgres-restored namespace: visionpush spec: instances: 1 bootstrap: recovery: source: visionpush-postgres recoveryTarget: targetTime: "2026-05-19T03:00:00Z" externalClusters: - name: visionpush-postgres barmanObjectStore: destinationPath: "s3://your-bucket/visionpush/" s3Credentials: accessKeyId: name: visionpush-secrets key: AWS_ACCESS_KEY_ID secretAccessKey: name: visionpush-secrets key: AWS_SECRET_ACCESS_KEY
Shellkubectl apply -f restore-cluster.yaml kubectl get cluster visionpush-postgres-restored -n visionpush -w
Analytics maintenance
Parquet compaction
Nightly compaction runs at 03:00 UTC automatically. To expand the analytics PVC when usage exceeds 80%:
Shell# Check current usage kubectl exec -n visionpush deploy/visionpush-worker \ -- df -h /data/analytics # Expand PVC kubectl patch pvc visionpush-analytics -n visionpush \ -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'
Incident response
API returning 5xx
- Check pod status:
kubectl get pods -n visionpush - Check logs:
kubectl logs deploy/visionpush-api -n visionpush --tail=100 - Check DB connectivity — API logs
sqlxerrors if DB is unreachable - Check NATS:
kubectl exec -n visionpush deploy/visionpush-api -- wget -qO- http://visionpush-nats:8222/healthz - If crash-looping:
kubectl describe pod -n visionpush <pod-name>to see OOM or probe failures - Force restart:
kubectl rollout restart deployment/visionpush-api -n visionpush
Push notifications not delivering
- Check NATS consumer lag in Grafana → Push Delivery row
- Check worker logs:
kubectl logs deploy/visionpush-worker -n visionpush --tail=200 - Look for
throttledorauth errormessages - If credentials are invalid, the campaign shows
failedin stats — check platform config in admin panel - Emergency scale:
kubectl scale deployment visionpush-worker --replicas=5 -n visionpush
Worker pods OOM
Shellhelm upgrade visionpush ./infra/k8s/charts/visionpush -n visionpush \ --set worker.resources.limits.memory=2Gi \ --set worker.resources.requests.memory=512Mi
Certificate expired
Shellkubectl get certificate -n visionpush kubectl describe certificate visionpush-tls -n visionpush # Force renewal kubectl delete certificate visionpush-tls -n visionpush # Helm upgrade recreates it
Secret rotation (JWT / encryption key)
Shellvault kv put secret/visionpush/production \ JWT_SECRET="$(openssl rand -base64 32)" \ CREDENTIAL_ENCRYPTION_KEY="$(openssl rand -base64 32)" kubectl annotate externalsecret visionpush-secrets \ force-sync=$(date +%s) -n visionpush kubectl rollout restart \ deployment/visionpush-api \ deployment/visionpush-worker \ -n visionpush
All existing JWTs are immediately invalidated. Admin panel users must log in again.
PostgreSQL failover
CloudNativePG handles failover automatically (~10 seconds). To check the current primary:
Shellkubectl get cluster visionpush-postgres -n visionpush \ -o jsonpath='{.status.currentPrimary}' # Manual switchover (maintenance) kubectl annotate cluster visionpush-postgres \ cnpg.io/switchoverCandidate=visionpush-postgres-2 -n visionpush
Maintenance mode
Shell# Enable (API returns 503) kubectl scale deployment visionpush-api --replicas=0 -n visionpush # Restore kubectl scale deployment visionpush-api --replicas=2 -n visionpush
The worker continues processing the NATS queue during API maintenance.
Monitoring checklist
Daily
| Check | Command / Location |
|---|---|
| All pods running | kubectl get pods -n visionpush |
| Latest backup exists | kubectl get backup -n visionpush |
| Analytics PVC < 80% | Grafana → Infrastructure row |
| No critical alerts firing | Grafana → Alerting page |
| NATS lag near zero | Grafana → Push Delivery row |
Weekly
| Check | Action |
|---|---|
| Dependabot PRs | Merge patch/minor updates |
| Trivy scan results | GitHub Security tab |
| cargo audit results | GitHub Actions CI |
| API key rotation | Admin panel → API Keys |