19.7 实战:L2 部署验收
这次实战不是模拟输出。本书在四台本地 Ubuntu VM 上用 exact Pigsty v4.4.0 部署 PostgreSQL 18,并执行了正式的只读验收。读者可以复跑同一 合同,但不能把硬件与 secret 路径照抄。
19.7.1 核对版本、拓扑、资源、端点和安全入口
风险分级
正常动作:
project / capture / verify / review / all
risk = L0 read-only against deployed service
local writes = selected evidence directory only它们不会:
deploy packages
render remote config
restart/fail over service
run DDL/DML
take/restore backup
remove cluster
export secret valuesreset:cluster 是独立 destructive action,不属于正常实验。
正式目标
target pg36-l2-vagrant
platform local Vagrant Linux sandbox
Pigsty exact v4.4.0 tag
PostgreSQL 18.4 observed
hosts 4
PostgreSQL clusters 2
members 4| 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 |
先读实验合同
入口:
先确认:
你控制的是 disposable local sandbox
没有生产数据/流量
inventory 是 private mode-0600 文件
四个地址可以 direct SSH
当前只执行 read-only acceptance生产环境不能因命令“只读”就跳过访问授权;主机和数据库 fact 本身也可能是 内部信息。
准备私有输入与 evidence 路径
export PG36_CH19_INVENTORY=/absolute/private/path/pg36.yml
export PG36_EVIDENCE_DIR=/absolute/private/path/evidence/ch19-run
export PG36_SSH_USER=vagrant要求:
inventory file mode = 0600
evidence path outside source control
inventory contains no production credential
SSH user has intended read/sudo ability on this sandbox本章不提供正式 live inventory。公开的
inventory.example.yml 只有结构和
sentinel。
先做 secret-free projection
static/labs/ch19/task.sh project预期:
status=projection-ok
secrets=redacted检查:
python3 -m json.tool \
"$PG36_EVIDENCE_DIR/inventory-projection.json"只检查字段,不把输出粘到公共日志。关键值:
status=secret-free-projection
source.mode_octal=0600
source.secret_values_exported=0
version=v4.4.0
pg_version=18
pg_locale=C.UTF-8
host_count=4direct SSH 避免 alias/forwarding 冒充目标
采集器固定:
ssh -F /dev/null \
-o BatchMode=yes \
-o UserKnownHostsFile=/dev/null \
-o StrictHostKeyChecking=no \
vagrant@10.10.10.10 ...禁用本机 config 是本实验对已知 alias 陷阱的防御。StrictHostKeyChecking=no
只适用于这个 disposable、隔离 sandbox;生产必须维护可信 host key,不应
复制这个选择。
正式 evidence 保存 machine ID 的 SHA-256,不保存原值:
four 64-hex hashes
all distinct
address/hostname exact matchhash 只是避免直接扩散标识,不是强匿名化;仍应把 evidence 当内部资料。
host capture 内容
每台生成:
hosts/<address>.json包含:
OS/kernel/architecture/virtualization
CPU/model/NUMA count
memory/swap/THP/overcommit
root and data backing filesystem/free bytes
timezone/NTP
addresses/DNS/firewall service state/listening ports
selected systemd services
selected package versions采集器最初尝试跟随 /pg symlink,目标是 PostgreSQL-owned mode-0700
目录,非特权用户得到 PermissionError。修正后采集 /data backing
filesystem,不跨越 PGDATA 权限边界。好的 audit 不应为了“读事实”扩大
权限或放松数据目录。
host acceptance 与真实例外
四台均:
Linux / Ubuntu 24.04.x / aarch64
Etc/UTC + NTP synchronized
swap = 0
THP = never
root free >= 8 GiB
required services active资源实际值:
pg-meta-1 2 vCPU, about 3.8 GiB RAM
pg-test-* 1 vCPU, about 1.9 GiB RAM each原设计推荐 2 vCPU/2 GiB。没有把阈值悄悄改成“全部合格”,而是:
hard disposable-sandbox acceptance floor = 1 vCPU / 1.8 GiB
recommended teaching floor = 2 vCPU / 2 GiB
EX19-LAB-RESOURCE-FLOOR = accepted exception这允许验证部署关系,禁止容量推断。
PostgreSQL capture 内容
采集器通过 direct SSH,在每个 node:
sudo -n -iu postgres \
psql -X -qAt --dbname=postgres --set=ON_ERROR_STOP=1输入固定
postgresql-facts.sql,不拼接 live secret,
不读取业务 row。
生成:
postgres/<address>.json验收:
PG 18
checksums on
block 8192
WAL segment 16777216
UTF8 / builtin / C.UTF-8
Etc/UTC
SCRAM verifier setting
SSL on
SQL recovery role
system identifier relationPatroni 与 endpoint capture
patronictl list --format=json 生成:
patroni/pg-meta.json
patroni/pg-test.jsonPatroni 表中的 healthy state 并不全叫 running:
leader state=running
replica state=streamingREST 根端点对 replica 可能返回非 2xx,同时带有效 Patroni JSON。采集器会 保存 HTTP status,并在 body 符合身份协议时标记 reachable,而不是把所有 HTTPError 都误判为网络失败。
endpoint 检查:
5432 direct postgres
5433 primary service
5434 replica service
5436 primary direct service
5438 offline service
8008 Patroni REST只证明 TCP/identity 可观察,不登录业务 service,也不证明路由语义。
执行完整正常实验
static/labs/ch19/task.sh all正式输出:
status=captured
target=pg36-l2-vagrant
hosts=4
secrets=redacted
production_approval=false
status=ok
target=pg36-l2-vagrant
deployment=pigsty-v4.4.0-postgresql-18
hosts=4-distinct
topology=pg-meta-1-primary+pg-test-1-primary-2-replicas
counterexamples=9-rejected
sandbox_l2=accepted-with-exceptions
production_ch19_gate=pending
mutation=none任何缺行都不应靠人工补成通过。
19.7.2 从 SQL、主机与 Pigsty 三侧验证同一事实
实际是四个证据面
本节标题说“三侧”,但严谨验收还包括 declaration:
inventory declared intent
host machine and service substrate
SQL PostgreSQL internal fact
Pigsty Patroni/service implementation observation四面回答同一问题:
| 问题 | 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 写:
pg_version: 18可能出现:
package install failed
old server still running
wrong inventory targeted
host variable override
partial rerun所以 SQL 必须返回 major 18。
同理,pg_checksum: true 或 role default 只表达意图,SHOW data_checksums
才表达运行 cluster 事实。
process/service active 不能替代 SQL
systemctl is-active patroni 返回 active,仍可能:
PostgreSQL startup failed and Patroni loops
member in catchup
wrong data directory
wrong cluster
timeline divergence
port occupied by another process因此 service state 与 SQL/Patroni identity 同时要求。
SQL recovery role 不能替代 global coordination
两台各自执行:
SELECT pg_is_in_recovery();如果都返回 false,SQL 只能说明两台都不是 standby;不能告诉你哪一台应当 获得流量,也不能自动 fence。Patroni membership/leader、DCS 和 service health 共同揭露冲突。
反例:
declare-two-live-leaders -> E_TOPOLOGYvalidator 要求每个 cluster 恰好一个 leader,并与每台 SQL recovery 状态 一致。
system identifier 连接 SQL 与 topology
假设:
三个 Patroni member 名和 address 都对
但 pg-test-3 是错误初始化的新 data directoryrole/status 可能短暂看似正常。system ID 检查会拒绝:
members of one cluster have different system identifiers另一个反例是 pg-meta 与 pg-test 共用同一 system ID,也会拒绝。
endpoint open 不等于正确 service
本章所有声明端口 reachable。这是必要条件,不是充分条件。
第 22 章还要验证:
5433 write reaches current primary
5434 is read-only and uses intended replicas
5438 selects offline members
PgBouncer pooling mode/session state
TLS/auth/database/role
drain/reconnect/failover behavior把端口扫描写成“service verification passed”会过度声明。
source checksum 防止验收脚本漂移
capture-manifest.json 保存 capture 时每个 lab source file 的 SHA-256。
review.py 再对当前 source 计算。
这样拒绝:
先 capture
后改 requirements/validator
用旧 evidence 宣称新规则通过修改 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
export PG36_EVIDENCE_DIR=/absolute/existing/evidence/ch19-run
static/labs/ch19/task.sh verify
static/labs/ch19/task.sh reviewverify 重新运行 policy;review 检查:
source checksums
positive report identity/counts/decision
negative code set
four distinct identities
resource exception exact hosts
endpoint evidence
sanitized deployment account
production boundary
reset_executed=false人工 review 仍不可省
自动 validator 不知道:
- owner placeholder 是否已变成真实责任;
- laptop 是否处于受控物理环境;
- business classification 是否正确;
- exception 接受者是否有权;
- package supply chain 是否可信;
- future production SLO 是否合理;
- 当前证据是否用于允许的目的。
机器验证 consistency,人类承担 judgment。
19.7.3 产出基线清单、风险例外与 reset:cluster
evidence bundle
一次 all 产出:
inventory-projection.json
capture-manifest.json
hosts/
10.10.10.10.json
10.10.10.11.json
10.10.10.12.json
10.10.10.13.json
postgres/
<four member facts>
patroni/
pg-meta.json
pg-test.json
endpoints.json
validation-report.json
negative-report.json
review.txt不包含:
live inventory
password/token/private key
customer rows
full deployment private log
raw machine ID
production approvalevidence directory应按内部运维资料管理并设置 retention。
sanitized deployment account
exact source tag/commit
generator command
inventory mode and secret export count
bootstrap/Ansible version
deploy command and safe recap
observed acceptance summary
explicit non-claims
reset_executed=false它方便读书,不替代 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 必须有:
ID
scope
reason
impact
owner/acceptor
expiry/review
remediation
claim blocked本章 JSON 记录前四类核心字段;真实生产审批系统还要补责任与到期。
验收决策
正式结果:
{
"sandbox_l2": "accepted-with-exceptions",
"production_ch19_gate": "pending",
"next_gate": "ch20-ha"
}不是:
production-ready
HA passed
RPO/RTO achieved
backup verified
capacity approved
security compliantreset:cluster 为什么存在
可重复实验必须说明如何回到起点。但删除 cluster:
停止服务
删除 PostgreSQL data
可能删除 backup state
改变 DCS/service/monitor registration
不可由正常 validation 自动恢复所以 reset 是 destructive exercise,不是 cleanup convenience。
本章提供
reset-cluster.sh,但正式运行没有执行:
deployment-run.boundary.reset_executed=falsereset 多重 guard
需要显式设置:
export PG36_RESET_TARGET='pg36-l2-vagrant/pg-meta+pg-test'
export PG36_CONFIRM_RESET='RESET_CH19_L2_SANDBOX'
export PG36_NO_PRODUCTION_DATA=yes
export PG36_CLIENTS_DRAINED=yes
export PG36_PIGSTY_HOME=/absolute/exact/pigsty-v4.4.0
export PG36_CH19_INVENTORY=/absolute/private/pg36.yml
export PG36_MACHINE_ID_ALLOWLIST=/absolute/private/machine-ids.json
export PG36_RESET_EVIDENCE_DIR=/absolute/new/pre-reset-evidence然后才可能:
static/labs/ch19/task.sh reset:cluster脚本还会:
- 检查 exact v4.4.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 应:
从一次已接受 evidence 提取
由 operator review address/hostname/asset
存放 source control 外
限制权限
在 VM rebuild 后重新批准若 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 章:
do not reset
do not fail over yet
do not restore
do not benchmark to saturation保留 accepted baseline,下一章才能比较 fault 前后。
handoff 包含:
exact source/deployment account
fresh accepted evidence
six exception IDs
known bootstrap role
system-ID relation
no reset performed
pending kernel reboot observation
production gate pending读者自检
完成本章后,应能回答:
- 为什么 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 默认这样”,还没有完成环境基线。
上一节:用声明式清单交付两个服务单元 · 返回本章目录 · 下一章:狡兔三窟:高可用拓扑与容灾目标 · 查看全书目录 · 查看索引中心