Skip to Content

Operations

Scripts, monitoring, backup, and operational procedures.

Overview

Operational tools include:

  • Backup/Restore - Data protection for all 8 data stores
  • Port Forwarding - Local access
  • Monitoring - Grafana, Loki, Prometheus
  • Debugging - Logs, traces, health checks
  • PodDisruptionBudgets - Safe rolling updates
  • Security Scanning - CI-integrated vulnerability detection
  • Secret Management - Vault + External Secrets Operator

Scripts

Port Forwarding

# Forward all services ./scripts/port-forward.sh # Infrastructure only ./scripts/port-forward-infra.sh

Service Ports

ServicePort
APISIX Gateway9080
APISIX Admin9180
Consul8500
PostgreSQL5432
Redis6379
Neo4j7474, 7687
NATS4222
MQTT1883
MinIO9000, 9001
Qdrant6333
Grafana3000
Loki3100
Vault8200

Big-Data KIND Validation

Use the Make targets from the isA_Cloud repo root to stand up and verify the local datalake foundation:

make setup-datalake-kind make verify-bigdata-kind make verify-bigdata-kind-readiness make verify-bigdata-kind-smoke make teardown-bigdata-kind

The setup flow preflights cert-manager CRDs before deploying the datalake stack. The verification flow checks readiness and smoke behavior for Kafka, PostgreSQL big-data metadata, Hive Metastore, MinIO, Iceberg tools, StarRocks, Apicurio Registry, Flink, Fluss, and Flink CDC jobs.

Backup

The platform provides two tiers of backup tooling: automated scripts that back up all 8 data stores in a single run, and per-service manual commands for targeted operations.

Full Cluster Backup (All 8 Stores)

# Back up everything: PostgreSQL, Redis, Qdrant, Neo4j, MinIO, Consul, NATS, MQTT ./scripts/backup-cluster-data.sh # Custom backup location ./scripts/backup-cluster-data.sh /path/to/backup # Specify cluster name KIND_CLUSTER=isa-cloud-staging ./scripts/backup-cluster-data.sh

The script creates timestamped directories under backups/ with subdirectories for each store. It handles pod discovery across different Helm chart naming conventions and uses port-forwarding for services that require API access (Qdrant, MinIO, NATS, Consul).

Stores backed up:

  • PostgreSQL - pg_dumpall (full) + per-database pg_dump -Fc (custom format)
  • Redis - BGSAVE trigger + RDB file copy
  • Qdrant - Per-collection snapshot via REST API
  • Neo4j - neo4j-admin database dump
  • MinIO - mc mirror of all buckets
  • Consul - Atomic snapshot via /v1/snapshot API (KV + service catalog)
  • NATS - JetStream stream configs + consumer definitions via NATS CLI
  • MQTT - Mosquitto persistence DB file (mosquitto.db)

NATS JetStream Backup

NATS JetStream stores stream configurations and consumer definitions. The backup captures both:

# Using the NATS CLI (preferred) nats stream list --json > streams.json # Per-stream config export for stream in $(nats stream list --json | jq -r '.[].config.name'); do nats stream info "$stream" --json > "nats/stream_${stream}.json" # Consumer configs for each stream for consumer in $(nats consumer list "$stream" --json | jq -r '.[].config.durable_name // empty'); do nats consumer info "$stream" "$consumer" --json > "nats/consumer_${stream}_${consumer}.json" done done

Alternatively, back up the JetStream data directory directly:

kubectl exec -n isa-cloud-staging deploy/nats -- \ tar czf /tmp/jetstream.tar.gz /data/jetstream kubectl cp isa-cloud-staging/nats-0:/tmp/jetstream.tar.gz \ ./nats-backup-$(date +%Y%m%d).tar.gz

Consul KV Backup

Consul supports two backup methods: atomic snapshots (preferred) and KV JSON export (fallback).

