281 lines
11 KiB
YAML
281 lines
11 KiB
YAML
# ─────────────────────────────────────────────────────────────────────────
|
|
# postgresql/cutover/test/pgha-dryrun.yaml — DISPOSABLE dry-run stack
|
|
#
|
|
# Validates the ADR-0001 cutover mechanics WITHOUT touching production:
|
|
# - own overlay network (pgha-test_db-backend), own stack name
|
|
# - throwaway "legacy" postgres:17 seeded with marker data, carrying the
|
|
# in-network alias "postgresql" (mirrors prod CLONE_HOST)
|
|
# - same etcd/patroni/haproxy topology, images, and env wiring as the
|
|
# real staging file — including clone-from-live via pg_basebackup
|
|
# - reuses the real Docker secrets (read-only mounts; harmless)
|
|
# - disposable data dirs under /volume1/docker/PostgreSQL/dryrun/
|
|
#
|
|
# Deploy: docker stack deploy -c <this file> pgha-test
|
|
# Teardown: docker stack rm pgha-test && rm -rf /volume1/docker/PostgreSQL/dryrun
|
|
#
|
|
# NOTE: command: blocks use $$(...) not $(...) — Compose's own variable
|
|
# interpolation parses $( as an attempted ${VAR} reference and fails with
|
|
# "invalid interpolation format" / "you may need to escape any $ with
|
|
# another $". $$ escapes to a literal $ for the shell at runtime. This
|
|
# applies uniformly to EVERY literal $ character anywhere in a command:
|
|
# block, including inside heredocs and nested quoting — Compose scans the
|
|
# raw text before any shell/heredoc logic ever runs, so quoting context
|
|
# doesn't exempt anything from this escaping requirement.
|
|
#
|
|
# NOTE: ETCD3_HOSTS not ETCD_HOSTS — confirmed against zalando/spilo
|
|
# configure_spilo.py: PATRONI_DCS includes both "etcd" (legacy v2 API,
|
|
# python-etcd client) and "etcd3" (v3 API, python-etcd3 client) as
|
|
# 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
|
|
# Patroni to hit /v2 endpoints that 404. ETCD3_HOSTS selects the correct
|
|
# v3-API client.
|
|
#
|
|
# NOTE: "legacy" needs a pg_hba.conf rule permitting REPLICATION-type
|
|
# connections (distinct from normal client connections), fixed via a
|
|
# /docker-entrypoint-initdb.d/ hook script (official extension point).
|
|
# "trust" is acceptable ONLY because this container is fully disposable.
|
|
# See note "ADR-0001 Addendum — pg_hba.conf replication prerequisite
|
|
# discovered in dry run" for the real production prerequisite this exposes.
|
|
#
|
|
# NOTE: patroni-0/patroni-1 override bootstrap.post_init via
|
|
# SPILO_CONFIGURATION (Spilo's documented, supported mechanism for
|
|
# overriding any generated Patroni config — configure_spilo.py deep-merges
|
|
# user-supplied SPILO_CONFIGURATION on top of its own generated config).
|
|
# This is needed because Spilo's own /scripts/post_init.sh hardcodes
|
|
# "ALTER VIEW ... OWNER TO postgres" with no way to parameterize that role
|
|
# name via env vars. Since our superuser is PGUSER_SUPERUSER=PGadmin (to
|
|
# match production), there is no role literally named "postgres" in the
|
|
# cloned data, so Spilo's unmodified script fails with
|
|
# 'ERROR: role "postgres" does not exist'. The override points
|
|
# bootstrap.post_init at our own wrapper script instead, which creates a
|
|
# harmless, idempotent, NOLOGIN "postgres" role first, then execs Spilo's
|
|
# real, UNMODIFIED post_init.sh with all original arguments passed through
|
|
# — Zalando's script itself is never patched or forked. This affects real
|
|
# production too (superuser there is also PGadmin, not postgres) and must
|
|
# be carried into the real staging/production compose files as well.
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
version: "3.6"
|
|
|
|
services:
|
|
|
|
# Stand-in for the production single-instance postgres (clone source)
|
|
legacy:
|
|
image: public.ecr.aws/docker/library/postgres:17
|
|
hostname: db
|
|
entrypoint: ["/bin/sh", "-c"]
|
|
command:
|
|
- |
|
|
mkdir -p /docker-entrypoint-initdb.d
|
|
cat > /docker-entrypoint-initdb.d/zz-enable-replication.sh <<'EOF'
|
|
#!/bin/sh
|
|
echo "host replication all all trust" >> "$$PGDATA/pg_hba.conf"
|
|
EOF
|
|
chmod +x /docker-entrypoint-initdb.d/zz-enable-replication.sh
|
|
exec docker-entrypoint.sh postgres
|
|
environment:
|
|
POSTGRES_USER: PGadmin
|
|
POSTGRES_PASSWORD_FILE: /run/secrets/postgresql_password
|
|
secrets:
|
|
- postgresql_password
|
|
volumes:
|
|
- /volume1/docker/PostgreSQL/dryrun/legacy:/var/lib/postgresql/data
|
|
networks:
|
|
db-backend:
|
|
aliases:
|
|
- postgresql
|
|
- db
|
|
deploy:
|
|
placement:
|
|
constraints:
|
|
- node.hostname == docker-2
|
|
|
|
etcd-1:
|
|
image: quay.io/coreos/etcd:v3.5.9
|
|
hostname: etcd-1
|
|
command:
|
|
- etcd
|
|
- --name=etcd-1
|
|
- --data-dir=/etcd-data
|
|
- --initial-advertise-peer-urls=http://etcd-1:2380
|
|
- --listen-peer-urls=http://0.0.0.0:2380
|
|
- --listen-client-urls=http://0.0.0.0:2379
|
|
- --advertise-client-urls=http://etcd-1:2379
|
|
- --initial-cluster=etcd-1=http://etcd-1:2380,etcd-2=http://etcd-2:2380,etcd-3=http://etcd-3:2380
|
|
- --initial-cluster-state=new
|
|
- --initial-cluster-token=pgha-test-etcd
|
|
volumes:
|
|
- /volume1/docker/PostgreSQL/dryrun/etcd-1:/etcd-data
|
|
networks:
|
|
- db-backend
|
|
deploy:
|
|
placement:
|
|
constraints:
|
|
- node.hostname == docker-1
|
|
|
|
etcd-2:
|
|
image: quay.io/coreos/etcd:v3.5.9
|
|
hostname: etcd-2
|
|
command:
|
|
- etcd
|
|
- --name=etcd-2
|
|
- --data-dir=/etcd-data
|
|
- --initial-advertise-peer-urls=http://etcd-2:2380
|
|
- --listen-peer-urls=http://0.0.0.0:2380
|
|
- --listen-client-urls=http://0.0.0.0:2379
|
|
- --advertise-client-urls=http://etcd-2:2379
|
|
- --initial-cluster=etcd-1=http://etcd-1:2380,etcd-2=http://etcd-2:2380,etcd-3=http://etcd-3:2380
|
|
- --initial-cluster-state=new
|
|
- --initial-cluster-token=pgha-test-etcd
|
|
volumes:
|
|
- /volume1/docker/PostgreSQL/dryrun/etcd-2:/etcd-data
|
|
networks:
|
|
- db-backend
|
|
deploy:
|
|
placement:
|
|
constraints:
|
|
- node.hostname == docker-2
|
|
|
|
etcd-3:
|
|
image: quay.io/coreos/etcd:v3.5.9
|
|
hostname: etcd-3
|
|
command:
|
|
- etcd
|
|
- --name=etcd-3
|
|
- --data-dir=/etcd-data
|
|
- --initial-advertise-peer-urls=http://etcd-3:2380
|
|
- --listen-peer-urls=http://0.0.0.0:2380
|
|
- --listen-client-urls=http://0.0.0.0:2379
|
|
- --advertise-client-urls=http://etcd-3:2379
|
|
- --initial-cluster=etcd-1=http://etcd-1:2380,etcd-2=http://etcd-2:2380,etcd-3=http://etcd-3:2380
|
|
- --initial-cluster-state=new
|
|
- --initial-cluster-token=pgha-test-etcd
|
|
volumes:
|
|
- /volume1/docker/PostgreSQL/dryrun/etcd-3:/etcd-data
|
|
networks:
|
|
- db-backend
|
|
deploy:
|
|
placement:
|
|
constraints:
|
|
- node.hostname == docker-3
|
|
|
|
patroni-0:
|
|
image: ghcr.io/zalando/spilo-17:4.0-p3
|
|
hostname: patroni-0
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
export PGPASSWORD_SUPERUSER="$$(cat /run/secrets/postgresql_password)"
|
|
export PGPASSWORD_STANDBY="$$(cat /run/secrets/postgresql_replication_password)"
|
|
export PATRONI_RESTAPI_PASSWORD="$$(cat /run/secrets/postgresql_patroni_password)"
|
|
export CLONE_PASSWORD="$$(cat /run/secrets/postgresql_password)"
|
|
mkdir -p /scripts
|
|
cat > /scripts/post_init_wrapper.sh <<'WRAP'
|
|
#!/bin/bash
|
|
set -e
|
|
psql -d "$$2" -v ON_ERROR_STOP=1 -c 'DO $$do$$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = $$x$$postgres$$x$$) THEN CREATE ROLE postgres; END IF; END $$do$$;'
|
|
exec /scripts/post_init.sh "$$@"
|
|
WRAP
|
|
chmod +x /scripts/post_init_wrapper.sh
|
|
exec /bin/sh /launch.sh init
|
|
environment:
|
|
SCOPE: pgha-test
|
|
PATRONI_NAME: patroni-0
|
|
ETCD3_HOSTS: '"etcd-1:2379","etcd-2:2379","etcd-3:2379"'
|
|
PGUSER_SUPERUSER: PGadmin
|
|
PGUSER_STANDBY: standby
|
|
PATRONI_RESTAPI_USERNAME: patroni
|
|
PGROOT: /home/postgres/pgdata/pgroot
|
|
CLONE_METHOD: CLONE_WITH_BASEBACKUP
|
|
CLONE_SCOPE: legacy-single
|
|
CLONE_HOST: postgresql
|
|
CLONE_PORT: "5432"
|
|
CLONE_USER: PGadmin
|
|
SPILO_CONFIGURATION: |
|
|
bootstrap:
|
|
post_init: /scripts/post_init_wrapper.sh "zalandos"
|
|
secrets:
|
|
- postgresql_password
|
|
- postgresql_replication_password
|
|
- postgresql_patroni_password
|
|
volumes:
|
|
- /volume1/docker/PostgreSQL/dryrun/patroni-0:/home/postgres/pgdata
|
|
networks:
|
|
- db-backend
|
|
deploy:
|
|
placement:
|
|
constraints:
|
|
- node.labels.pg-role == primary
|
|
|
|
patroni-1:
|
|
image: ghcr.io/zalando/spilo-17:4.0-p3
|
|
hostname: patroni-1
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
export PGPASSWORD_SUPERUSER="$$(cat /run/secrets/postgresql_password)"
|
|
export PGPASSWORD_STANDBY="$$(cat /run/secrets/postgresql_replication_password)"
|
|
export PATRONI_RESTAPI_PASSWORD="$$(cat /run/secrets/postgresql_patroni_password)"
|
|
export CLONE_PASSWORD="$$(cat /run/secrets/postgresql_password)"
|
|
mkdir -p /scripts
|
|
cat > /scripts/post_init_wrapper.sh <<'WRAP'
|
|
#!/bin/bash
|
|
set -e
|
|
psql -d "$$2" -v ON_ERROR_STOP=1 -c 'DO $$do$$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = $$x$$postgres$$x$$) THEN CREATE ROLE postgres; END IF; END $$do$$;'
|
|
exec /scripts/post_init.sh "$$@"
|
|
WRAP
|
|
chmod +x /scripts/post_init_wrapper.sh
|
|
exec /bin/sh /launch.sh init
|
|
environment:
|
|
SCOPE: pgha-test
|
|
PATRONI_NAME: patroni-1
|
|
ETCD3_HOSTS: '"etcd-1:2379","etcd-2:2379","etcd-3:2379"'
|
|
PGUSER_SUPERUSER: PGadmin
|
|
PGUSER_STANDBY: standby
|
|
PATRONI_RESTAPI_USERNAME: patroni
|
|
PGROOT: /home/postgres/pgdata/pgroot
|
|
CLONE_METHOD: CLONE_WITH_BASEBACKUP
|
|
CLONE_SCOPE: legacy-single
|
|
CLONE_HOST: postgresql
|
|
CLONE_PORT: "5432"
|
|
CLONE_USER: PGadmin
|
|
SPILO_CONFIGURATION: |
|
|
bootstrap:
|
|
post_init: /scripts/post_init_wrapper.sh "zalandos"
|
|
secrets:
|
|
- postgresql_password
|
|
- postgresql_replication_password
|
|
- postgresql_patroni_password
|
|
volumes:
|
|
- /volume1/docker/PostgreSQL/dryrun/patroni-1:/home/postgres/pgdata
|
|
networks:
|
|
- db-backend
|
|
deploy:
|
|
placement:
|
|
constraints:
|
|
- node.labels.pg-role == replica
|
|
|
|
haproxy:
|
|
image: haproxy:2.9-alpine
|
|
hostname: haproxy
|
|
volumes:
|
|
- /volume1/docker/compose-files/postgresql/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
|
networks:
|
|
- db-backend
|
|
deploy:
|
|
mode: global
|
|
|
|
networks:
|
|
db-backend:
|
|
attachable: true
|
|
driver: overlay
|
|
|
|
secrets:
|
|
postgresql_password:
|
|
external: true
|
|
postgresql_replication_password:
|
|
external: true
|
|
postgresql_patroni_password:
|
|
external: true
|