railiance-platform/docs/postgresql-ha.md
codex 1eb5fa51ed
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Has been cancelled
Finish architecture-cleanup RAILIANCE-WP-0016 T05
Item 13 is the restore RESOURCE-WP-0002 already proved. Item 14 retires
the dead Bitnami postgresql-ha deploy path and gates Valkey until a
consumer exists. Item 17 publishes the versioned S3 consumer-interface
index.
2026-08-15 14:43:44 +02:00

158 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PostgreSQL HA — retired
**Status:** retired 2026-08-15 (`RAILIANCE-WP-0016` item 14)
**Superseded by:** CloudNativePG clusters in `databases` (`apps-pg`,
`forgejo-db`, `platform-pg`, and the other CNPG instances).
**Do not deploy.** `make pg-deploy` is fail-closed.
The Bitnami `postgresql-ha` (repmgr + pgpool) stack never ran as a live
S3 service on railiance01. There is no `platform` namespace and no
matching pods. Giteas historical subchart in S2 was replaced by
`forgejo-db`. Keep this file as the retirement record and the
pgpool-password incident note. New databases go through `docs/apps-pg.md`
or `rapp-postgres`.
---
## Architecture
```
Apps (S5)
└── pgpool (load balancer / connection pooler)
├── postgresql-0 [Primary — repmgr]
├── postgresql-1 [Standby — repmgr]
└── postgresql-2 [Standby — repmgr]
```
- **pgpool-II** distributes reads across standbys, routes writes to primary
- **repmgr** handles automatic failover if the primary disappears
- All pods in `platform` namespace; app pods connect via pgpool service
## Connection string pattern
```
postgresql://DBUSER:DBPASS@postgresql-ha-pgpool.platform.svc.cluster.local:5432/DBNAME
```
Replace `DBUSER`, `DBPASS`, `DBNAME` with the database-specific credentials.
---
## Initial deployment
### Prerequisites
- `railiance-cluster` converged (`make smoke` passes)
- SOPS age key accessible: `sops -d helm/postgresql-ha-values.sops.yaml` returns plaintext
- `helm repo add bitnami https://charts.bitnami.com/bitnami && helm repo update` done on the node
### Steps
```bash
# 1. Ensure the platform namespace exists
kubectl create namespace platform --dry-run=client -o yaml | kubectl apply -f -
# 2. Deploy (from railiance-platform/)
make pg-deploy
# 3. Verify
make pg-status
# Expected: 3 postgresql pods + 1 pgpool pod, all Running
# 4. Smoke test
make smoke
```
---
## Creating a new database for an app
```bash
# Connect via pgpool
kubectl exec -it -n platform \
$(kubectl get pod -n platform -l app.kubernetes.io/component=pgpool -o name | head -1) \
-- psql -U postgres
# Inside psql:
CREATE DATABASE myapp;
CREATE USER myapp WITH PASSWORD 'strong-password';
GRANT ALL PRIVILEGES ON DATABASE myapp TO myapp;
\c myapp
GRANT ALL ON SCHEMA public TO myapp;
\q
```
Add the user password to the app's own secrets (managed in the app's repo,
not here). The connection string for the app will be:
```
postgresql://myapp:strong-password@postgresql-ha-pgpool.platform.svc.cluster.local:5432/myapp
```
---
## Password rotation
1. Update the password in the plaintext values template
2. Re-encrypt: `sops -e -i helm/postgresql-ha-values.sops.yaml`
3. Upgrade: `make pg-deploy`
4. Update the app's connection secret to match
5. Rolling restart the app pods to pick up the new connection
---
## pgpool-password — critical note
The `postgresql.pgpoolPassword` value in the Helm chart maps to the
`pgpool-password` key in the `postgresql-ha-postgresql` Kubernetes Secret.
The pgpool container mounts this key at startup; if it is absent, pgpool
enters CrashLoopBackOff with **no log output**.
**This was the root cause of the 2026-03-10 incident (RAIL-BS-WP-0003).**
Always verify after `helm upgrade`:
```bash
kubectl get secret -n platform postgresql-ha-postgresql \
-o jsonpath='{.data.pgpool-password}' | base64 -d && echo
# Must print a non-empty string
```
---
## HA failover test
Per Decision D3, any change to this service requires a passing failover test:
```bash
# From railiance-cluster/
make test-ha-failover GITEA_URL=https://<gitea-hostname>
```
The test kills the primary PostgreSQL pod and asserts:
1. repmgr promotes a standby within 60s
2. All pods return to Running within 120s
3. pgpool returns to Running (catches the missing-key bug)
---
## Backup
Platform backup (PostgreSQL logical dump) is handled by the `railiance-backup`
tool in this repo:
```bash
make backup
```
This produces an age-encrypted dump uploaded to Nextcloud. For cluster-level
backup (etcd, kubeconfig), see `railiance-cluster/`.
---
## Scaling to 3 nodes (ThreePhoenix)
When Railiance02 and Railiance03 join the cluster:
1. Switch StorageClass from `local-path` to `longhorn` in the values file
2. Change `postgresql.podAntiAffinityPreset` from `soft` to `hard`
3. Run `make pg-deploy` — Helm rolling update spreads pods across nodes
4. Run `make test-ha-failover` to confirm HA is genuine (not just replicated on one node)