From 40a6295470937c10dac2e095bdc0358b5c547041 Mon Sep 17 00:00:00 2001 From: Lee Chapman Date: Sun, 4 Oct 2026 16:22:57 -0700 Subject: [PATCH] feat(high-command): manage high-command-postgres via Fleet Adopt the hand-applied high-command-postgres Cluster and Pooler (helm.takeOwnership); spec mirrors live object so adoption is a no-op apart from max_slot_wal_keep_size=2GB to stop dead slots filling WAL. Co-authored-by: Cursor --- README.md | 15 ++++ fleet.yaml | 5 ++ k8s/high-command/postgres-cluster.yaml | 115 +++++++++++++++++++++++++ k8s/high-command/postgres-pooler.yaml | 18 ++++ 4 files changed, 153 insertions(+) create mode 100644 k8s/high-command/postgres-cluster.yaml create mode 100644 k8s/high-command/postgres-pooler.yaml diff --git a/README.md b/README.md index 2e02829..2dbce9d 100644 --- a/README.md +++ b/README.md @@ -329,6 +329,21 @@ mv kustomization.yaml .kustomization.yaml - Vault - Your GitOps tool's secret management +## High Command database (CloudNativePG) + +`k8s/high-command/` holds the `high-command-postgres` CloudNativePG Cluster (3 instances, 10Gi, +database `highcommand`) and its `high-command-postgres-pooler` PgBouncer Pooler in the +`high-command` namespace. Both were originally applied by hand and are adopted by Fleet via +`helm.takeOwnership` in `fleet.yaml`. The Cluster spec deliberately mirrors the live object, +including operator-defaulted fields. Keep it that way: under Helm adoption, any field dropped +from the manifest is removed from the live object. + +- `high-command-postgres-credentials` (username, password) is created by hand, not in git. +- `max_slot_wal_keep_size: 2GB` caps WAL retained for a lagging or broken replica, so a dead + replication slot cannot fill the volume. +- Rebuild a single broken replica with `kubectl cnpg destroy high-command-postgres -n high-command`. + CNPG re-clones it with `pg_basebackup` and drops the old slot. + ## Documentation - **[MCP Servers Review](docs/MCP_SERVERS_REVIEW.md)** - Detailed review of all HTTP MCP servers diff --git a/fleet.yaml b/fleet.yaml index d6a73f4..af56733 100644 --- a/fleet.yaml +++ b/fleet.yaml @@ -19,3 +19,8 @@ # mv .kustomization.yaml kustomization.yaml # kubectl kustomize . # mv kustomization.yaml .kustomization.yaml + +# Adopt objects that existed before they were added here (high-command-postgres Cluster + Pooler +# were applied by hand). Without this Helm refuses to manage them. +helm: + takeOwnership: true diff --git a/k8s/high-command/postgres-cluster.yaml b/k8s/high-command/postgres-cluster.yaml new file mode 100644 index 0000000..a238187 --- /dev/null +++ b/k8s/high-command/postgres-cluster.yaml @@ -0,0 +1,115 @@ +# CloudNativePG Cluster for High Command (database: highcommand) +# Requires: high-command-postgres-credentials secret (username, password) in this namespace. +# +# Adopted from a hand-applied Cluster (helm.takeOwnership in fleet.yaml). The spec mirrors the +# live object including operator-defaulted fields, so adoption does not drop or change anything. +# storage.storageClass is intentionally unset (PVCs use the cluster default, truenas-csi-nfs). +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: high-command-postgres + namespace: high-command +spec: + instances: 3 + imageName: ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie + affinity: + podAntiAffinityType: preferred + bootstrap: + initdb: + database: highcommand + encoding: UTF8 + localeCType: C + localeCollate: C + owner: highcommand + secret: + name: high-command-postgres-credentials + enablePDB: true + enableSuperuserAccess: false + failoverDelay: 0 + logLevel: info + maxSyncReplicas: 0 + minSyncReplicas: 0 + monitoring: + customQueriesConfigMap: + - key: queries + name: cnpg-default-monitoring + disableDefaultQueries: false + enablePodMonitor: false + podSecurityContext: + runAsGroup: 26 + runAsNonRoot: true + runAsUser: 26 + postgresGID: 26 + postgresUID: 26 + postgresql: + parameters: + archive_mode: "on" + archive_timeout: 5min + checkpoint_completion_target: "0.9" + default_statistics_target: "100" + dynamic_shared_memory_type: posix + effective_cache_size: 1GB + effective_io_concurrency: "200" + full_page_writes: "on" + log_destination: csvlog + log_directory: /controller/log + log_filename: postgres + log_rotation_age: "0" + log_rotation_size: "0" + log_truncate_on_rotation: "false" + logging_collector: "on" + maintenance_work_mem: 64MB + max_connections: "200" + max_parallel_maintenance_workers: "2" + max_parallel_workers: "4" + max_parallel_workers_per_gather: "2" + max_replication_slots: "32" + # Cap WAL kept for lagging/broken replicas so a dead slot cannot fill the 10Gi volume. + max_slot_wal_keep_size: 2GB + max_wal_size: 4GB + max_worker_processes: "4" + min_wal_size: 1GB + random_page_cost: "1.1" + shared_buffers: 256MB + shared_memory_type: mmap + shared_preload_libraries: "" + ssl_max_protocol_version: TLSv1.3 + ssl_min_protocol_version: TLSv1.3 + wal_buffers: 16MB + wal_keep_size: 512MB + wal_level: logical + wal_log_hints: "on" + wal_receiver_timeout: 5s + wal_sender_timeout: 5s + work_mem: 4MB + syncReplicaElectionConstraint: + enabled: false + primaryUpdateMethod: restart + primaryUpdateStrategy: unsupervised + probes: + liveness: + isolationCheck: + connectionTimeout: 1000 + enabled: true + requestTimeout: 1000 + replicationSlots: + highAvailability: + enabled: true + slotPrefix: _cnpg_ + synchronizeReplicas: + enabled: true + updateInterval: 30 + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: 500m + memory: 512Mi + smartShutdownTimeout: 180 + startDelay: 3600 + stopDelay: 1800 + storage: + resizeInUseVolumes: true + size: 10Gi + switchoverDelay: 3600 diff --git a/k8s/high-command/postgres-pooler.yaml b/k8s/high-command/postgres-pooler.yaml new file mode 100644 index 0000000..040a373 --- /dev/null +++ b/k8s/high-command/postgres-pooler.yaml @@ -0,0 +1,18 @@ +# PgBouncer pooler (rw) in front of high-command-postgres. Adopted from a hand-applied Pooler. +apiVersion: postgresql.cnpg.io/v1 +kind: Pooler +metadata: + name: high-command-postgres-pooler + namespace: high-command +spec: + cluster: + name: high-command-postgres + instances: 2 + type: rw + pgbouncer: + poolMode: transaction + paused: false + parameters: + default_pool_size: "25" + max_client_conn: "1000" + reserve_pool_size: "5"