RAILIANCE-WP-0003 T02-T06: provision shared apps-pg cnpg cluster

Adds the shared CloudNativePG cluster apps-pg for S5 application
databases:
- helm/apps-pg-cluster.yaml — Cluster CR, PG 16, 1 instance, 10Gi
- helm/apps-pg-networkpolicies.yaml — egress-to-kube-api +
  ingress-from-cnpg-operator + label-based ingress opt-in
  (railiance.io/postgres-client=apps-pg)
- helm/apps-pg-secret.sops.yaml.template — bootstrap credential
  template (encrypt with SOPS before committing the real .sops.yaml)
- Makefile targets: apps-pg-deploy, apps-pg-status (with cnpg-plugin
  fallback), apps-pg-shell (apps_admin/apps_meta), apps-pg-logs
- docs/apps-pg.md (codex) — consumer onboarding contract clarifying
  the CNPG 1.28 role/database lifecycle boundary

Also fixes helm/gitea-db-cluster.yaml: spec.postgresql.version is not
a valid CNPG v1 field (strict decoding rejects it). Replaced with
spec.imageName matching the live cluster (postgresql:18.1-system-trixie)
so make db-deploy is a no-op instead of an apply rejection.

Live state at commit time: Cluster apps-pg in healthy state, primary
apps-pg-1 Running, smoke-tested via psql from a labeled temp ns.

Co-Authored-By: codex <noreply@openai.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 04:50:40 +02:00
parent 626ad7f3a7
commit 1a5b65a338
6 changed files with 262 additions and 5 deletions

44
helm/apps-pg-cluster.yaml Normal file
View File

@@ -0,0 +1,44 @@
---
# Shared CNPG Cluster for S5 application databases (RAILIANCE-WP-0003).
# Owned by railiance-platform (S3). Operator lives in cnpg-system.
#
# Apply: kubectl apply -f helm/apps-pg-cluster.yaml
# Status: kubectl cnpg status apps-pg -n databases (requires cnpg kubectl plugin)
# or: kubectl get cluster apps-pg -n databases -o wide
#
# Pre-condition: apps-pg-credentials Secret must exist in databases ns.
# See helm/apps-pg-secret.sops.yaml.template for the bootstrap recipe.
#
# Consumer onboarding: see docs/apps-pg.md. The bootstrap role apps_admin
# and meta DB apps_meta exist only to anchor the cluster; per-app roles
# and databases are added through the documented onboarding contract.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: apps-pg
namespace: databases
labels:
app.kubernetes.io/name: apps-pg
app.kubernetes.io/component: database
app.kubernetes.io/managed-by: manual
railiance.io/layer: s3-platform
railiance.io/role: shared-apps-database
spec:
instances: 1 # bump to 3 when node RAM > 8GB
imageName: ghcr.io/cloudnative-pg/postgresql:16
storage:
size: 10Gi
bootstrap:
initdb:
database: apps_meta
owner: apps_admin
secret:
name: apps-pg-credentials
# HA replica + connection pooler are deferred (RAILIANCE-WP-0003 Notes):
# managed:
# services:
# additional:
# - selectorType: rw
# serviceTemplate:
# metadata:
# name: apps-pg-pooler-rw

View File

@@ -0,0 +1,71 @@
# NetworkPolicies for the shared apps-pg cnpg cluster (RAILIANCE-WP-0003).
# The databases namespace has a default-deny-all policy; each cluster
# needs explicit egress-to-kube-api, ingress-from-cnpg-operator, and
# ingress-from-app-namespace policies.
#
# Unlike gitea-db (which hard-codes `default` as the consumer ns), this
# triplet uses a label-based opt-in: any namespace carrying the label
# `railiance.io/postgres-client=apps-pg` may connect on TCP/5432. The
# shared cluster cannot know its consumer namespaces in advance, so it
# expects each consumer to add this label as part of its onboarding.
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-kube-api-apps-pg
namespace: databases
spec:
podSelector:
matchLabels:
cnpg.io/cluster: apps-pg
policyTypes:
- Egress
egress:
- ports:
- port: 6443
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-from-cnpg-operator-apps-pg
namespace: databases
spec:
podSelector:
matchLabels:
cnpg.io/cluster: apps-pg
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: cnpg-system
ports:
- port: 5432
protocol: TCP
- port: 8000
protocol: TCP
- port: 9187
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-from-app-namespaces-apps-pg
namespace: databases
spec:
podSelector:
matchLabels:
cnpg.io/cluster: apps-pg
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
railiance.io/postgres-client: apps-pg
podSelector: {}
ports:
- port: 5432
protocol: TCP

View File

@@ -0,0 +1,25 @@
# Template for the apps-pg-credentials Secret.
# DO NOT commit this file with real credentials.
# Encrypt with: sops -e -i helm/apps-pg-secret.sops.yaml
# Apply with: kubectl apply -f <(sops -d helm/apps-pg-secret.sops.yaml)
#
# This Secret is consumed by the bootstrap.initdb stanza of
# helm/apps-pg-cluster.yaml and only exists to create the platform
# bootstrap role `apps_admin` and meta DB `apps_meta`. It is NOT a
# runtime credential for any S5 application — those are issued per
# consumer through the onboarding contract in docs/apps-pg.md.
---
apiVersion: v1
kind: Secret
metadata:
name: apps-pg-credentials
namespace: databases
labels:
app.kubernetes.io/name: apps-pg
app.kubernetes.io/component: database-bootstrap
app.kubernetes.io/managed-by: manual
railiance.io/layer: s3-platform
type: kubernetes.io/basic-auth
stringData:
username: apps_admin
password: REPLACE_WITH_PASSWORD # encrypt with SOPS before committing

View File

@@ -24,8 +24,10 @@ metadata:
railiance.io/layer: s3-platform
spec:
instances: 1 # bump to 3 when node RAM > 8GB
postgresql:
version: "16"
# spec.postgresql.version is not a real CNPG v1 field; use imageName.
# Live cluster was upgraded to PG 18.1; match the live state so
# `make db-deploy` (kubectl apply) is a no-op rather than a rejection.
imageName: ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie
storage:
size: 10Gi
bootstrap: