跳至内容

12.6 部署与接入 `pg36_shop`

部署数据库应用不是把一条 URL 放进环境变量。完整交付关系是:

role identity and privilege
  + database/schema contract
  + credential delivery
  + service endpoint and routing semantics
  + application/pool configuration
  + readiness and business evidence
  + rollback/reconnect procedure

Pigsty 提供 role/database 声明、PgBouncer、HAProxy service 与监控;应用团队仍要声明自己使用哪个入口、依赖什么 pool mode、允许多少并发,以及如何证明业务合同成立。

12.6.1 角色、数据库、服务与凭据声明

Owner 与 runtime 分离

本书延续第 4、6 章角色:

pg36_owner
  NOLOGIN
  object owner
  migration effective role

pg36_app
  LOGIN
  runtime DML only
  no CREATEDB/CREATEROLE/SUPERUSER/REPLICATION/BYPASSRLS

pg36_ro
  LOGIN
  reviewed read path

应用绝不能用 owner 连接。否则:

  • schema injection/DDL defect 的 blast radius 扩大;
  • owner 可能绕过 RLS;
  • migration 与业务 activity 无法区分;
  • secret 泄露可修改所有 objects;
  • readiness 的 current_user 失去保护价值。

本章 setup 最终断言:

current_user=pg36_app
has_schema_privilege(CREATE)=false
has_table_privilege(DELETE)=false

“最小权限”必须由 catalog 证明,不能只看 YAML。

Pigsty declaration

声明示例 中的关键部分:

pg_users:
  - name: pg36_app
    login: true
    superuser: false
    createdb: false
    createrole: false
    replication: false
    bypassrls: false
    connlimit: 40
    pgbouncer: true
    pool_mode: transaction
    pool_connlimit: 32

pg_databases:
  - name: pg36_shop
    owner: pg36_owner
    revokeconn: true
    pgbouncer: true
    pool_mode: transaction
    pool_size: 32
    pool_reserve: 8
    pool_connlimit: 64

这些数字是教学起点,不是容量答案。需要按:

app replicas and MaxConns
PgBouncer pool partitioning by user/database
PostgreSQL connection budget
workload hold time
HA/failover headroom

重新计算。

Pigsty declaration 负责创建/管理 cluster-level identity;对象级:

GRANT
ALTER DEFAULT PRIVILEGES
schema marker
named constraints
SECURITY DEFINER
fixture/migration

仍应进入 versioned SQL 与 code review。不要把所有权限散落在临时 psql 历史里。

Credential 是输入,不是源码

样例只要求:

PG36_DATABASE_URL

但不记录它。生产建议:

  • secret store/inventory overlay 生成 runtime file;
  • file owner 是 service account,mode 0600;
  • 不进 Git、artifact、process args、日志;
  • role credential 可轮换;
  • 支持 overlap/dual credential 时有明确窗口;
  • TLS verification 与 CA/hostname 按环境配置;
  • PgBouncer auth 与 PostgreSQL role 同步路径已验证。

systemd unit 示例 从:

/etc/pg36-api/runtime.env

读取变量。示例 URL 故意没有密码;部署系统负责填充 secret。不要把:

ExecStart=... --database-url 'postgres://user:password@...'

写进 process list。

Connection string 也要版本化非秘密部分

应记录:

host/service DNS
port
database
user
sslmode
target_session_attrs if used
connect timeout
application_name
query mode
pool bounds

秘密值单独管理。发布 evidence 可以保存“参数名与非秘密身份”,不保存 expanded URL。

12.6.2 通过连接池和服务端点接入

Pigsty 默认服务语义

Pigsty v4.4 默认:

serviceporttarget
primary5433read/write primary via PgBouncer 6432
replica5434read-only replicas via PgBouncer 6432
default5436direct primary PostgreSQL 5432
offline5438direct offline/replica analytical path

因此:

online application → primary :5433
reviewed DDL/admin → default :5436

不是“应用永远只能用 5433”的宇宙规则:Pigsty 允许修改 service destination 或自定义 services。运行手册必须保存目标集群的实际配置,不能仅凭端口推断。

为什么 migration 用 direct path

DDL、session-level diagnostic、某些 bulk operation 或 pooler admin 不适合 transaction pool。direct service:

  • session identity 稳定;
  • prepared/session state 边界简单;
  • DDL error 与 backend 更直接;
  • 不与在线 client pool 混在同一入口。

这不等于 direct path 可绕过审核。它应只对 migration/admin role 开放,设置:

application_name
lock_timeout
statement_timeout
target guard
change identity
evidence directory

应用运行角色不需要 direct 管理权限。

应用侧仍然使用 pgxpool

PgBouncer 不是 Go 并发安全 connection handle 的替代。应用侧 pool:

  • 复用 client connections;
  • 限制每个 process 同时进入数据库路径的请求;
  • 暴露 acquisition queue;
  • 管理 connection lifetime/health;
  • 将 request context 传播到 acquire。

但两层池不要无限叠加:

100 pods × MaxConns 100
→ 10,000 PgBouncer clients

即使 PostgreSQL 只有 64 server connections,app、network、PgBouncer fd/memory 和排队仍可能过载。

晋级时使用不变的 service suite

本地验证连接:

service=pg36-admin user=pg36_app
direct PostgreSQL socket

manifest 明确:

validation_path=direct-postgresql
pooler_validation=not-run