# Atomic snapshot (captures KV + service catalog + ACLs) consul snapshot save /tmp/consul-backup.snap # Or via the HTTP API curl -sf -o consul-snapshot.snap http://localhost:8500/v1/snapshot # Fallback: KV-only export as JSON consul kv export > consul-kv-$(date +%Y%m%d).json

MQTT Retained Messages Backup

For Mosquitto brokers, the persistence database file contains retained messages:

kubectl cp isa-cloud-staging/<mosquitto-pod>:/mosquitto/data/mosquitto.db \ ./mqtt-backup-$(date +%Y%m%d)/mosquitto.db

PostgreSQL CronJob Backup (Production)

Production uses a Kubernetes CronJob (postgresql-backup) for automated daily backups. The manifest is at deployments/kubernetes/production/manifests/postgresql-backup.yaml.

Schedule: Daily at 2:00 AM UTC, with a 7-day retention window.

# Key CronJob configuration spec: schedule: "0 2 * * *" # Daily at 2 AM UTC concurrencyPolicy: Forbid # Never run two backups at once successfulJobsHistoryLimit: 7 failedJobsHistoryLimit: 3

The CronJob runs pg_dumpall (compressed) plus per-database custom-format dumps to a 100Gi PVC (postgresql-backup-data). It also writes a latest.json metadata file and prunes backups older than the retention window.

A companion verification CronJob (postgresql-backup-verify) runs weekly on Sundays at 3:30 AM UTC. It checks:

  • Backup file exists and is non-empty
  • Valid gzip integrity
  • SQL content headers are present
  • Backup freshness (must be less than 26 hours old)

Restore from CronJob backups:

# Full restore (latest) kubectl exec -it postgresql-backup-pod -- bash /scripts/restore.sh # Restore specific timestamp kubectl exec -it postgresql-backup-pod -- bash /scripts/restore.sh pgdump_20260309_020000 # Restore single database (uses pg_restore with parallel jobs) kubectl exec -it postgresql-backup-pod -- bash /scripts/restore.sh --db isa_production

Manual Backup (Individual Services)

# PostgreSQL kubectl exec -it postgres-0 -n isa-cloud-staging -- \ pg_dump -U postgres isa_db | gzip > backup.sql.gz # Redis kubectl exec -it redis-master-0 -n isa-cloud-staging -- \ redis-cli BGSAVE # MinIO mc mirror minio/bucket ./backup/bucket

Restore

Full Cluster Restore

./scripts/restore-cluster-data.sh backups/cluster-backup-20260114-120000

The restore script waits for infrastructure pods to be ready before restoring. Each store is restored in order: PostgreSQL, MinIO, Qdrant, Neo4j, Redis, NATS, Consul, MQTT.

NATS JetStream Restore

Stream and consumer configurations are restored from the JSON backups:

# Restore stream configs for stream_file in nats/stream_*.json; do STREAM_NAME=$(jq -r '.config.name' "$stream_file") jq '.config' "$stream_file" | nats stream add "$STREAM_NAME" --config /dev/stdin done # Restore consumer configs for consumer_file in nats/consumer_*.json; do STREAM_NAME=$(jq -r '.stream_name' "$consumer_file") CONSUMER_NAME=$(jq -r '.config.durable_name' "$consumer_file") jq '.config' "$consumer_file" | nats consumer add "$STREAM_NAME" "$CONSUMER_NAME" --config /dev/stdin done

Or restore from a data directory backup:

kubectl cp nats-backup.tar.gz isa-cloud-staging/nats-0:/tmp/ kubectl exec -n isa-cloud-staging deploy/nats -- \ tar xzf /tmp/nats-backup.tar.gz -C / kubectl rollout restart statefulset/nats -n isa-cloud-staging

Consul KV Restore

# From atomic snapshot (preferred) curl -sf -X PUT --data-binary @consul-snapshot.snap http://localhost:8500/v1/snapshot # Or via consul CLI consul snapshot restore /tmp/consul-backup.snap # From KV JSON export consul kv import @consul-kv-export.json

