19.7 实战:L2 部署验收
这次实战不是模拟输出。本书在四台本地 Ubuntu VM 上用 exact Pigsty v4.5.0 部署 PostgreSQL 18,并执行了正式的只读验收。读者可以复跑同一 合同,但不能把硬件与 secret 路径照抄。
19.7.1 核对版本、拓扑、资源、端点和安全入口
风险分级
正常动作:
它们不会:
reset:cluster 是独立 destructive action,不属于正常实验。
正式目标
| address | hostname after convergence | service unit | observed role |
|---|---|---|---|
10.10.10.10 |
pg-meta-1 |
pg-meta |
primary |
10.10.10.11 |
pg-test-1 |
pg-test |
primary |
10.10.10.12 |
pg-test-2 |
pg-test |
streaming replica |
10.10.10.13 |
pg-test-3 |
pg-test |
streaming replica/offline |
先读实验合同
入口:
先确认:
生产环境不能因命令“只读”就跳过访问授权;主机和数据库 fact 本身也可能是 内部信息。
准备私有输入与 evidence 路径
要求:
本章不提供正式 live inventory。公开的
inventory.example.yml 只有结构和
sentinel。
先做 secret-free projection
预期:
检查:
只检查字段,不把输出粘到公共日志。关键值:
direct SSH 避免 alias/forwarding 冒充目标
采集器固定:
禁用本机 config 是本实验对已知 alias 陷阱的防御。StrictHostKeyChecking=no
只适用于这个 disposable、隔离 sandbox;生产必须维护可信 host key,不应
复制这个选择。
正式 evidence 保存 machine ID 的 SHA-256,不保存原值:
hash 只是避免直接扩散标识,不是强匿名化;仍应把 evidence 当内部资料。
host capture 内容
每台生成:
包含:
采集器最初尝试跟随 /pg symlink,目标是 PostgreSQL-owned mode-0700
目录,非特权用户得到 PermissionError。修正后采集 /data backing
filesystem,不跨越 PGDATA 权限边界。好的 audit 不应为了“读事实”扩大
权限或放松数据目录。
host acceptance 与真实例外
四台均:
资源实际值:
原设计推荐 2 vCPU/2 GiB。没有把阈值悄悄改成“全部合格”,而是:
这允许验证部署关系,禁止容量推断。
PostgreSQL capture 内容
采集器通过 direct SSH,在每个 node:
输入固定
postgresql-facts.sql,不拼接 live secret,
不读取业务 row。
生成:
验收:
Patroni 与 endpoint capture
patronictl list --format=json 生成:
Patroni 表中的 healthy state 并不全叫 running:
REST 根端点对 replica 可能返回非 2xx,同时带有效 Patroni JSON。采集器会 保存 HTTP status,并在 body 符合身份协议时标记 reachable,而不是把所有 HTTPError 都误判为网络失败。
endpoint 检查:
只证明 TCP/identity 可观察,不登录业务 service,也不证明路由语义。
执行完整正常实验
正式输出:
任何缺行都不应靠人工补成通过。
19.7.2 从 SQL、主机与 Pigsty 三侧验证同一事实
实际是四个证据面
本节标题说“三侧”,但严谨验收还包括 declaration:
四面回答同一问题:
| 问题 | inventory | host | SQL | Pigsty/endpoint |
|---|---|---|---|---|
| target 是谁 | address/group | hostname/machine hash | cluster name/system ID | member/host |
| 版本 | pg_version |
package | server version | Patroni REST server version |
| 当前 role | bootstrap pg_role |
process/service | pg_is_in_recovery |
Leader/Replica |
| replica healthy | member declared | processes/ports | recovery identity | streaming |
| locale/checksum | desired vars/defaults | OS context | database/settings | n/a |
| service exists | definitions | listeners | n/a | TCP/REST |
某一面冲突就停止,不做多数表决。
declaration 不能替代 observation
inventory 写:
可能出现:
所以 SQL 必须返回 major 18。
同理,pg_checksum: true 或 role default 只表达意图,SHOW data_checksums
才表达运行 cluster 事实。
process/service active 不能替代 SQL
systemctl is-active patroni 返回 active,仍可能:
因此 service state 与 SQL/Patroni identity 同时要求。
SQL recovery role 不能替代 global coordination
两台各自执行:
如果都返回 false,SQL 只能说明两台都不是 standby;不能告诉你哪一台应当 获得流量,也不能自动 fence。Patroni membership/leader、DCS 和 service health 共同揭露冲突。
反例:
validator 要求每个 cluster 恰好一个 leader,并与每台 SQL recovery 状态 一致。
system identifier 连接 SQL 与 topology
假设:
role/status 可能短暂看似正常。system ID 检查会拒绝:
另一个反例是 pg-meta 与 pg-test 共用同一 system ID,也会拒绝。
endpoint open 不等于正确 service
本章所有声明端口 reachable。这是必要条件,不是充分条件。
第 22 章还要验证:
把端口扫描写成“service verification passed”会过度声明。
source checksum 防止验收脚本漂移
capture-manifest.json 保存 capture 时每个 lab source file 的 SHA-256。
review.py 再对当前 source 计算。
这样拒绝:
修改 lab source 后必须重新 capture。
positive test 与 negative test 是两类证据
正向:
反向:
九个反例:
| case | expected code |
|---|---|
| sandbox 声称 production SLO | E_PRODUCTION_CLAIM |
| 两地址复用 machine identity | E_HOST_IDENTITY |
| 混用 OS | E_HOST_UNIFORMITY |
| clock 未同步 | E_CLOCK |
| memory 低于 hard floor | E_RESOURCE_FLOOR |
| checksum off | E_PG_INIT |
| 两 leader | E_TOPOLOGY |
| inventory mode 0644 | E_SECRET_FILE_MODE |
| evidence 导出 secret value | E_SECRET_EXPORT |
negative-cases.json 只修改内存中的 evidence
副本,不破坏 live cluster。
单独复核已捕获 evidence
verify 重新运行 policy;review 检查:
人工 review 仍不可省
自动 validator 不知道:
- owner placeholder 是否已变成真实责任;
- laptop 是否处于受控物理环境;
- business classification 是否正确;
- exception 接受者是否有权;
- package supply chain 是否可信;
- future production SLO 是否合理;
- 当前证据是否用于允许的目的。
机器验证 consistency,人类承担 judgment。
19.7.3 产出基线清单、风险例外与 reset:cluster
evidence bundle
一次 all 产出:
不包含:
evidence directory 应按内部运维资料管理并设置 retention。
sanitized deployment account
它方便读书,不替代 fresh capture。host role、package 和 endpoint 会漂移。
六个风险例外
| exception | 事实 | 阻止的生产结论 |
|---|---|---|
| shared hypervisor | 全部 VM 共用 laptop | 四个独立 failure domains |
| single etcd | DCS 单节点 | control-plane HA |
| single backup target | MinIO/control 共置 | DR independence |
| virtual storage | 未做 IOPS/latency/durability qualification | capacity/durability |
| inventory secrets | 临时 0600 inventory | production secret lifecycle |
| resource floor | 三节点低于推荐 2C/2G | sizing/concurrency |
exception 必须有:
本章 JSON 记录前四类核心字段;真实生产审批系统还要补责任与到期。
验收决策
正式结果:
不是:
reset:cluster 为什么存在
可重复实验必须说明如何回到起点。但删除 cluster:
所以 reset 是 destructive exercise,不是 cleanup convenience。
本章提供
reset-cluster.sh,但正式运行没有执行:
reset 多重 guard
需要显式设置:
然后才可能:
脚本还会:
- 检查 exact v4.5.0 marker;
- 检查 inventory mode 0600;
- 要求新 evidence path;
- 先执行 fresh read-only
all; - 将四台当前 machine hash 与预先 review 的 allowlist 比较;
- 要求
/dev/tty再输入 exact token; - 先移除
pg-test,后移除pg-meta。
任何 guard 缺失,exit 77,什么都不删除。
machine allowlist 不能由 reset 当场自我批准
machine-identity-allowlist.example.json
只有 sentinel。真实 allowlist 应:
若 reset 先读取当前机器、再自动把当前值当 allowlist,identity guard 就没有 外部 authority。
不要在 production override safeguard
Pigsty removal playbook 支持 pg_safeguard。production inventory 应显式
启用保护。override 是 emergency/destructive authority,不应复制到普通
runbook、CI 或 alias。
本书脚本的 guard 也不能让 production reset 变安全:
target classification、data ownership、recovery、change approval 与现场 判断仍然优先。发现任何可能是 production,立即停止。
本章结束时保留环境
为了第 20–22 章:
保留 accepted baseline,下一章才能比较 fault 前后。
handoff 包含:
读者自检
完成本章后,应能回答:
- 为什么 deployment
rc=0不等于 production ready? - 哪些初始化事实必须从 SQL 读取?
- 为什么四个 IP 需要 machine identity?
- 为什么 one leader + two replica 还不能给出 HA RTO?
- 为什么 endpoint open 不证明 routing?
- 如何在不泄露 inventory 的情况下 review topology?
- 六个 exception 各阻止什么结论?
- 为什么 reset 不属于
all?
若任何答案只能是“因为 Pigsty 默认这样”,还没有完成环境基线。
上一节:用声明式清单交付两个服务单元 · 返回本章目录 · 下一章:狡兔三窟:高可用拓扑与容灾目标 · 查看全书目录 · 查看索引中心