postgresql-ha-staging.yaml: switch from CLONE_WITH_BASEBACKUP to Patroni standby_cluster (continuous streaming, validated in pgha-test dry run) - closes pre-cutover write gap
ci/woodpecker/push/deploy Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
This commit is contained in:
@@ -14,16 +14,55 @@
|
|||||||
# - Joins the EXISTING postgresql_db-backend overlay (external) — the
|
# - Joins the EXISTING postgresql_db-backend overlay (external) — the
|
||||||
# live single-instance stack is untouched and keeps the "postgresql"
|
# live single-instance stack is untouched and keeps the "postgresql"
|
||||||
# and "db" aliases.
|
# and "db" aliases.
|
||||||
# - patroni-0/patroni-1 bootstrap a NEW cluster (scope: postgres-ha) by
|
# - patroni-0/patroni-1 bootstrap a NEW cluster (scope: postgres-ha) as a
|
||||||
# CLONING the live instance via pg_basebackup (CLONE_WITH_BASEBACKUP,
|
# Patroni STANDBY CLUSTER continuously streaming from the live
|
||||||
# CLONE_HOST=postgresql → resolves to the OLD instance during staging).
|
# instance (standby_cluster, host: postgresql — resolves to the OLD
|
||||||
# CLONE_* is set on BOTH patroni nodes: whichever wins the initial
|
# instance during staging). This REPLACES the earlier
|
||||||
# leader race performs the clone; the other syncs from it.
|
# CLONE_WITH_BASEBACKUP design (one-shot snapshot) — see "REVISION"
|
||||||
|
# note below for why.
|
||||||
# - HAProxy runs WITHOUT production aliases and WITHOUT published ports.
|
# - HAProxy runs WITHOUT production aliases and WITHOUT published ports.
|
||||||
# It only gets those in postgresql-ha-final.yaml at promote time.
|
# It only gets those in postgresql-ha-final.yaml at promote time.
|
||||||
# - Superuser is PGadmin (matches the cloned data's existing superuser,
|
# - Superuser is PGadmin (matches the cloned data's existing superuser,
|
||||||
# same password secret), NOT Spilo's default "postgres".
|
# same password secret), NOT Spilo's default "postgres".
|
||||||
#
|
#
|
||||||
|
# ─── REVISION (2026-08-01): switched from CLONE_WITH_BASEBACKUP to a
|
||||||
|
# Patroni "standby cluster" (continuous streaming) ───
|
||||||
|
#
|
||||||
|
# WHY: CLONE_WITH_BASEBACKUP is a ONE-SHOT snapshot — once pg_basebackup
|
||||||
|
# completes, the new cluster has ZERO further connection to the live
|
||||||
|
# database. Any write landing on production between the snapshot and the
|
||||||
|
# actual traffic cutover would be silently lost — a real risk given ~13
|
||||||
|
# active consumers and a 42GB production database. Patroni's standby_cluster
|
||||||
|
# mode (https://patroni.readthedocs.io/en/latest/standby_cluster.html)
|
||||||
|
# instead makes the new cluster's leader ("standby leader") continuously
|
||||||
|
# stream from the remote primary indefinitely, right up until an explicit
|
||||||
|
# promotion (done by removing the standby_cluster key from DCS config —
|
||||||
|
# there is no dedicated patronictl subcommand for this) — closing the gap
|
||||||
|
# to near-zero. Fully validated end-to-end in the pgha-test dry run
|
||||||
|
# (2026-08-01): bootstrap, continuous live-write propagation to the far
|
||||||
|
# cascade replica in <3s, and a full promote-with-zero-data-loss cycle —
|
||||||
|
# see "ADR-0001 Dry-Run Debugging Log" note for the complete play-by-play.
|
||||||
|
#
|
||||||
|
# ⚠️ NEW PRODUCTION PREREQUISITE THIS INTRODUCES — NOT YET DONE, must
|
||||||
|
# happen before this file can actually be deployed for real: the dry run's
|
||||||
|
# "legacy" stand-in creates a genuine replication role named "standby"
|
||||||
|
# (matching PGUSER_STANDBY below) via a fresh-initdb hook script, since
|
||||||
|
# standby_cluster streaming authenticates using the cluster's real
|
||||||
|
# replication identity (PGUSER_STANDBY/PGPASSWORD_STANDBY), NOT the
|
||||||
|
# PGadmin superuser that CLONE_WITH_BASEBACKUP used. The LIVE
|
||||||
|
# `postgresql_postgresql` container has NO such hook mechanism available
|
||||||
|
# (it's long since initialized, hooks only run on fresh initdb) and does
|
||||||
|
# NOT currently have a "standby" role. This must be created manually
|
||||||
|
# against the live database before real cutover, analogous to how the
|
||||||
|
# pg_hba.conf replication rule was added manually — e.g.:
|
||||||
|
# CREATE ROLE standby WITH REPLICATION LOGIN PASSWORD '<same value as
|
||||||
|
# the postgresql_replication_password secret>';
|
||||||
|
# This is NOT part of this compose file (can't be — it's a one-time SQL
|
||||||
|
# statement against already-running production, not something a compose
|
||||||
|
# file can express) but MUST be tracked and done as an explicit manual
|
||||||
|
# step in the real Phase 3 procedure. See ADR-0001 Dry-Run Debugging Log
|
||||||
|
# note, "Not yet done" section, for status.
|
||||||
|
#
|
||||||
# NOTE: command: blocks use $$(...) not $(...) — Compose's own variable
|
# NOTE: command: blocks use $$(...) not $(...) — Compose's own variable
|
||||||
# interpolation parses $( as an attempted ${VAR} reference and fails with
|
# interpolation parses $( as an attempted ${VAR} reference and fails with
|
||||||
# "invalid interpolation format" / "you may need to escape any $ with
|
# "invalid interpolation format" / "you may need to escape any $ with
|
||||||
@@ -32,9 +71,8 @@
|
|||||||
# block, including inside heredocs and nested quoting — Compose scans the
|
# block, including inside heredocs and nested quoting — Compose scans the
|
||||||
# raw text before any shell/heredoc logic ever runs, so quoting context
|
# raw text before any shell/heredoc logic ever runs, so quoting context
|
||||||
# doesn't exempt anything from this escaping requirement. (Confirmed the
|
# doesn't exempt anything from this escaping requirement. (Confirmed the
|
||||||
# hard way in the pgha-test dry run — see "ADR-0001 Dry-Run Debugging Log"
|
# hard way in the pgha-test dry run — this file originally had the
|
||||||
# note — this file originally had the unescaped $(...) bug, including on
|
# unescaped $(...) bug too, before this standby_cluster rewrite.)
|
||||||
# the CLONE_PASSWORD line which is unique to this staging file.)
|
|
||||||
#
|
#
|
||||||
# NOTE: ETCD3_HOSTS not ETCD_HOSTS — confirmed against zalando/spilo
|
# NOTE: ETCD3_HOSTS not ETCD_HOSTS — confirmed against zalando/spilo
|
||||||
# configure_spilo.py: PATRONI_DCS includes both "etcd" (legacy v2 API,
|
# configure_spilo.py: PATRONI_DCS includes both "etcd" (legacy v2 API,
|
||||||
@@ -42,7 +80,7 @@
|
|||||||
# distinct DCS backends selected by env var prefix. Our etcd containers
|
# distinct DCS backends selected by env var prefix. Our etcd containers
|
||||||
# (v3.5.9) have the v2 API disabled by default, so ETCD_HOSTS causes
|
# (v3.5.9) have the v2 API disabled by default, so ETCD_HOSTS causes
|
||||||
# Patroni to hit /v2 endpoints that 404. ETCD3_HOSTS selects the correct
|
# Patroni to hit /v2 endpoints that 404. ETCD3_HOSTS selects the correct
|
||||||
# v3-API client. (Also originally wrong in this file — fixed after dry run.)
|
# v3-API client.
|
||||||
#
|
#
|
||||||
# NOTE: patroni-0/patroni-1 override bootstrap.post_init via
|
# NOTE: patroni-0/patroni-1 override bootstrap.post_init via
|
||||||
# SPILO_CONFIGURATION (Spilo's documented, supported mechanism for
|
# SPILO_CONFIGURATION (Spilo's documented, supported mechanism for
|
||||||
@@ -65,7 +103,7 @@
|
|||||||
# existence + SUPERUSER) were found and validated end-to-end in the
|
# existence + SUPERUSER) were found and validated end-to-end in the
|
||||||
# pgha-test dry run before being ported here — see the dry-run debugging
|
# pgha-test dry run before being ported here — see the dry-run debugging
|
||||||
# note for full detail. This is CRITICAL for this file specifically since
|
# note for full detail. This is CRITICAL for this file specifically since
|
||||||
# it clones real production data with the real PGadmin superuser.
|
# it streams real production data with the real PGadmin superuser.
|
||||||
# ─────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
version: "3.6"
|
version: "3.6"
|
||||||
|
|
||||||
@@ -150,7 +188,6 @@ services:
|
|||||||
export PGPASSWORD_SUPERUSER="$$(cat /run/secrets/postgresql_password)"
|
export PGPASSWORD_SUPERUSER="$$(cat /run/secrets/postgresql_password)"
|
||||||
export PGPASSWORD_STANDBY="$$(cat /run/secrets/postgresql_replication_password)"
|
export PGPASSWORD_STANDBY="$$(cat /run/secrets/postgresql_replication_password)"
|
||||||
export PATRONI_RESTAPI_PASSWORD="$$(cat /run/secrets/postgresql_patroni_password)"
|
export PATRONI_RESTAPI_PASSWORD="$$(cat /run/secrets/postgresql_patroni_password)"
|
||||||
export CLONE_PASSWORD="$$(cat /run/secrets/postgresql_password)"
|
|
||||||
mkdir -p /scripts
|
mkdir -p /scripts
|
||||||
cat > /scripts/post_init_wrapper.sh <<'WRAP'
|
cat > /scripts/post_init_wrapper.sh <<'WRAP'
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
@@ -171,13 +208,12 @@ services:
|
|||||||
SPILO_CONFIGURATION: |
|
SPILO_CONFIGURATION: |
|
||||||
bootstrap:
|
bootstrap:
|
||||||
post_init: /scripts/post_init_wrapper.sh "zalandos"
|
post_init: /scripts/post_init_wrapper.sh "zalandos"
|
||||||
# Clone-from-live: only runs when PGDATA is empty (first bootstrap).
|
dcs:
|
||||||
# "postgresql" resolves to the OLD single instance during staging.
|
standby_cluster:
|
||||||
CLONE_METHOD: CLONE_WITH_BASEBACKUP
|
host: postgresql
|
||||||
CLONE_SCOPE: legacy-single
|
port: 5432
|
||||||
CLONE_HOST: postgresql
|
create_replica_methods:
|
||||||
CLONE_PORT: "5432"
|
- basebackup_fast_xlog
|
||||||
CLONE_USER: PGadmin
|
|
||||||
secrets:
|
secrets:
|
||||||
- postgresql_password
|
- postgresql_password
|
||||||
- postgresql_replication_password
|
- postgresql_replication_password
|
||||||
@@ -201,7 +237,6 @@ services:
|
|||||||
export PGPASSWORD_SUPERUSER="$$(cat /run/secrets/postgresql_password)"
|
export PGPASSWORD_SUPERUSER="$$(cat /run/secrets/postgresql_password)"
|
||||||
export PGPASSWORD_STANDBY="$$(cat /run/secrets/postgresql_replication_password)"
|
export PGPASSWORD_STANDBY="$$(cat /run/secrets/postgresql_replication_password)"
|
||||||
export PATRONI_RESTAPI_PASSWORD="$$(cat /run/secrets/postgresql_patroni_password)"
|
export PATRONI_RESTAPI_PASSWORD="$$(cat /run/secrets/postgresql_patroni_password)"
|
||||||
export CLONE_PASSWORD="$$(cat /run/secrets/postgresql_password)"
|
|
||||||
mkdir -p /scripts
|
mkdir -p /scripts
|
||||||
cat > /scripts/post_init_wrapper.sh <<'WRAP'
|
cat > /scripts/post_init_wrapper.sh <<'WRAP'
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
@@ -222,11 +257,12 @@ services:
|
|||||||
SPILO_CONFIGURATION: |
|
SPILO_CONFIGURATION: |
|
||||||
bootstrap:
|
bootstrap:
|
||||||
post_init: /scripts/post_init_wrapper.sh "zalandos"
|
post_init: /scripts/post_init_wrapper.sh "zalandos"
|
||||||
CLONE_METHOD: CLONE_WITH_BASEBACKUP
|
dcs:
|
||||||
CLONE_SCOPE: legacy-single
|
standby_cluster:
|
||||||
CLONE_HOST: postgresql
|
host: postgresql
|
||||||
CLONE_PORT: "5432"
|
port: 5432
|
||||||
CLONE_USER: PGadmin
|
create_replica_methods:
|
||||||
|
- basebackup_fast_xlog
|
||||||
secrets:
|
secrets:
|
||||||
- postgresql_password
|
- postgresql_password
|
||||||
- postgresql_replication_password
|
- postgresql_replication_password
|
||||||
|
|||||||
Reference in New Issue
Block a user