From 5c259237d75b0ba528d115d7a594865dc5b723a2 Mon Sep 17 00:00:00 2001 From: Story Crater Bot <19826264+Riotpiaole@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:53:59 -0700 Subject: [PATCH] feat(data): Add CNPG cluster + database schema initialization Create production PostgreSQL cluster via CNPG (3-node HA, Longhorn storage). Schema initialization Job creates schemas for: - Authentik (identity provider) - Temporal (workflow engine) - Vault (secrets management) - App (generic application databases) Database layer now captures complete IaC for stateful infrastructure. Services find ready schemas when deployed. --- k8s/data/db-init-job.yaml | 67 +++++++++++++++++++++++++++++++++++++ k8s/data/ddb-cluster.yaml | 62 ++++++++++++++++++++++++++++++++++ k8s/data/kustomization.yaml | 11 +++--- k8s/data/schemas.yaml | 36 ++++++++++++++++++++ 4 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 k8s/data/db-init-job.yaml create mode 100644 k8s/data/ddb-cluster.yaml create mode 100644 k8s/data/schemas.yaml diff --git a/k8s/data/db-init-job.yaml b/k8s/data/db-init-job.yaml new file mode 100644 index 0000000..08b9d4f --- /dev/null +++ b/k8s/data/db-init-job.yaml @@ -0,0 +1,67 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: db-schema-init + namespace: ddb + labels: + app: postgresql-init + layer: data +spec: + backoffLimit: 3 + ttlSecondsAfterFinished: 3600 # Keep Job for 1 hour after completion + template: + metadata: + labels: + app: postgresql-init + spec: + serviceAccountName: default + restartPolicy: Never + containers: + - name: schema-init + image: postgres:16.2-alpine + env: + - name: PGHOST + value: ddb-cluster-rw.ddb.svc.cluster.local + - name: PGPORT + value: "5432" + - name: PGDATABASE + value: app + - name: PGUSER + value: app + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: ddb-cluster-app + key: password + command: + - /bin/sh + - -c + - | + # Wait for cluster to be ready + echo "Waiting for PostgreSQL to be ready..." + until pg_isready -h $PGHOST -p $PGPORT -U $PGUSER; do + echo "Waiting..." + sleep 5 + done + + echo "Creating schemas..." + + # Create schemas from ConfigMap + psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE << 'EOF' + CREATE SCHEMA IF NOT EXISTS authentik; + GRANT USAGE ON SCHEMA authentik TO app; + GRANT CREATE ON SCHEMA authentik TO app; + + CREATE SCHEMA IF NOT EXISTS temporal; + GRANT USAGE ON SCHEMA temporal TO app; + GRANT CREATE ON SCHEMA temporal TO app; + + CREATE SCHEMA IF NOT EXISTS vault; + GRANT USAGE ON SCHEMA vault TO app; + GRANT CREATE ON SCHEMA vault TO app; + + GRANT USAGE ON SCHEMA public TO app; + GRANT CREATE ON SCHEMA public TO app; + EOF + + echo "✓ Schema initialization complete" diff --git a/k8s/data/ddb-cluster.yaml b/k8s/data/ddb-cluster.yaml new file mode 100644 index 0000000..dfb8f54 --- /dev/null +++ b/k8s/data/ddb-cluster.yaml @@ -0,0 +1,62 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: ddb-cluster + namespace: ddb + labels: + app: postgresql + layer: data +spec: + # 3-node cluster for HA + instances: 3 + + # PostgreSQL 16.2 + imageName: ghcr.io/cloudnative-pg/postgresql:16.2 + + # Bootstrap: create app database + extensions + 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; + + # Disable superuser (security) + enableSuperuserAccess: false + + # PostgreSQL configuration + postgresql: + parameters: + shared_buffers: "256MB" + max_parallel_workers: "4" + max_parallel_workers_per_gather: "4" + # WAL archiving for backups + archive_mode: "on" + archive_timeout: "5min" + log_destination: "csvlog" + log_directory: "/controller/log" + log_filename: "postgres" + log_rotation_age: "0" + dynamic_shared_memory_type: "posix" + + # Storage on Longhorn + storage: + size: 10Gi + storageClass: longhorn + + # Monitoring + monitoring: + enablePodMonitor: false + disableDefaultQueries: false + customQueriesConfigMap: + - name: cnpg-default-monitoring + key: queries + + # Pod anti-affinity for spreading replicas + affinity: + podAntiAffinityType: preferred diff --git a/k8s/data/kustomization.yaml b/k8s/data/kustomization.yaml index 71b6460..397026f 100644 --- a/k8s/data/kustomization.yaml +++ b/k8s/data/kustomization.yaml @@ -4,9 +4,12 @@ metadata: name: data # Layer 6: Data — stateful services (databases, message brokers) -# Dependencies: all previous layers +# Dependencies: all previous layers (bootstrap, platform, etc.) # Order: Applied last -# Placeholder for future database deployments -# Currently empty; databases deployed via Layer 5 applications -resources: [] +# PostgreSQL cluster + schema initialization +# Required by: Authentik, Temporal, Vault, SQS +resources: + - ddb-cluster.yaml + - schemas.yaml + - db-init-job.yaml diff --git a/k8s/data/schemas.yaml b/k8s/data/schemas.yaml new file mode 100644 index 0000000..7a10bfe --- /dev/null +++ b/k8s/data/schemas.yaml @@ -0,0 +1,36 @@ +--- +# Schema initialization ConfigMap for PostgreSQL services +# Services: Authentik, Vault (optional), Temporal, others +# Applied via initdb Jobs after CNPG cluster is ready + +apiVersion: v1 +kind: ConfigMap +metadata: + name: db-schemas + namespace: ddb + labels: + app: postgresql-schemas +data: + # Authentik schema (minimal — Authentik creates most tables via Django ORM) + authentik-init.sql: | + CREATE SCHEMA IF NOT EXISTS authentik; + GRANT USAGE ON SCHEMA authentik TO app; + GRANT CREATE ON SCHEMA authentik TO app; + + # Temporal schema + temporal-init.sql: | + CREATE SCHEMA IF NOT EXISTS temporal; + GRANT USAGE ON SCHEMA temporal TO app; + GRANT CREATE ON SCHEMA temporal TO app; + + # Vault schema (if using PostgreSQL backend) + vault-init.sql: | + CREATE SCHEMA IF NOT EXISTS vault; + GRANT USAGE ON SCHEMA vault TO app; + GRANT CREATE ON SCHEMA vault TO app; + + # Application schema (generic) + app-init.sql: | + -- Default app schema (public) + GRANT USAGE ON SCHEMA public TO app; + GRANT CREATE ON SCHEMA public TO app;