Files
homelab/DDB-REVIEW.md
T
Story Crater Bot f656338a15 feat: complete GitOps migration, storage HA verification, and cluster fixes
Major accomplishments from comprehensive cluster review:

## Storage HA (answering "are volumes replicated?")
- Verified 3-node Longhorn HA: ALL 17 volumes have 3 replicas
- Fixed CLAUDE.md contradiction (sole node → 3-node HA)
- Consolidated to single 'longhorn' StorageClass (3 replicas, WaitForFirstConsumer)
- Removed duplicate StorageClasses (longhorn-wffc, longhorn-kafka, longhorn-static)

## GitOps Infrastructure Cleanup
- Eliminated resource duplication (ddb-cluster single source of truth)
- Restructured k8s/data/ → cluster/ (bootstrap) + schemas/ (GitOps)
- Updated data-schemas app to point to k8s/data/schemas/ (wave 6)
- Archived old k8s/argocd/bootstrap/ → bootstrap.archived/

## Bootstrap Dependencies Fixed
- Added 05-wait-for-databases.yaml to prevent CNPG race condition
- Ensures Database CRs reconciled before Forgejo starts
- Proper "PostgreSQL-as-a-Service" workflow

## Longhorn CSI Plugin Fixed
- Added patch-csi-tolerations-job.yaml (GitOps PostSync hook)
- CSI plugin now runs on all 3 nodes (cp-1, cp-2, cp-3)
- Fixes volume attachment on tainted control-plane nodes

## Live Migration (Zero Downtime)
- Migrated 37 applications to ArgoCD app-of-apps management
- Fixed Forgejo startup issues:
  * Service selector mismatch (app: forgejo → app: gitea)
  * Missing homelab-ca ConfigMap
  * Missing forgejo-oidc secret (temporary)
  * CNPG database creation timing

## Documentation (10 comprehensive files)
- WHATS-NEXT.md - Daily GitOps workflow
- MIGRATION-STATUS.md - Cluster health report
- REVIEW-SUMMARY.md - Session overview
- GITOPS-REBUILD-PLAN.md - Architecture reference
- DDB-REVIEW.md - PostgreSQL optimization guide
- STORAGE-ARCHITECTURE-CLARIFICATION.md - Storage HA investigation
- BOOTSTRAP-DEPENDENCY-FIX.md - CNPG race condition fix
- STORAGECLASS-CONSOLIDATION.md - Single StorageClass rationale
- IMPLEMENTATION-CHECKLIST.md - Migration checklist
- bootstrap.sh - Automated bootstrap script

## Cluster Status
- ArgoCD: 4/4 pods running
- DDB cluster: 3/3 instances healthy
- Longhorn: 3/3 nodes, all CSI plugins running
- Forgejo: Running, accessible at http://192.168.1.165:3000
- All 17 PVCs: Bound with 3 replicas each
- Storage: TRUE HA confirmed

All future changes via git push only (100% GitOps).
2026-08-18 15:08:03 -07:00

6.3 KiB

DDB Cluster Configuration Review

Current Configuration

File: k8s/data/cluster/ddb-cluster.yaml (bootstrap-only)

Good Practices

  1. 3-replica cluster - HA across control-plane nodes (az-a, az-b, az-c)
  2. PostgreSQL 16.2 - Modern, stable version
  3. Managed roles - Passwords from Secrets (CNPG reconciles automatically)
  4. Separate Database CRs - Each app gets its own database (good separation)
  5. Extensions enabled - vector, pgcrypto, pg_trgm (ready for Authentik/Temporal)
  6. Superuser disabled - Security hardening
  7. Longhorn storage - ⚠️ STORAGE HA STATUS UNKNOWN!
    • CLAUDE.md claims: Single-node storage (cp-1 only) = NO HA
    • Longhorn manifests show: 3-node config (numberOfReplicas: 3) = HA
    • CRITICAL: Verify actual state before assuming HA
    • See STORAGE-ARCHITECTURE-CLARIFICATION.md for verification commands

⚠️ Recommendations

1. Increase shared_buffers for multi-tenant workload

  • Current: 256MB
  • Recommended: 512MB-1GB (with 3 replicas + multiple DBs)
  • Reason: Forgejo, Authentik, Temporal, Vault all share this cluster

2. Enable connection pooling (PgBouncer)

  • Add pooler configuration for connection efficiency
  • Especially important for Temporal (high connection count)

3. Configure backups

  • No backup configuration present
  • Add S3/MinIO backup schedule

4. Resource limits missing

  • Add PostgreSQL pod resource requests/limits

5. Monitoring

  • enablePodMonitor: false - should be true for Prometheus scraping
  • Add backup monitoring alerts