Pigsty 晋级不能把字段手改成 runtask.sh 保留 admin PGSERVICE 用于 setup/observer,并允许用独立的 PG36_APP_DATABASE_URL 指向实际 primary service:

cd static/labs/ch12
export PGSERVICEFILE=/secure/path/pg_service.conf
export PGSERVICE=pg36-admin
export PG36_APP_DATABASE_URL='postgres://pg36_app@pg-demo:5433/pg36_shop?sslmode=verify-full'
./task.sh all

不设置该变量时,本地教学路径从 named admin service 派生 user=pg36_app 的 direct connection。设置后,manifest 只会声明:

validation_path=operator-supplied-application-endpoint
pooler_validation=behavior-run-config-identity-required

这表示完整行为矩阵确实经过该 endpoint,但 endpoint 自称是 5433 仍不能证明其内部 pool mode/config。promotion job 应显式接收两个独立目标:

admin direct URL
application primary/PgBouncer URL

并在证据中保存:

  • resolved service/port;
  • PgBouncer version;
  • database/user pool_mode;
  • max_prepared_statements
  • TLS/auth identity;
  • query mode;
  • primary writable identity;
  • full HTTP/failure suite。

不要为了“复用脚本”让 admin DDL 也走 app pool,或让 app smoke 使用 owner。

Failover 不由连接串自动变安全

Pigsty service 可在 primary 变化后把新连接路由到新主库。但 in-flight transaction 可能:

  • 连接断开;
  • rollback;
  • commit outcome unknown;
  • 请求超时后在新 primary 重试。

应用仍需要:

  • idempotency key;
  • finite retry;
  • writable readiness;
  • no remote side effect in transaction callback;
  • failover fault test;
  • reconnect/backoff jitter;
  • old primary fencing 由 HA layer 保证。

“HA service”解决目标发现与路由,不替应用解决命令重放语义。

12.6.3 用平台指标验证部署,而非只看进程存活

Deployment gate

部署后按层验证:

process:
  exact binary/config checksum
  liveness

service:
  DNS/VIP/HAProxy target
  TLS/auth
  primary writable identity

pool:
  pgxpool bounds
  PgBouncer mode and slots
  no unexpected waiting/cancel spike

database:
  current_database/current_user
  schema marker
  privileges
  query/constraint contract

business:
  idempotent order/payment smoke
  outbox and trace

operations:
  logs/metrics/dashboard
  rollback and reconnect

systemctl is-active 只覆盖第一层的一小部分。

Pigsty 观察面

发布窗口至少同时看:

  • PostgreSQL overview/cluster/instance;
  • active sessions 与 wait events;
  • query statistics 与 error/latency;
  • PgBouncer clients, servers, pools, wait;
  • HAProxy/service health;
  • WAL rate 与 replica lag;
  • CPU、memory、disk、network;
  • application R/E/D/S(rate/error/duration/saturation)。

具体 dashboard 名称随 Pigsty 版本调整,运行手册应链接目标环境实际页面,不在代码里硬编码一串脆弱 panel ID。

用关系做验收

不设跨环境绝对 golden:

p95 < 12.3 ms
acquire count = 24
backend PID = 12345

应设合同关系和 SLO:

same idempotency key does not add writes
pool saturation fails within budget
liveness does not consume DB slot
readiness removes unready instance
cancel clears backend
SQLSTATE ratio remains within error budget
no idle-in-transaction leak
primary change does not duplicate business effect
tail latency meets declared SLO under declared load

其中最后两项必须在目标环境实测。

发布后观察而非立即 contract

应用 100% 新版本不等于旧连接/worker 已退出。保留观察窗口:

old application_name/query identity=0
old deployment replicas=0
queue/cron/ETL inventory reviewed
new error/tail latency stable
pool wait stable
rollback artifact still usable

满足后才让第 11 章 contract gate 进入审批。数据库 DDL 与应用 rollout 的 observability 要合并在同一个 UTC window 中。

失败时停止什么

信号首要动作
wrong DB/user/replicareadiness fail,立即停止流量
schema marker missing停应用晋级,检查 migration state
PgBouncer mode/config unknown不发布 prepared/session-dependent path
pool wait 上升、DB 未饱和降 app admission/查 pooler,不先加 index
DB lock/IO saturated停 rollout/回退流量,按第 8 章诊断
idempotency duplicate停止写入晋级,保存 ledger/outbox
unknown commit during failover同 key 查询/重放,不换 key
logs 泄露 secret安全事件处置与 credential rotation

本节检查表

  • owner NOLOGIN,runtime 非 owner;
  • Pigsty user/database declaration 无明文 secret;
  • object grants 由 versioned SQL 管理;
  • catalog 证明 app 无 CREATE/DELETE/BYPASSRLS;
  • primary/default/offline service 职责明确;
  • 实际 service destination/pool_mode 已验证;
  • app pool 与 PgBouncer pool 联合预算;
  • app path 与 admin path 使用不同 role/endpoint;
  • deployment artifact 不记录 expanded URL;
  • readiness 验证 writable primary;
  • full business/failure suite 通过实际 pooler path;
  • Pigsty、PgBouncer、PostgreSQL 与 app 同窗观察;
  • failover unknown commit 通过 idempotency 处理;
  • 旧 artifact/worker identity 清零后才 contract。

参考资料


上一节:服务级可观测性 · 返回本章目录 · 下一节:实战:交付应用闭环与规约 v1.0 · 查看全书目录 · 查看索引中心

最后更新于