From bd74aead51cd231e93287b94827cb52546cfc61b Mon Sep 17 00:00:00 2001 From: seonNoh Date: Thu, 24 Sep 2026 00:39:13 +0900 Subject: [PATCH] feat: add PostgreSQL primary/replica apply scripts for the DB VMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WP-09(Terraform 설계서 6.10, 실험·앱 설계서 3.3). - postgres-primary: PostgreSQL 16, conf.d 오버라이드(listen *, ssl on, scram, wal_level replica, slot 10, wal_keep_size 256MB), pg_hba는 hostssl+scram만(app: AWS·GCP 대역, harness: GCP 대역, replicator: 10.20.0.21). 역할 app/replicator/harness(비밀번호는 Secret Manager selfheal-db-password), DB app + 스키마 app.price·app.orders + 시드. harness에 pg_wal_replay_pause/resume·pg_promote EXECUTE와 pg_read_all_stats 부여 (카탈로그 db.replay_resume·db.promote). - postgres-replica: 첫 실행에 pg_basebackup -R -C -S replica1(TLS 필수)로 standby 구성, 이후 실행은 재시작만. --- vm/postgres-primary/README.md | 3 -- vm/postgres-primary/apply.sh | 92 +++++++++++++++++++++++++++++++++++ vm/postgres-replica/README.md | 3 -- vm/postgres-replica/apply.sh | 52 ++++++++++++++++++++ 4 files changed, 144 insertions(+), 6 deletions(-) delete mode 100644 vm/postgres-primary/README.md create mode 100755 vm/postgres-primary/apply.sh delete mode 100644 vm/postgres-replica/README.md create mode 100755 vm/postgres-replica/apply.sh diff --git a/vm/postgres-primary/README.md b/vm/postgres-primary/README.md deleted file mode 100644 index 2ce7ce7..0000000 --- a/vm/postgres-primary/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# vm/postgres-primary - -Placeholder. See the root README for the work package that fills this directory. diff --git a/vm/postgres-primary/apply.sh b/vm/postgres-primary/apply.sh new file mode 100755 index 0000000..2b19dbb --- /dev/null +++ b/vm/postgres-primary/apply.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# db-primary (10.20.0.20): PostgreSQL 16 primary for the app DB (experiment design 3.3, Terraform design 6.10). +# Idempotent: package install, config, roles/schema/seed are all re-runnable. Passwords come from Secret Manager +# (secret selfheal-db-password, one value shared by roles app / replicator / harness in this lab) via the VM SA. +set -euo pipefail +export DEBIAN_FRONTEND=noninteractive +PGV=16 +PRIMARY_IP=10.20.0.20; REPLICA_IP=10.20.0.21; AWS_CIDR=10.10.0.0/16; GCP_CIDR=10.20.0.0/20 + +apt-get update -qq +apt-get install -y -qq postgresql-${PGV} jq >/dev/null +PROJECT="$(curl -sf -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/project/project-id)" +TOKEN="$(curl -sf -H 'Metadata-Flavor: Google' 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token' | jq -r .access_token)" +DB_PASSWORD="$(curl -sf -H "Authorization: Bearer ${TOKEN}" "https://secretmanager.googleapis.com/v1/projects/${PROJECT}/secrets/selfheal-db-password/versions/latest:access" | jq -r .payload.data | base64 -d)" +unset TOKEN +[ -n "$DB_PASSWORD" ] || { echo "empty DB password" >&2; exit 1; } + +CONF=/etc/postgresql/${PGV}/main +# --- postgresql.conf overrides (conf.d is included by the packaged postgresql.conf) --- +install -d -m 755 -o postgres -g postgres "$CONF/conf.d" +cat > "$CONF/conf.d/10-selfheal.conf" < "$CONF/pg_hba.conf" </dev/null +systemctl restart postgresql +sleep 2 + +# --- roles, database, schema, seed (idempotent) --- +sudo -u postgres psql -v ON_ERROR_STOP=1 -v pw="$DB_PASSWORD" <<'SQL' +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='app') THEN CREATE ROLE app LOGIN; END IF; + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='replicator') THEN CREATE ROLE replicator LOGIN REPLICATION; END IF; + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='harness') THEN CREATE ROLE harness LOGIN; END IF; +END $$; +ALTER ROLE app PASSWORD :'pw'; +ALTER ROLE replicator PASSWORD :'pw'; +ALTER ROLE harness PASSWORD :'pw'; +-- harness: the catalogue actions db.replay_resume (replica) and db.promote (design 2.3); read access for checks +GRANT EXECUTE ON FUNCTION pg_wal_replay_pause() TO harness; +GRANT EXECUTE ON FUNCTION pg_wal_replay_resume() TO harness; +GRANT EXECUTE ON FUNCTION pg_promote(boolean, integer) TO harness; +GRANT pg_read_all_stats TO harness; +SELECT 'CREATE DATABASE app OWNER app' WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname='app') \gexec +SQL +sudo -u postgres psql -v ON_ERROR_STOP=1 -d app <<'SQL' +CREATE SCHEMA IF NOT EXISTS app AUTHORIZATION app; +CREATE TABLE IF NOT EXISTS app.price ( + item text PRIMARY KEY, + price numeric(10,2) NOT NULL, + updated_at timestamptz NOT NULL DEFAULT now() +); +CREATE TABLE IF NOT EXISTS app.orders ( + id bigserial PRIMARY KEY, + item text NOT NULL REFERENCES app.price(item), + qty integer NOT NULL CHECK (qty > 0), + status text NOT NULL DEFAULT 'new', + created_at timestamptz NOT NULL DEFAULT now() +); +ALTER TABLE app.price OWNER TO app; ALTER TABLE app.orders OWNER TO app; +ALTER SEQUENCE app.orders_id_seq OWNER TO app; +GRANT USAGE ON SCHEMA app TO harness; GRANT SELECT ON ALL TABLES IN SCHEMA app TO harness; +INSERT INTO app.price (item, price) VALUES ('apple', 120.00), ('banana', 80.00), ('cherry', 450.00), ('durian', 1800.00) + ON CONFLICT (item) DO NOTHING; +INSERT INTO app.orders (item, qty, status) + SELECT 'apple', 2, 'done' WHERE NOT EXISTS (SELECT 1 FROM app.orders); +SQL +unset DB_PASSWORD +echo "--- primary status" +sudo -u postgres psql -Atc "select version()" | cut -d, -f1 +sudo -u postgres psql -Atc "show ssl" +sudo -u postgres psql -d app -Atc "select count(*) from app.price" diff --git a/vm/postgres-replica/README.md b/vm/postgres-replica/README.md deleted file mode 100644 index 373e450..0000000 --- a/vm/postgres-replica/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# vm/postgres-replica - -Placeholder. See the root README for the work package that fills this directory. diff --git a/vm/postgres-replica/apply.sh b/vm/postgres-replica/apply.sh new file mode 100755 index 0000000..338f540 --- /dev/null +++ b/vm/postgres-replica/apply.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# db-replica (10.20.0.21): hot standby of db-primary via streaming replication (Terraform design 6.10; +# PostgreSQL docs 26.2.4/26.2.5). First run: pg_basebackup -R (creates standby.signal + primary_conninfo, slot replica1). +# Re-runs leave an existing standby alone. Password from Secret Manager via the VM SA. +set -euo pipefail +export DEBIAN_FRONTEND=noninteractive +PGV=16 +PRIMARY_IP=10.20.0.20 +DATA=/var/lib/postgresql/${PGV}/main +CONF=/etc/postgresql/${PGV}/main + +apt-get update -qq +apt-get install -y -qq postgresql-${PGV} jq >/dev/null +install -d -m 755 -o postgres -g postgres "$CONF/conf.d" +cat > "$CONF/conf.d/10-selfheal.conf" < "$CONF/pg_hba.conf" <&2; exit 1; } + systemctl stop postgresql + rm -rf "$DATA" + install -d -m 700 -o postgres -g postgres "$DATA" + sudo -u postgres PGPASSWORD="$PGPASSWORD" PGSSLMODE=require pg_basebackup -h "$PRIMARY_IP" -U replicator -D "$DATA" -R -X stream -C -S replica1 -P + unset PGPASSWORD + systemctl start postgresql +fi +sleep 3 +echo "--- replica status" +sudo -u postgres psql -Atc "select pg_is_in_recovery()" +sudo -u postgres psql -Atc "select status, sender_host, slot_name from pg_stat_wal_receiver" 2>/dev/null || true