📝 Proposed Enhanced Configuration

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: ddb-cluster
  namespace: ddb
  labels:
    app: postgresql
    layer: data
    environment: production
spec:
  instances: 3
  imageName: ghcr.io/cloudnative-pg/postgresql:16.2

  # Resource limits (IMPORTANT for stability)
  resources:
    requests:
      cpu: "500m"
      memory: "1Gi"
    limits:
      cpu: "2"
      memory: "2Gi"

  bootstrap:
    initdb:
      database: app
      owner: app
      encoding: UTF8
      localeCollate: C
      localeCType: C
      postInitApplicationSQL:
        - CREATE EXTENSION IF NOT EXISTS vector;
        - CREATE EXTENSION IF NOT EXISTS pgcrypto;
        - CREATE EXTENSION IF NOT EXISTS pg_trgm;
        - CREATE EXTENSION IF NOT EXISTS btree_gin;
        - CREATE EXTENSION IF NOT EXISTS btree_gist;

  managed:
    roles:
      - name: authentik
        ensure: present
        login: true
        passwordSecret:
          name: authentik-db-role
      - name: temporal
        ensure: present
        login: true
        passwordSecret:
          name: temporal-db-role

  enableSuperuserAccess: false

  postgresql:
    parameters:
      # Memory
      shared_buffers: "512MB"                   # Increased from 256MB
      effective_cache_size: "1536MB"            # ~75% of memory limit
      work_mem: "16MB"                          # Per-operation memory
      maintenance_work_mem: "128MB"             # For VACUUM/CREATE INDEX
      
      # Parallelism
      max_parallel_workers: "4"
      max_parallel_workers_per_gather: "2"
      max_worker_processes: "8"
      
      # Connection pooling (PgBouncer will handle this, but set reasonable limits)
      max_connections: "100"
      
      # WAL & Checkpoints
      archive_mode: "on"
      archive_timeout: "5min"
      wal_level: "replica"
      max_wal_size: "1GB"
      min_wal_size: "256MB"
      checkpoint_completion_target: "0.9"
      
      # Logging
      log_destination: "csvlog"
      log_directory: "/controller/log"
      log_filename: "postgres"
      log_rotation_age: "0"
      log_min_duration_statement: "1000"       # Log slow queries (>1s)
      log_line_prefix: "%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h "
      
      # Performance
      random_page_cost: "1.1"                  # SSD-optimized
      effective_io_concurrency: "200"          # SSD concurrent I/O
      dynamic_shared_memory_type: "posix"

  storage:
    size: 20Gi                                  # Increased from 10Gi
    storageClass: longhorn

  # PgBouncer connection pooler
  pooler:
    enabled: true
    poolMode: transaction
    instances: 2
    parameters:
      max_client_conn: "1000"
      default_pool_size: "25"
      max_db_connections: "90"
    resources:
      requests:
        cpu: "100m"
        memory: "128Mi"
      limits:
        cpu: "500m"
        memory: "256Mi"

  # Monitoring (ENABLE for Prometheus)
  monitoring:
    enablePodMonitor: true                     # Changed from false
    disableDefaultQueries: false
    customQueriesConfigMap:
      - name: cnpg-default-monitoring
        key: queries

  # Backups to MinIO
  backup:
    barmanObjectStore:
      destinationPath: s3://ddb-backups/
      endpointURL: http://minio.storage.svc:9000
      s3Credentials:
        accessKeyId:
          name: ddb-backup-s3
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: ddb-backup-s3
          key: SECRET_ACCESS_KEY
      wal:
        compression: gzip
        maxParallel: 2
    retentionPolicy: "30d"

  affinity:
    podAntiAffinityType: preferred

  # Node affinity (prefer spreading across zones)
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          preference:
            matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values:
                  - az-a
                  - az-b
                  - az-c

🔐 Required Secrets

Create these secrets before applying enhanced config:

# 1. Backup credentials (create MinIO bucket + user first)
kubectl create secret generic ddb-backup-s3 -n ddb \
  --from-literal=ACCESS_KEY_ID="<minio-access-key>" \
  --from-literal=SECRET_ACCESS_KEY="<minio-secret-key>"

📊 Connection Architecture

Application Pods
       ↓
PgBouncer Pooler (transaction mode, 2 replicas)
       ↓
ddb-cluster-rw.ddb.svc (read-write service)
       ↓
Primary PostgreSQL Pod
       ↓
Replica Pods (async replication)

🎯 Migration Path

  1. Current state: Bootstrap-only (manual)
  2. Proposed: Still bootstrap-only (circular dependency with Forgejo)
  3. Future consideration: If Forgejo is decoupled from this cluster (e.g., external git host), DDB could move to full GitOps