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 procedurePigsty 提供 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 同步路径已验证。
/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 默认:
| service | port | target |
|---|---|---|
| primary | 5433 | read/write primary via PgBouncer 6432 |
| replica | 5434 | read-only replicas via PgBouncer 6432 |
| default | 5436 | direct primary PostgreSQL 5432 |
| offline | 5438 | direct 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 socketmanifest 明确:
validation_path=direct-postgresql
pooler_validation=not-runPigsty 晋级不能把字段手改成 run。task.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 reconnectsystemctl 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/replica | readiness 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。
参考资料
- Pigsty:Service / Access
- Pigsty:PgBouncer Administration
- Pigsty:PostgreSQL User
- Pigsty:PostgreSQL Database
- Pigsty:PostgreSQL Dashboards
- PostgreSQL 18:libpq Connection Parameters
上一节:服务级可观测性 · 返回本章目录 · 下一节:实战:交付应用闭环与规约 v1.0 · 查看全书目录 · 查看索引中心