MQTT Restore

# Copy persistence file back and restart the broker kubectl cp mqtt-backup/mosquitto.db \ isa-cloud-staging/<mosquitto-pod>:/mosquitto/data/mosquitto.db kubectl rollout restart deployment/mosquitto -n isa-cloud-staging

PodDisruptionBudgets

The isa-service Helm chart includes a PodDisruptionBudget (PDB) template that is automatically enabled for services with more than one replica. This ensures safe rolling updates and node drains.

Template: deployments/charts/isa-service/templates/pdb.yaml

# Enabled automatically when replicas > 1 podDisruptionBudget: enabled: true minAvailable: 1 # or use maxUnavailable instead # maxUnavailable: 1 # alternative: allow at most 1 pod down

The PDB is only created when podDisruptionBudget.enabled is true and the service has more than one replica (computed from replicas or autoscaling.minReplicas). You can configure either minAvailable or maxUnavailable; if minAvailable is set it takes precedence.

Usage in service values:

# values.yaml for a service name: auth-service namespace: isa-cloud-production replicas: 3 podDisruptionBudget: enabled: true minAvailable: 2 # Always keep at least 2 pods running during disruptions

Security Scanning

The platform runs automated security scans in CI via the .github/workflows/security-scan.yaml workflow. It triggers on pushes to main, pull requests, and on a weekly cron schedule (Mondays at 9:00 AM UTC).

Scan Layers

ScanToolWhat It Checks
SecretsgitleaksLeaked credentials, API keys, tokens in git history
Python Dependenciespip-auditKnown CVEs in Python packages
Python SASTbanditCommon security anti-patterns in Python code
Go DependenciesgovulncheckKnown vulnerabilities in Go modules
Go SASTgosecSecurity issues in Go source code

How It Works

The workflow is composed of four jobs:

  1. secrets-scan - Runs gitleaks against the full git history to detect leaked secrets.
  2. python-security - Runs pip-audit on isA_common/requirements.txt for dependency vulnerabilities, then bandit for static analysis.
  3. go-security - Detects go.mod presence, runs govulncheck and gosec if Go code exists.
  4. security-summary - Aggregates results into a GitHub Actions step summary and fails the workflow if any scan failed.

Results are uploaded as artifacts (retained for 30 days) and written to the GitHub Actions step summary for easy review.

Reusable Workflow

Other repositories can call the security scan as a reusable workflow:

# In another repo's workflow jobs: security: uses: xenoISA/isA_Cloud/.github/workflows/security-scan.yaml@main with: python_path: '.' go_path: '.'

Vault + External Secrets Operator

Production secrets are managed through HashiCorp Vault with the External Secrets Operator (ESO) syncing them into native Kubernetes Secrets. This eliminates hardcoded credentials in Helm values and manifests.

Architecture

Vault (HA, 3 replicas) └── KV v2 engine at secret/ └── secret/data/isa-cloud/production/<service> ├── postgresql (password, replication-password, pgpool-admin-password) ├── redis (password) ├── neo4j (password) ├── minio (root-user, root-password) ├── emqx (dashboard-password) └── apisix (admin-key) External Secrets Operator └── ClusterSecretStore (vault-backend) └── ExternalSecret CRs (one per service) └── K8s Secrets (postgresql-secret, redis-secret, etc.)

Vault runs in HA mode with 3 replicas using Consul as its storage backend. It uses Kubernetes auth so ESO authenticates via its ServiceAccount token.

ESO refreshes secrets every hour (refreshInterval: 1h). Each ExternalSecret CR maps a Vault path to a native K8s Secret that Helm charts already reference.

Key Files

