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 구성, 이후 실행은 재시작만.
93 lines
4.6 KiB
Bash
Executable File
93 lines
4.6 KiB
Bash
Executable File
#!/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
|
|
# managed by selfheal-lab-gitops vm/postgres-primary/apply.sh
|
|
listen_addresses = '*'
|
|
ssl = on
|
|
password_encryption = 'scram-sha-256'
|
|
wal_level = replica
|
|
max_wal_senders = 10
|
|
max_replication_slots = 10
|
|
wal_keep_size = '256MB'
|
|
hot_standby = on
|
|
log_line_prefix = '%m [%p] %u@%d '
|
|
CONF
|
|
chown postgres:postgres "$CONF/conf.d/10-selfheal.conf"
|
|
# --- pg_hba: TLS + scram only for network clients ---
|
|
cat > "$CONF/pg_hba.conf" <<HBA
|
|
# managed by selfheal-lab-gitops vm/postgres-primary/apply.sh
|
|
local all postgres peer
|
|
local all all peer
|
|
hostssl app app ${AWS_CIDR} scram-sha-256
|
|
hostssl app app ${GCP_CIDR} scram-sha-256
|
|
hostssl all harness ${GCP_CIDR} scram-sha-256
|
|
hostssl replication replicator ${REPLICA_IP}/32 scram-sha-256
|
|
HBA
|
|
chown postgres:postgres "$CONF/pg_hba.conf"; chmod 640 "$CONF/pg_hba.conf"
|
|
systemctl enable --now postgresql >/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"
|