6.6 将规约接入统一实验环境
规约若只存在于 repository,就无法约束实际环境;平台若只负责“把 PostgreSQL 装起来”,又无法知道业务对象是否满足合同。Pigsty 与版本化 SQL 在这里承担不同职责:
Pigsty inventory
├─ cluster / instance / service / HBA / pool
├─ role、database、schema 的基础声明
└─ database/role GUC 默认
Versioned SQL
├─ object privileges / default privileges
├─ table / type / constraint / view / function
├─ migration history / schema version
└─ fixture / positive / negative / post-state
Runtime verification
├─ catalog / pg_settings / session
├─ service route / pool behavior
└─ metrics / logs / evidence checksum这三层共同组成统一实验环境。平台声明不能替代业务 migration,migration 成功也不能证明 HAProxy/Pgbouncer 路由正确。
6.6.1 角色、数据库与服务声明
Pigsty 是配置驱动平台:inventory 的 global、cluster、host 层按覆盖顺序形成最终参数,再由 playbook 生成并应用 Patroni、PostgreSQL、Pgbouncer、HAProxy 与相关配置。pg_users 和 pg_databases 允许在 cluster vars 中声明业务身份和数据库。
本章提供一个不含凭据的 pigsty-declaration.example.yml。它是应合并到目标 cluster vars 的片段,不是完整 inventory:
pg_users:
- name: pg36_owner
login: false
superuser: false
createdb: false
createrole: false
replication: false
bypassrls: false
- name: pg36_app
login: true
superuser: false
createdb: false
createrole: false
pgbouncer: true
pool_mode: transaction
- name: pg36_ro
login: true
superuser: false
createdb: false
createrole: false
pgbouncer: true
pool_mode: transaction
pg_databases:
- name: pg36_shop
owner: pg36_owner
encoding: UTF8
locale: C
revokeconn: true
pgbouncer: true
pool_mode: transaction
schemas:
- { name: shop, owner: pg36_owner }
- { name: shop_api, owner: pg36_owner }
- { name: shop_private, owner: pg36_owner }完整样例还包含连接池预算和 database-level timeout/UTC 默认。数值是教学起点,必须按真实 connection budget 与 workload 调整。
为什么先声明 role,再声明 database
PostgreSQL role 属于整个 cluster,不属于单个 database;database owner 在创建 database 时必须已经存在。Pigsty 的 pg_users 又按数组顺序创建,所以样例先创建 NOLOGIN owner,再创建 application/read-only LOGIN role,最后创建由 owner 持有的 database。
LOGIN role 的 credential 没有进入样例。实际 inventory 必须从受控 secret overlay 注入 SCRAM secret 或采用组织认证方案;不能把展开后凭据提交到本书 repository。若直接应用这份无密码片段,role 可以创建,但不能靠密码认证登录——这是有意的 fail-closed,不是可直接上线的完整安全配置。
revokeconn: true 会撤销 PUBLIC CONNECT,并保留 owner/管理/监控等受控入口。pg36_app 与 pg36_ro 的精确 CONNECT、schema USAGE、table/sequence privilege 和 default privilege 仍由 ch01 versioned SQL 授予。这里故意不把所有业务授权改成 Pigsty 内置全局 dbrole_readwrite:本书要验证 pg36_shop 的对象级最小权限,而不是让跨库角色隐式扩大范围。
为什么不把业务 schema 塞进一次性 baseline
Pigsty pg_databases.baseline 会在 database 首次创建时执行 SQL,已有 database 会跳过;encoding、locale、template 等字段又具有创建时不可变的边界。它适合明确的一次性引导,但不能单独承担持续 schema migration。
本书让:
Pigsty: database/role/service 基础存在
SQL chain: ch01 → ch03 → ch04 → 后续版本fresh install 与 upgrade 因此复用同一 migration authority。即使 schemas 已由 Pigsty 创建,SQL 使用 CREATE SCHEMA IF NOT EXISTS 后仍验证 owner/privilege;若同名 schema 形状或 owner 不符合合同,后验会失败,而不是因为“存在”就默认正确。
使用默认 service,而不是再造一个名字
Pigsty v4.4 每个 PostgreSQL cluster 默认提供:
| Service | Port | 本章用途 |
|---|---|---|
primary | 5433 | production read/write,经 primary Pgbouncer |
replica | 5434 | production read-only,经 replica Pgbouncer |
default | 5436 | admin/ETL/direct primary PostgreSQL |
offline | 5438 | OLAP/ETL/个人只读类 direct workload |
pg36_app 的日常 OLTP 连接应使用 primary:5433;受审计 migration、catalog 诊断和本章 SET ROLE gate 使用 default:5436 direct path。两者都指向当前 primary,但 pool/session 语义不同。read-only role 也不能仅凭名字就发送到 replica:调用方要选择 replica service,并接受复制延迟与 read-after-write 语义。
本章无需自定义 pg_services。只有默认 selector、health check、destination 或端口不能表达 workload 时才增加 service,并同时说明 failover、fallback 与容量边界。多一个 service 名不是更安全;没有调用合同的 service 只会增加误路由。
6.6.2 初始化、验证与重置入口
统一环境需要把“基础设施声明”和“书中 SQL”排成可重复顺序。
第一次初始化
先在 Pigsty repository 中把样例片段合并到已确认的目标 cluster。不要照抄 cluster 名;先查看 inventory graph、最终 host vars 和 diff。对于已有 cluster,官方 v4.4 的精确入口是:
bin/pgsql-user <cluster> pg36_owner
bin/pgsql-user <cluster> pg36_app
bin/pgsql-user <cluster> pg36_ro
bin/pgsql-db <cluster> pg36_shop这些命令只是说明 apply 顺序。真正执行前必须:
- 用
-l/wrapper 的 cluster 参数限制到单一已确认目标; - 确认 secret overlay 已生效但不会打印到 evidence;
- 确认同名 role/database 没有另一业务含义;
- 对 immutable database 字段检查现状,不用
state: recreate强制收敛; - 保存 inventory commit、resolved target 和 playbook result。
新 cluster 可以在受控 pgsql.yml -l <cluster> 初始化中创建这些对象;已有 cluster 应用专用 pgsql-user/pgsql-db,不要为了新增一个 database 重新运行无范围的全局 playbook。
然后通过 default:5436 的私有 libpq service 执行书中版本链:
ch01 setup → role/database/schema/privilege baseline
ch03 setup + seed → logical model v0
ch04 migrate → reliable physical model v1
ch04 verify → catalog + data checksum
ch06 all → session + query + baseline quality gate实际目录中各章的 task.sh 固定 action 与 evidence。不要把这些步骤复制成一条不检查中间状态的长 shell command;每个 version boundary 成功后保存 summary,失败时停在已知状态。
每次任务只有一个 action 合同
action 名应表达风险和后置状态:
setup / migrate / seed
verify / observe / negative / review
reset(仅专属可销毁 target)统一入口负责:
- 解析 action,未知值以 usage/exit 64 拒绝;
- 检查依赖工具与
PGSERVICEFILE; - 创建 mode 0700/umask 077 evidence directory;
- 写 source manifest;
- 运行 context guard 和 verify-before;
- 执行 action;
- 即使预期报错,也核对精确 exit/SQLSTATE;
- 写 verify-after 与 machine-readable summary;
- 清理本次启动的精确 worker。
脚本不应根据“这是开发机”自动猜测 database 可以删除。环境分类可以决定是否允许 R1/R2,但 destructive target 与 token 仍要精确。
reset 不属于正常升级路径
ch01/ch03/ch04 的 reset 用于放弃整个教学模型并重建,属于 R2,必须使用章节定义的双重令牌。它不能用于:
- 清理未知生产漂移;
- 让失败 migration 看起来重新成功;
- 在保留价值不明时重建 database;
- 替代 application/schema 兼容回退。
本章没有持久写入,所以不提供 reset。成功的 all 应保证 relation checksum 不变;若 checksum 漂移,正确动作是停下来调查,不是自动调用上一章 reset。
应用流量还要单独验收 pooled path
本章 quality gate 使用 pg36-admin direct service,因为它需要稳定 session、catalog visibility 与 SET ROLE pg36_owner。它没有证明 application 经 primary:5433 的行为。应用交付前还应使用 pg36_app service 测试:
frontend endpoint = primary:5433
effective identity = pg36_app
read/write privilege = exact contract
owner/DDL privilege = denied
transaction pool reuse = no leaked session state
timeout/cancel = driver contract
application_name = attributable这层将在 ch12 的“从数据库到服务”中成为 v1.0 验收项。
6.6.3 配置事实与运行事实分开审查
一次平台变更至少有四类事实:
| 层次 | 证据 | 能证明什么 | 不能证明什么 |
|---|---|---|---|
| Git/inventory | reviewed YAML + commit | 期望状态和变更意图 | 已应用到哪个 target |
| apply | playbook target/diff/result | 某次动作在某批 host 执行 | 所有运行事实持续正确 |
| PostgreSQL | catalog、GUC、SQLSTATE、checksum | 当前数据库实际对象与语义 | 客户端经过哪个 frontend service |
| routing/observability | HAProxy/Pgbouncer state、连接 endpoint、dashboard/log | service 路由、pool 与时间趋势 | 业务不变量全部正确 |
“配置里写了”只能回答第一行。一次严谨审查同时保留 desired、apply 和 actual。
从 YAML 回到 PostgreSQL catalog
pg_users 的后验不是搜索配置文本,而是:
SELECT
rolname,
rolcanlogin,
rolsuper,
rolcreatedb,
rolcreaterole,
rolreplication,
rolbypassrls,
rolconnlimit
FROM pg_catalog.pg_roles
WHERE rolname IN ('pg36_owner', 'pg36_app', 'pg36_ro');database/schema 后验包括 owner、encoding、locale/collation、CONNECT、schema owner/USAGE/CREATE。role membership 在 Pigsty 中可能是 additive;从 inventory 删除一个 role name 不一定等于数据库里自动撤销已有 membership,必须用显式 absent/revoke 和 catalog 后验。
database immutable 参数若与 inventory 不同,不应自动 state: recreate。先把漂移记录为 change,评估数据保留、backup/PITR 和 application downtime,再决定迁移或接受有 expiry 的 waiver。
从参数声明回到生效值和来源
ALTER DATABASE/ROLE SET 通常只影响新 session。检查:
SELECT
name,
setting,
unit,
source,
sourcefile,
pending_restart
FROM pg_catalog.pg_settings
WHERE name IN (
'statement_timeout',
'lock_timeout',
'idle_in_transaction_session_timeout'
);再在目标 role/database 的新连接中 SHOW/current_setting()。pg_settings 的当前 backend 值与 source 能解释本会话,但不能仅凭 postgresql.conf 文件推断覆盖后的结果。pending restart、reload 与新连接边界也必须区分。
从 service 名回到真实路由
连接 primary:5433 时保存:
client requested host/port/service
current_database / session_user / current_user
pg_is_in_recovery()
inet_server_addr / inet_server_port
application_name / backend_start
HAProxy/Pgbouncer service state and timestamppg_is_in_recovery()=false 证明当前 backend 可写 primary,不证明客户端一定经过预期 HAProxy port;客户端 endpoint 证明请求入口,不证明 selector 在未来 failover 始终正确。要将两类事实与 PGSQL Service/Proxy/Pgbouncer dashboard 或 HAProxy state 对齐。
连接 replica:5434 也不能只检查 default_transaction_read_only:健康 selector、实际 recovery state、replication lag 与 fallback policy共同决定读语义。对 read-after-write 敏感的请求通常应继续走 primary,或显式等待/携带一致性标记。
漂移处理不是“以谁为准”一句话
发现 inventory 与 actual 不同时,先分类:
尚未 apply
apply failed/partial
manual hotfix 未回写
运行时临时 SET/override
版本/不可变属性导致不能收敛
检查器读错 target然后选择:
- 重新 apply 并验证;
- 把合法 hotfix 回写 inventory/migration;
- 撤销未经授权的手工漂移;
- 为不可变差异设计迁移;
- 修正检查 target;
- 在有 owner/expiry 的 waiver 中暂时接受。
不能机械地让自动化“配置覆盖运行”,也不能把实际状态反向复制进 Git 就算解决。权威来源取决于对象:cluster/service desired state 通常在 Pigsty inventory,业务 schema version 在 migration ledger,当前故障处置可能暂时以 incident hotfix 为准,但结束后必须回写。
本节验收
把样例接入一个已确认 L1 后,应能提供三组独立证据:
desired:
inventory commit + resolved cluster vars(secret redacted)
applied:
exact cluster target + pgsql-user/db playbook result
actual:
pg_roles / pg_database / schemas / grants / GUC
direct admin quality gate
pooled application service probe只有三组吻合,才能说“规约已经接入环境”。本章实验只完成 direct admin 和 PostgreSQL actual 部分;真正 Pigsty cluster 的 apply 与 pooled application probe必须在读者自己的 L1 中完成并保存 target-specific evidence。
参考资料
- Pigsty v4.4:PostgreSQL Configuration
- Pigsty v4.4:User/Role
- Pigsty v4.4:Managing Users
- Pigsty v4.4:Database
- Pigsty v4.4:Managing Databases
- Pigsty v4.4:Service/Access
- Pigsty v4.4:PGSQL Playbooks
上一节:交付物与质量门 · 返回本章目录 · 下一节:实战:发布规约 baseline v0.1 · 查看全书目录 · 查看索引中心