FilePurpose
production/values/vault.yamlVault Helm values (HA, Consul backend, audit logs)
production/values/external-secrets.yamlESO Helm values (2 replicas, webhook validation)
production/manifests/cluster-secret-store.yamlClusterSecretStore connecting ESO to Vault
production/manifests/external-secrets.yamlExternalSecret CRs for all infrastructure services
production/scripts/vault-init.shOne-time Vault init, unseal, auth setup, and secret seeding

Initial Setup

# 1. Deploy Vault helm install vault hashicorp/vault -n isa-cloud-production \ -f production/values/vault.yaml # 2. Initialize and unseal Vault (one-time) ./production/scripts/vault-init.sh # 3. Deploy External Secrets Operator helm install external-secrets external-secrets/external-secrets \ -n external-secrets --create-namespace \ -f production/values/external-secrets.yaml # 4. Apply the ClusterSecretStore and ExternalSecret CRs kubectl apply -f production/manifests/cluster-secret-store.yaml kubectl apply -f production/manifests/external-secrets.yaml

Common Operations

# Check Vault status ./production/scripts/vault-init.sh status # Unseal Vault after pod restart ./production/scripts/vault-init.sh unseal # Update or add secrets ./production/scripts/vault-init.sh seed # Verify ExternalSecrets are syncing kubectl get externalsecrets -n isa-cloud-production # Check a specific synced secret kubectl get secret postgresql-secret -n isa-cloud-production -o yaml

Monitoring

Grafana Dashboards

Access at http://localhost:3000:

DashboardPurpose
InfrastructureCPU, memory, disk usage
ServicesRequest rate, latency, errors
DatabaseQuery performance, connections
APISIXGateway metrics, routes

Key Metrics

# Request rate sum(rate(apisix_http_status[5m])) by (service) # Error rate sum(rate(apisix_http_status{code=~"5.."}[5m])) / sum(rate(apisix_http_status[5m])) # Latency P99 histogram_quantile(0.99, sum(rate(apisix_http_latency_bucket[5m])) by (le, service))

Loki Log Queries

# Service errors {namespace="isa-cloud-staging", app="auth-service"} |= "error" # gRPC requests {namespace="isa-cloud-staging"} | json | method=~".*" # Slow requests {namespace="isa-cloud-staging"} | json | duration > 1s

Health Checks

Service Health

# Check all pods kubectl get pods -n isa-cloud-staging # Check specific service kubectl describe pod auth-service-xxx -n isa-cloud-staging # View logs kubectl logs -f auth-service-xxx -n isa-cloud-staging

Consul Health

# All services curl http://localhost:8500/v1/catalog/services | jq # Service health curl http://localhost:8500/v1/health/service/auth_service | jq # Critical services curl http://localhost:8500/v1/health/state/critical | jq

gRPC Health

grpcurl -plaintext localhost:50061 grpc.health.v1.Health/Check

Troubleshooting

Pod Not Starting

# Check events kubectl get events -n isa-cloud-staging --sort-by='.lastTimestamp' # Describe pod kubectl describe pod <pod-name> -n isa-cloud-staging # Check resource limits kubectl top pods -n isa-cloud-staging

Service Not Reachable

# Check service exists kubectl get svc -n isa-cloud-staging # Check endpoints kubectl get endpoints <service-name> -n isa-cloud-staging # Test DNS kubectl run debug --rm -it --image=busybox -- nslookup <service-name>

Alerting

Common Alerts

AlertConditionAction
ServiceDownPod not ready > 5mCheck pod logs, restart
HighErrorRateError rate > 5%Check service logs
HighLatencyP99 > 1sCheck database, scale
DiskFullUsage > 90%Cleanup or expand
HighMemoryUsage > 85%Scale or optimize

Runbooks

Service Restart

kubectl rollout restart deployment/<service-name> -n isa-cloud-staging kubectl rollout status deployment/<service-name> -n isa-cloud-staging

Clear Redis Cache

kubectl exec -it redis-master-0 -n isa-cloud-staging -- redis-cli FLUSHDB

Next Steps