跳至内容
6.6 将规约接入统一实验环境

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_userspg_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_apppg36_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 默认提供:

ServicePort本章用途
primary5433production read/write,经 primary Pgbouncer
replica5434production read-only,经 replica Pgbouncer
default5436admin/ETL/direct primary PostgreSQL
offline5438OLAP/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)

统一入口负责:

  1. 解析 action,未知值以 usage/exit 64 拒绝;
  2. 检查依赖工具与 PGSERVICEFILE
  3. 创建 mode 0700/umask 077 evidence directory;
  4. 写 source manifest;
  5. 运行 context guard 和 verify-before;
  6. 执行 action;
  7. 即使预期报错,也核对精确 exit/SQLSTATE;
  8. 写 verify-after 与 machine-readable summary;
  9. 清理本次启动的精确 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/inventoryreviewed YAML + commit期望状态和变更意图已应用到哪个 target
applyplaybook target/diff/result某次动作在某批 host 执行所有运行事实持续正确
PostgreSQLcatalog、GUC、SQLSTATE、checksum当前数据库实际对象与语义客户端经过哪个 frontend service
routing/observabilityHAProxy/Pgbouncer state、连接 endpoint、dashboard/logservice 路由、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 timestamp

pg_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。

参考资料


上一节:交付物与质量门 · 返回本章目录 · 下一节:实战:发布规约 baseline v0.1 · 查看全书目录 · 查看索引中心

最后更新于