开天辟地:环境规划与部署基线
19 开天辟地:环境规划与部署基线
上卷回答了 PostgreSQL 能做什么;下卷从一个更苛刻的问题开始:
我们准备把什么服务,交付给谁,承诺到什么程度;声明的版本、主机、 初始化与拓扑,是否真的存在?
本章先写需求与资源合同,再冻结 PostgreSQL 初始化选择,最后用 exact Pigsty v4.5.0 在四台 Linux VM 上完成 PostgreSQL 18 部署和 L2 沙箱验收。正式 结果是“带六项例外的沙箱通过”,不是生产批准。
本章目标
读完并完成实验后,你应当能够:
- 从业务损失、数据分类、owner、workload 与 RPO/RTO 写服务需求;
- 将 CPU、memory、storage、network 与可观察饱和/故障条件连接;
- 建立 OS baseline,而不是照抄 sysctl 模板;
- 冻结并验证 locale/provider、encoding、checksum、page/WAL、auth 等 PostgreSQL 初始化契约;
- 严格区分 node、instance、database cluster、HA cluster、database、 service、pool 与 DCS;
- 用 secret-safe Pigsty inventory 声明两个 service unit;
- 从 inventory、host、SQL、Patroni/service 四面交叉验收;
- 区分 sandbox acceptance、exception 与 production gate;
- 为 destructive reset 建立显式 guard,而不把它混入正常检查。
前置与后续
前置:
- 第 18 章 PostgreSQL 数据平台与替代边界 提供 service catalog、 capability placement 与下卷 evidence gate;
- 读者应已掌握 Linux 与 SQL;
- 不要求预先掌握 PostgreSQL HA、Pigsty 或 Ansible internals。
后续:
- 第 20 章 高可用拓扑与容灾目标 使用本章保留 baseline 验证 election、fencing、RTO 与数据损失边界;
- 第 21 章验证 backup/restore;
- 第 22 章验证 service routing、pooling 与 client semantics;
- 后续容量、安全、变更和升级 gate 不由本章安装结果代替。
学习路径
前三节解决“为什么、需要什么、机器是什么”;19.4–19.6 解决“哪些选择必须 提前冻结、如何声明”;19.7 只读验证声明与事实是否一致。
正式实验结果
六项例外:
这些例外不是脚注;它们逐项阻止 failure-domain、DCS、DR、durability、 secret lifecycle 与 capacity 的生产结论。
本章目录
19.1 先写服务需求
19.2 计算、内存、存储与网络
19.3 操作系统与主机基线
19.4 版本与数据库初始化契约
19.5 拓扑、命名与故障域
19.6 用声明式清单交付两个服务单元
19.7 实战:L2 部署验收
实验入口
lab-contract.md:风险、输入、动作、验收边界;requirements.json:机器可验证的需求;baseline-v2.0-sandbox.json: 接受决定与非生产边界;inventory.example.yml:无真实凭据的结构示例;task.sh:project/capture/verify/review/all;deployment-adr.md:架构决定;deployment-run.json:无 secret 的执行摘要。
正常 all 是只读验收,不包含 deploy、failover、restore 或 reset。
本章最重要的判断
如果只记住一个方法,就记住:
同一事实必须由合适的 authority 证明;差异必须被拒绝或登记为有范围的 exception,不能被一句“看起来正常”吞掉。
上一章:万法归宗:PostgreSQL 数据平台与替代边界 · 返回下卷导读 · 下一章:狡兔三窟:高可用拓扑与容灾目标 · 查看全书目录 · 查看索引中心
19.1 先写服务需求
部署的第一行不应是:
而应是:
如果这些问题没有答案,CPU、节点数、同步复制和备份频率都只能靠猜。
19.1.1 业务重要性、数据分类与所有者
先定义业务能力
pg36_shop 提供的不是“一个 database”,而是:
不同能力的故障损失不同:
| 能力 | 失败影响 | 可接受降级 |
|---|---|---|
| 创建订单 | 直接收入/履约风险 | 拒绝比重复创建安全 |
| 查询支付状态 | 财务与客服风险 | 不可用旧缓存猜测 |
| 商品描述 | 转化下降 | 可短时有标签陈旧 |
| 搜索排序 | 发现能力下降 | 可回退简单检索 |
| 分析报表 | 决策延迟 | 可显示 last watermark |
| 图片读取 | 体验下降 | placeholder/重试 |
一个统一 database 可以承载它们,但 service objective 不能只写一个“重要”。
业务重要性需要损失模型
常用分级:
分级必须连接动作:
| 项 | critical | standard | development |
|---|---|---|---|
| owner/on-call | 24×7 明确 | 支持窗口明确 | 工作时间 |
| HA | 多故障域并演练 | 按目标设计 | 可无 |
| backup | 跨域、频繁演练 | 标准恢复 | 可选且声明 |
| change | 强审批/回退 | 标准流程 | 自助边界 |
| capacity | 高 headroom | 正常 headroom | best effort |
| incident | 快速升级 | 标准升级 | issue |
若分级只改变标签颜色,就没有价值。
数据分类决定安全与恢复边界
分类至少包含:
对每类记录:
- 数据 owner;
- 合法用途;
- 可访问角色;
- 是否可复制到 dev/test;
- 加密与审计要求;
- 地域限制;
- retention;
- 删除时限;
- backup 中如何处理;
- incident 通知义务。
本章正式 sandbox 只允许:
这是硬边界。VM 看起来像生产拓扑,也不能把生产数据“临时导入测试”。
数据 owner 与平台 owner 不同
建议至少区分:
| 角色 | 责任 |
|---|---|
| business/data owner | 数据用途、正确性、分类、保留、损失 |
| service owner | SLO、错误预算、依赖、发布、事件 |
| database platform | PostgreSQL/Pigsty、HA、备份、接入、容量 |
| security/privacy | 威胁、控制、审计、合规 |
| application team | schema/SQL、连接、重试、兼容、流量 |
| incident commander | 事件期协调和决策 |
一个人可以兼任,责任不能消失。
owner 要能作决定
在事故中,DBA 可以说:
但通常不能独自决定哪种业务损失更小。需要提前指定有权选择:
“负责人:数据库团队”太宽泛。最终要映射到值班角色、升级路径和替代人。
数据清单要包含派生副本
第 18 章已经定义:
部署规划不能只列 primary 数据目录。数据流 inventory 应记录:
| 副本 | authority | freshness | retention | deletion | rebuild |
|---|---|---|---|---|---|
| replica | PostgreSQL | replay lag | current | follows WAL | base backup |
| backup | historical recovery | backup/WAL | policy | retention expiry | restore |
| cache | derived | TTL/version | eviction | tombstone/TTL | source refill |
| event bus | delivery log | publish lag | broker policy | workflow | replay/snapshot |
| lake/search | projection | watermark | generation | tombstone | rebuild |
这些副本会影响存储、网络、密钥和故障域规划。
分类要进入 inventory 与 evidence,但不要泄密
配置可保存:
不应把:
写入基线报告。
本章的 inventory projection 只输出安全 allowlist,记录:
它证明审计过程知道 secret 存在,同时不把值复制到 evidence。
未知 owner 是阻断项
没有 owner 的 service 不应进入生产。原因很现实:
- 谁批准 maintenance?
- 谁判断数据正确?
- 谁接 incident 电话?
- 谁接受 RPO?
- 谁决定退役?
- 谁支付容量?
可以在 sandbox 用 placeholder,但 production gate 必须把 placeholder 映射到 真实责任人/组和升级路径。
pg36_shop 的当前需求身份
本章
requirements.json
写的是:
所以它可以验收部署流程,不能通过真实 pg-ha-standard 的 owner/data gate。
19.1.2 负载形态、增长、峰谷和批处理窗口
“OLTP”不是容量需求
同为 OLTP,可能分别是:
需要一份 workload inventory:
| 维度 | 例 |
|---|---|
| transaction classes | browse/order/pay/admin |
| read/write ratio | 分类别 |
| statements/transaction | 分布 |
| rows touched | P50/P95/P99 |
| connection behavior | pool、active、idle |
| latency | P50/P95/P99/timeout |
| concurrency | arrival、active、parallel |
| WAL | bytes/s 与 burst |
| temp | bytes/query 与 aggregate |
| locks | wait、deadlock、long xact |
| maintenance | vacuum/index/backup |
第 26 章才正式做容量曲线,本章先确保环境规划有输入。
交易、搜索、空间与分析分开建模
pg36_shop 的四类负载形状:
如果只用总 QPS,分析扫描和订单写入会互相隐藏。
每类要记录:
峰值不是日均乘一个系数
峰值来源:
真实 peak envelope 至少有:
一分钟 10 倍峰值与持续 4 小时 3 倍峰值需要不同资源和降级。
流量转移后的单节点峰值
三节点 topology 正常时,读流量可分散;一台 replica 故障后:
[ load_{remaining}
\frac{total\ eligible\ load}{remaining\ eligible\ capacity} ]
若两个 replica 平时各 50%,失去一个后另一个可能接近 100%,也可能 fallback 到 primary。容量必须按 failure state 规划,不只按 happy path。
同理,failover 后:
- 新 primary 承接全部写;
- cache 冷;
- client 重连;
- replica 重新追赶;
- backup/maintenance 可能仍在;
- 旧 primary 需要重建。
增长要分逻辑与物理
逻辑增长:
物理增长:
一个粗略存储模型:
[ capacity = (heap + toast + indexes + free\ space) \times replica\ factor
- backup/archive
- maintenance/upgrade\ headroom ]
不能直接拿业务 CSV 大小乘副本数。
使用增长曲线,不只用线性外推
记录:
容量到达时间:
[ T_{exhaust}
\frac{usable\ capacity - current\ usage - required\ headroom} {growth\ rate} ]
增长率有区间时给出 earliest/expected,而不是一个虚假精确日期。
批处理窗口是一项共享资源预约
批任务包括:
每项保存:
| 字段 | 意义 |
|---|---|
| earliest start / deadline | 可运行窗口 |
| duration distribution | 不只平均 |
| CPU/I/O/WAL/temp | 资源 |
| locks/snapshot | 并发影响 |
| retry | 是否会叠加 |
| freshness | 延迟后果 |
| owner | 谁停止/恢复 |
| conflict priority | 与交易冲突时谁让路 |
“晚上跑”不是窗口。跨时区业务可能没有真正夜间。
维护和业务峰值要画在同一时间轴
建议按 UTC 画一周:
你可能发现:
所谓低谷实际上是数据库最忙时段。
负载可回放性
为了比较环境,保存:
只保存一条 pgbench -c 100 命令不足以代表业务。
sandbox 的资源结论边界
本章为 Vagrant sandbox 建议每台至少:
这些只是教学环境的建议下限,不是 pg36_shop 的生产容量规格。正式实验中,
三台 pg-test VM 只有 1 个 vCPU、约 1.9 GiB 内存;部署虽然完成,但必须以
EX19-LAB-RESOURCE-FLOOR 记录偏差,不能把成功运行反推为资源充足。
四台 VM 共享一台 laptop 的:
因此任何 benchmark 都不能外推生产。
需求表中的 unknown
本章刻意不为以下项目造数字:
它们进入第 24、26、27 章。部署基线的职责是让 unknown 可见,并阻止默认值被 误报为需求。
19.1.3 可用性、RPO、RTO 与维护窗口
四个概念先分开
它们相关,但不能互相替代。
三节点自动 failover 可能有较短可用性中断,却无法恢复昨天误删的数据;一天 一次 full backup 可能可恢复,却不提供当前 primary HA。
先定义成功请求
可用性分母和成功必须明确:
如果数据库返回 200/row 但金额错误,不应算可用。
“几个九”换算为时间只是直觉
以 30 天窗口为例:
| 目标 | 粗略不可用预算 |
|---|---|
| 99% | 7h 12m |
| 99.9% | 43m 12s |
| 99.95% | 21m 36s |
| 99.99% | 4m 19s |
真正 SLO 仍由事件型 SLI 计算,不能只用服务器 uptime。
RPO 必须绑定故障场景
示例:
“RPO=0”若没有场景和确认语义,就是不完整承诺。
应用还要知道:
RTO 从开始点到结束点
RTO 的起点可能是:
终点可能是:
如果不定义,两个团队报告的 RTO 可以相差整个检测与验证阶段。
建议分解:
[ RTO = detection
- decision
- execution
- validation
- traffic\ restoration ]
每段都能优化,也都可能失败。
HA RTO 与 restore RTO 不同
| 场景 | 路径 |
|---|---|
| primary host loss | detect → elect → promote → route → client retry |
| database deleted | stop damage → choose target → restore → replay → validate |
| corrupt pages | preserve evidence → classify → restore/rebuild → validate |
| region loss | activate remote infra → restore/promote → dependencies → DNS |
不要用 Patroni failover 的秒数回答整库恢复需要多久。
maintenance 是预算,不是免责
维护窗口应记录:
即使计划维护被 SLO 排除,用户损失仍存在。高成熟度平台会:
- 滚动维护;
- 验证连接恢复;
- 限制每次 blast radius;
- 保留 rollback;
- 记录实际中断;
- 复审窗口是否足够。
升级窗口必须包含回退判断
不只计算安装时间:
有些 PostgreSQL major upgrade 在数据目录切换后没有简单 rollback;最后可逆 点必须写清。第 30 章专门演练。
服务目标从损失与成本共同推导
目标越强,通常需要:
不能只问“技术上能否做到”,还要问业务是否愿意持续支付,以及组织能否操作。
本章不通过第 20/21 章
第 19 章只验证环境前提:
它不注入故障,不执行 failover,不做 restore。因此:
sandbox 的准确结论
本章四节点能证明:
不能证明:
因为所有 VM 共享物理 laptop,etcd 与 backup target 也各只有一个控制节点。
把例外当结构化结果
requirements.json
固定六项 sandbox exception:
通过结果必须写:
“验收通过”后面没有范围,是一种危险省略。
返回本章目录 · 下一节:计算、内存、存储与网络 · 查看全书目录 · 查看索引中心
19.2 计算、内存、存储与网络
资源规划不是列一张“推荐配置”。它要把 workload 的每种稀缺资源与一个可 观察的饱和点连接起来。
19.2.1 CPU 核数、频率、NUMA 与虚拟化
核数与单核性能解决不同问题
更多核心有利于:
更强单核性能有利于:
不能用总 vCPU 数替代 CPU 型号、频率、代际与持续性能。
SMT 线程不等于物理核心
操作系统报告 32 CPU,可能是:
基线记录:
以及 hypervisor/cloud 的:
PostgreSQL 并行 worker 数应基于实测吞吐和并发,不按 nproc 自动拉满。
CPU 饱和要看排队
观察:
CPU 100% 但吞吐继续线性增长,和 CPU 70% 但 cgroup throttling/steal 很高, 不是同一问题。
NUMA 使“总内存/总核心”失去均匀假设
多 socket/NUMA 系统中:
记录:
还要确认 BIOS/VM 的 NUMA 暴露、进程/IRQ/pinning 策略。
不要无条件“禁用 NUMA”。小单节点、超大多 socket、VM、容器的最佳策略可能 不同。变更要有 workload 实验。
本章 Vagrant VM 每台只报告一个 NUMA node,因此只能验证采集与同构,不能 形成大型 NUMA 结论。
虚拟化要记录资源保证
虚拟机的抽象层可能引入:
容器还要记录:
“8 vCPU/32 GiB”若没有 guarantee 与 failure domain,只是一个接口数字。
指令集与架构是兼容矩阵的一部分
本章正式 sandbox 是:
所有节点必须相同。生产扩展 package、JIT、compression、加密库与备份恢复 都要覆盖目标架构。不能假设 x86_64 上测试的二进制扩展会在 ARM 恢复目标上 存在。
CPU 基线不是调优
第 19 章只记录:
第 26 章测饱和,第 27 章才改变并行和参数。看到核心数不等于知道
max_parallel_workers_per_gather 应设多少。
19.2.2 内存预算、页缓存与 OOM 边界
PostgreSQL 使用多类内存
只算 shared_buffers + max_connections × work_mem 仍然过度简化。
页缓存与 shared buffers 共同工作
PostgreSQL 使用自己的 shared buffer cache,也依赖操作系统 cache。官方
Resource Consumption
给出 shared_buffers 的起始建议,同时说明 PostgreSQL 还依赖 OS cache。
这意味着:
- 不应把全部 RAM 分给
shared_buffers; - 文件系统/backup/extension 也需要 cache;
- database cache hit 不等于没有底层 I/O;
- VM host cache 还可能再加一层;
- cold/warm benchmark 要定义清楚。
乘法内存必须按峰值并发
近似账本:
[ M_{total}
M_{shared}
- M_{OS}
- N_{backend}M_{backend}
- \sum work\ nodes
- M_{maintenance}
- M_{other}
- headroom ]
work_mem 是每 sort/hash 节点的基础预算;并行 worker 与多个节点会放大。
平台应按 role/workload class 设置,而不是一个全局大值:
max_connections 是内存与调度承诺
每个 PostgreSQL connection 对应 backend process。大量 idle connection 也有:
大量 active connection 会导致 CPU 排队与 cache 抖动。
规划顺序:
不是先把 max_connections 改成 5000。
OOM 不是一种可接受的流量控制
当 Linux OOM killer 选择 PostgreSQL 进程:
- backend 被杀;
- postmaster 可能触发所有 backend 重启;
- client 事务中断;
- recovery/checkpoint 增加恢复时间;
- HA 可能触发切换;
- evidence 可能被噪声覆盖。
应使用:
在系统被 OOM 前拒绝或排队。
overcommit 与 swap 要显式
记录:
策略取决于宿主/容器/工作负载,但未知是不可接受的。
本章 sandbox 要求 guest SwapTotal=0,同时承认 macOS host 仍可能压缩/交换
VM 内存;VM 内看到无 swap 不证明物理主机不会产生内存压力。
Transparent Huge Pages 与显式 huge pages 不同
PostgreSQL 18 官方
Resource Consumption
说明 Linux THP 在一些环境中会导致性能下降,目前不鼓励;这与 PostgreSQL
显式 huge_pages 不是同一机制。
采集:
Pigsty sandbox 的预期:
若要启用显式 huge pages,应按 PostgreSQL
Managing Kernel Resources
用 shared_memory_size_in_huge_pages 计算并验证,不靠固定百分比模板。
内存 floor 与 production sizing 分离
2 GiB 是本章建议的教学下限;正式沙箱对低于该值的三台 VM 记录了
EX19-LAB-RESOURCE-FLOOR,而没有把建议偷偷降格。production 仍需依据:
通过 floor 不代表容量 gate 通过。
19.2.3 IOPS、吞吐、时延、容量与冗余
四个存储指标互不等价
小随机 WAL/fsync、索引随机读、大顺序扫描、backup stream 的瓶颈不同。
“云盘 20,000 IOPS”不说明:
- block size;
- read/write mix;
- queue depth;
- P99 latency;
- burst duration;
- fsync/FUA;
- shared cap;
- failure behavior。
PostgreSQL 的典型 I/O 路径
| 路径 | 特征 |
|---|---|
| WAL write/flush | 小、顺序、durability latency 敏感 |
| checkpoint | 大量脏页写、可能 burst |
| heap/index read | random/sequential 混合 |
| temp spill | 大量短寿命读写 |
| vacuum | 扫描 + index cleanup + WAL |
| backup | 大顺序读 + network + repository write |
| replica | WAL receive/write/replay + data I/O |
| restore | repository read + data write + WAL replay |
需要同时测生产 workload 和维护/故障状态。
平均延迟会隐藏 tail
保存:
交易 commit 通常对 tail latency 更敏感;一次 2 秒 flush 可能制造级联 queue。
文件系统与设备语义要完整记录
基线:
同时从基础设施层记录:
数据库内部无法证明底层存储真正持久。
fsync=on 仍依赖硬件诚信
PostgreSQL 发出同步请求后,操作系统/设备必须诚实地把数据持久化。虚假 write cache acknowledgment 会破坏 WAL 设计前提。
验收需要:
- 合格存储/云服务语义;
- power-loss protection;
- 厂商/基础设施保证;
- 故障测试与恢复;
- checksums/备份/取证作为纵深。
不要用 fsync=off 解决 production latency;那是在更改持久性合同。
容量要给多个并发动作留空间
磁盘不能规划到 95% 常态:
headroom 应按最坏的已批准维护/故障动作计算。
redundancy 不等于 backup
RAID/云盘副本:
PostgreSQL replica:
backup/PITR:
三者互补。
检查 checksum,但不要过度解读
PostgreSQL 18
initdb
默认启用 data checksums,可用 --no-data-checksums 关闭。checksum 能检测一类
页面静默损坏;它不纠正错误,也不覆盖:
本章要求每个成员 data_checksums=on,并把实际检测与修复留给第 28/35 章。
Vagrant 存储只能做流程验证
本章采集 root 与 /pg mount、总量/free、类型/options,但明确例外:
它不能为 production IOPS、P99、endurance、power-loss 或冗余签字。
19.2.4 时钟、DNS、带宽、防火墙与故障域
时钟是分布式证据的坐标
时钟影响:
每台记录:
验收:
“时间看起来差不多”不够。
数据库通常用 UTC 保存绝对时刻;用户展示时再按业务时区转换。OS 日志也应 统一可换算。
DNS 是服务依赖
记录:
不要让 PostgreSQL 成员依赖一个单点、不可观察的外部 resolver。
/etc/hosts 适合固定 sandbox,生产服务发现要有 owner 与变更流程。
IP、hostname、service name 分层
客户端连 service,不连“当前 primary 的 IP”。IP 可以变,service 语义应 稳定。
带宽要算复制与恢复
网络预算:
恢复或新副本同步可能是最大流量。
若:
还要加 checksum、compression、I/O、WAL catch-up 与争用。链路标称带宽不是 effective。
网络延迟进入同步提交
同步副本确认需要跨 failure domain 往返。距离越远,commit latency 越高; 距离太近,则可能不覆盖目标故障。
所以同步位置不是“同城最好”或“跨区最好”,而是 RPO、延迟、可用性和故障域 共同取舍。第 20 章实测。
防火墙从允许关系生成
不要先“关防火墙排障”,再忘记打开。建立 flow matrix:
| source | destination | port/protocol | purpose | auth |
|---|---|---|---|---|
| app | PG service | TCP | business SQL | TLS + role |
| PG member | PG member | TCP | streaming | replication role |
| Patroni | etcd | TCP/TLS | DCS | cert/credential |
| LB | Patroni REST | TCP | role health | network policy |
| backup | repository | TCP/TLS | archive/restore | scoped secret |
| monitoring | exporters | TCP | metrics | network/auth |
| admin | hosts | SSH | control | admin identity |
规则要双向核对:inventory 声明、主机 firewall、cloud security group、实际 probe。
Pigsty 4.5 Node Parameters 说明其 firewall 模式与 intranet/public 策略;具体默认仍要从目标配置与主机 事实确认。
“四台机器”不等于四个故障域
故障域清单:
两个 node ID 可能共享除进程外的所有域。
本章四个 machine-id 确实不同,但它们共享:
所以 validator 同时要求:
只通过前一项会制造错误结论。
DCS 与 backup 也有故障域
三节点 PostgreSQL + 单节点 etcd:
不是完整生产 HA。
备份放在同一 control VM/physical host:
拓扑图必须画控制与恢复依赖,不只画 PostgreSQL。
本节的验收产物
主机采集器
remote_host_facts.py
只读输出:
它记录事实,不自动执行任何 sysctl、mount 或 firewall 调优。
上一节:先写服务需求 · 返回本章目录 · 下一节:操作系统与主机基线 · 查看全书目录 · 查看索引中心
19.3 操作系统与主机基线
主机调优的最大风险,不是漏掉某个神奇参数,而是不知道当前系统实际是什么, 却批量套用一份来源、版本和目标都不明的模板。
19.3.1 文件系统、挂载、预读与透明大页
文件系统选择是一份兼容与恢复合同
记录:
选择 XFS、ext4 或其他支持文件系统,要基于目标 OS、存储、运维能力与 PostgreSQL/Pigsty 支持矩阵,不凭论坛结论。
数据、WAL、日志、备份与临时文件的路径
典型职责:
分盘不是目的。要问:
- 故障是否独立;
- I/O 是否真正隔离;
- capacity 是否独立;
- backup/restore 是否更复杂;
- mount 缺失时会不会写进 root;
- 监控是否覆盖每个 filesystem;
- 权限和 SELinux/AppArmor 是否一致。
防止“挂载没上,目录还在”
危险场景:
保护:
本章 capture 同时记录 target、source、fstype、options 与 free,不只记录
/pg 存在。
mount option 不应照抄
常见选项需要理解:
现代文件系统默认行为会变化。任何影响持久性或恢复的选项,必须有官方 文档、目标版本和故障测试证据。
预读依赖访问形状
OS/block device read-ahead 对顺序扫描可能有益,对随机 OLTP 可能放大无效 I/O。
记录:
然后用:
分别测。不要把某 SSD 模板的 KB 值复制到所有设备。
I/O scheduler 也要与设备匹配
物理旋转盘、NVMe、virtio、cloud block 的队列和 scheduler 不同。
采集:
改变前后同时看 workload latency、throughput、queue 和 CPU。
THP 与显式 huge pages
当前值:
本章要求 THP never,与 Pigsty node tuning 预期一致。PostgreSQL 官方
Resource Consumption
区分 THP 与显式 huge pages,并指出 THP 对部分 PostgreSQL 环境会造成性能
下降。
显式 huge pages 需要:
若 huge_pages=on 且数量不足,PostgreSQL 会拒绝启动;这是刻意强约束,
不能未经演练启用。
data directory 权限
PostgreSQL 官方
initdb
要求以最终 server owner 运行,不能 root 运行。目录应由 database OS user
拥有并限制权限。
检查:
父目录权限同样重要。不要让普通应用用户读取 data files、WAL 或 backup。
19.3.2 用户、目录、权限、时间同步与日志
OS 身份与数据库身份分开
OS:
PostgreSQL:
同名不代表同一身份。Peer authentication 才把 OS user 映射为 database role。
admin user 的前提要验收
Pigsty multi-node 部署需要管理用户可以:
这是一项强权限。要用:
本章 disposable Vagrant 用 vagrant + nopass sudo,只能在本地 sandbox
接受;production 必须重新设计。
目录清单是接口
记录每个组件:
| 项 | owner/mode | purpose | retention |
|---|---|---|---|
| source/inventory | admin | IaC | versioned/private |
/pg/data |
postgres | data | authoritative |
/pg/log |
postgres/agents | component logs | policy |
/pg/backup |
backup | repository/spool | backup policy |
/etc/patroni |
root/postgres | HA config | config |
/etc/pgbouncer |
service | pool config | config |
/etc/pgbackrest |
controlled | backup config/secret refs | config |
/etc/haproxy |
root | routing | config |
不要把目录路径写死进应用;通过 service/config 接口访问。
文件权限要从 threat model 验证
检查:
特别关注:
本章 live inventory 必须 0600,projection 不能含 secret value。
时间同步先于集群判断
chrony/其他 NTP 服务需要:
只看 service active 不够;本章读取 timedatectl NTPSynchronized。
时间跳变可能影响日志、lease、TLS、业务 timestamp 与 incident timeline。 数据库业务时间语义仍要用第 16 章的 UTC/event-time 合同。
hostname、machine-id 与 address
基线同时保存:
原因:
- IP 可能被错误复用;
- hostname 可能全部相同;
- SSH config 可能把不同别名指向同一主机;
- VM clone 可能复制 machine-id;
- inventory 拼写可能连错环境。
本章准备部署时就发现,常规 SSH config 把 Vagrant 地址重写为本地转发端口;
只看命令参数不够。正式采集强制 ssh -F /dev/null 直连,并以 machine-id
hash 证明四个目标不同。
hash 不让 evidence 暴露原始 machine-id,但仍能检测重复。
日志要有来源身份
每条集中日志至少关联:
不能只按 hostname,如果重装/复用会混淆。
日志容量与敏感性
PostgreSQL 日志可能含:
控制:
“为了排障全量记录 SQL 与参数”可能制造数据泄露。
logrotate 与磁盘故障
要验证:
日志不能与 PGDATA 互相填满;集中日志不可达也不应无限占用本地磁盘。
19.3.3 基线检查必须记录事实而非套用调优模板
基线与目标分两列
推荐格式:
| 项 | observed | desired | status | evidence |
|---|---|---|---|---|
| OS | Ubuntu 24.04.4 | Ubuntu 24.04.x | pass | host JSON |
| THP | never | never | pass | sysfs |
| swap | 0 | 0 | pass | meminfo |
| filesystem | ext4 virtual | qualified prod storage | sandbox exception | findmnt |
| NTP | synchronized | synchronized | pass | timedatectl |
事实不符合时:
不能把 desired 覆盖 observed 后再宣布通过。
“最佳实践参数”有上下文
网络文章常给:
每项必须问:
答不出就先记录,不变更。
配置层级要可追踪
一个值可能来自:
审计要保存最终事实和来源。只看 Git inventory 无法发现手工漂移;只看
sysctl -a 又无法知道下次重启会恢复成什么。
一次性探测命令也有风险
基线 collector 应:
避免:
本章 collector 不读取原始 machine-id 以外的 secret 文件;machine-id 只输出 SHA-256。
自动修复与验收分开
部署 playbook 可以改变主机;acceptance collector 只读。
好处:
- 验收失败不会偷偷修复;
- 能发现 automation 未覆盖的漂移;
- 证据可由不同身份运行;
- 重跑不会增加变更;
- 故障现场可先保留状态。
task.sh all 不会执行 deploy.yml。
运行两次不等于幂等证明
Ansible 第二次 changed=0 是强证据,但不是全部:
- 外部 API 可能有副作用;
- task 可能每次重启但未报告 change;
- 数据初始化可能非幂等;
- template 中动态值可漂移;
- service 行为可能改变;
- secrets/certificates 可能轮换。
需要同时比较:
pending reboot 也是事实
包安装可能提示:
不要忽略。记录:
是否立即 reboot 取决于授权和维护窗口,不应由 collector 自动作出。
例外要结构化
例外至少:
本章 requirements 先固定 ID/reason/impact;production 使用时还必须补真实 owner、expiry 与 control。
基线漂移比较
每次变更前后比较:
某些字段天然变化:
比较器要分类,不能要求整个 JSON byte-identical。
本章采集 schema
remote_host_facts.py
输出稳定分类:
动态运行指标留给第 25/26 章;本章是配置与环境基线。
最后的判断
主机基线的合格结论不是:
所有参数都等于模板。
而是:
对每个与服务目标有关的主机事实,我们知道 observed 值、来源、期望、 差异、证据和 owner;没有任何未解释差异被伪装成通过。
上一节:计算、内存、存储与网络 · 返回本章目录 · 下一节:版本与数据库初始化契约 · 查看全书目录 · 查看索引中心
19.4 版本与数据库初始化契约
安装包可以升级,初始化事实却不一定能在线改。版本与初始化契约要在第一次 创建数据目录前冻结,并在创建后从 PostgreSQL 自己读取回来。
本节正式契约是:
这里的 captured patch 是验收时观察值,不是允许集群成员长期运行不同小版本。
19.4.1 PostgreSQL、locale、collation、编码与 checksum
“PostgreSQL 18”不是完整版本身份
至少要区分:
major 决定磁盘格式、系统目录、SQL/行为兼容边界;patch 主要承载 bug 与安全 修复。扩展和操作系统包又有各自版本。只写“PG18”无法重建环境。
从 server 读取:
从 package manager、容器 image digest 或 artifact repository 记录 package
身份。不要用 psql --version 代替 server 版本;它只说明客户端。
database cluster、database 与 locale 的层次
PostgreSQL 官方
initdb
创建一个 database cluster:数据目录、共享系统目录和
postgres、template1、template0。随后 CREATE DATABASE 通常从模板
复制。
因此初始化默认会向后传播:
不要把 OS 的 LANG、cluster 默认和某个 database 的 locale 混为一谈。
encoding 解决字节到字符,不解决语言排序
UTF8 定义字符编码。它不自动定义:
- “ä”排在什么位置;
- 大小写转换规则;
- 字符类别;
- 模糊/前缀查询的索引语义;
- Unicode 版本变化后的排序稳定性。
这些主要属于 collation/provider。
查当前 database:
PG18 的字段反映 provider 与 locale;跨版本工具不要假设每个字段都存在。
collation 是数据语义,不是显示偏好
PostgreSQL 的 Collation Support 说明 collation 会参与:
这会产生一个重要结论:
改变 collation provider 或版本,可能改变既有索引所依据的顺序;不能把 它当成客户端展示参数。
生产选择至少比较:
| provider | 来源 | 优势 | 需要管理的变化 |
|---|---|---|---|
builtin |
PostgreSQL 内建 | 跨 OS 可预测,部署依赖少 | 受 PostgreSQL major 语义约束 |
libc |
操作系统 C library | 与 OS locale 集成 | OS/glibc locale 版本漂移 |
icu |
ICU library | 多语言与定制能力强 | ICU 版本、包与索引刷新 |
本章选 PG17+ 可用的内建 C.UTF-8。Pigsty 的
资源准备建议
也推荐 PG17+ 以它作为默认。这个选择偏向稳定、可预测的数据库基础排序;
它不声称提供每种自然语言的用户期望顺序。需要语言相关排序时,应明确
column/expression collation,并有业务样例。
C.UTF-8 与 C 不能只看名字
需要记录:
相同 C.UTF-8 字符串由不同 provider 提供时,不应靠名称推断行为完全一致。
本章验收同时要求:
初始化时显式选择,创建后从目录复核
PG18 initdb 的 provider 默认仍是 libc;选 builtin 必须显式指定合法
builtin locale。不要因为 Pigsty wizard 给出了合理结果,就误记成 PostgreSQL
自己的默认。
等价意图可以表达为:
在 Pigsty 中由 pg_encoding、pg_locale 及生成的 Patroni bootstrap
配置承载。最终证据仍是 pg_database,不是模板文件。
checksum 检测静默页损坏,不创造副本
data checksum 在读页时帮助发现 I/O 系统造成的静默损坏。它不能:
- 修复坏页;
- 替代 backup;
- 替代 replica;
- 防止错误 SQL;
- 证明 storage 持久性;
- 覆盖 WAL 文件的所有损坏形态。
PG18 initdb 默认启用 checksum,也允许显式
--no-data-checksums。因此不能用“版本是 18”推断某个现存 cluster 已启用。
读取:
on 表示检测机制启用,不表示从未发生 storage 问题;failure counter、
日志、scrub/backup 验证要一起看。
checksum 可以通过离线工具改变,但那仍是停机、容量和回退都要规划的 maintenance。对新平台,把它当初始化契约更简单。
本章的验证 SQL
postgresql-facts.sql
一次读取:
它以 JSON 输出,避免人眼从四台机器复制表格时错列。
19.4.2 WAL、页大小、扩展与认证前提
block size 属于二进制与磁盘格式合同
本章观察:
8 KiB 是常见构建值。它影响 relation page、buffer、部分容量公式和工具 兼容。不要假设所有自编译发行版都相同,也不要把 OS filesystem block size 当成 PostgreSQL page size。
恢复、物理复制、低层工具与扩展需要与 server build 兼容。
WAL segment size 只能在初始化时选择
PG18
initdb --wal-segsize
接受 1–1024 MiB 的 2 次幂,默认 16 MiB,而且只能初始化时设置。
本章冻结:
segment 大小不改变 WAL 逻辑正确性,但会影响:
改变它不是调一个 reload 参数,而是重建/迁移问题。
wal_level 等运行参数不是全部初始化事实
要区分三类:
| 类型 | 例子 | 典型改变方式 |
|---|---|---|
| compile/init | page size、WAL segment、system identifier、默认 locale | rebuild/init/migrate |
| restart | shared_preload_libraries、部分 WAL/worker 参数 |
rolling maintenance |
| reload/session | 许多 planner/logging/timeout 参数 | controlled reload/role policy |
同样一个配置文件中的两行,变更成本可能完全不同。变更系统应记录
context:
system identifier 界定物理 cluster 身份
pg_control_system() 可读取 system identifier:
本章要求:
它能揭露把同一 data directory/复制链误报成两个服务单元的错误。它不是 secret,但属于运维身份;对外报告可只保存关系或受控 evidence。
timeline 不是 cluster identifier。promotion 后 timeline 可以改变,system identifier 保持。第 20 章会使用这个区别。
扩展契约从 package 开始,不从 CREATE EXTENSION 开始
对每个扩展记录:
四个节点能启动 core PostgreSQL,不表示某个动态库在 future promotion
candidate 上存在。package parity 应在部署时验收;catalog 中
pg_extension 只说明当前 database 安装了什么。
第 14 章负责扩展生命周期;本章只冻结部署前提。
shared_preload_libraries 是重启边界
本章观察:
预加载库会进入 server 生命周期。新增/移除通常需要 restart,并必须在每个 可能承接 primary 的节点有兼容库。配置字符串一致还不够:
authentication method 与 password verifier 是两件事
password_encryption=scram-sha-256 控制新密码保存为什么 verifier;真正
允许哪类连接,由:
共同决定。
initdb --auth-* 会生成初始 HBA,但 Pigsty 随后声明式管理业务、复制和
管理访问。不要把 initdb 选项当作最终 access policy。
SCRAM 迁移需要客户端矩阵
本章新环境要求:
只改 password_encryption 不会重写既有角色密码。role 需要重新设置密码才
生成新 verifier。不要查询或导出 pg_authid.rolpassword 到 evidence。
TLS on 不是 TLS 完成
SHOW ssl = on 仅证明 server 可接受 TLS。完整合同还要在第 31 章验证:
本章只把 ssl=on 作为部署前提,不宣称传输安全审计已经完成。
不安全的初始化捷径
生产拒绝:
官方文档明确把 --no-sync 定位为测试用途;系统崩溃可能让初始化后的目录
损坏。自动化快几秒不是持久性理由。
19.4.3 版本矩阵、升级窗口与勘误入口
一条“版本号”要展开成兼容矩阵
建议基线:
| 层 | 当前身份 | 兼容/升级问题 |
|---|---|---|
| OS | Ubuntu 24.04.4 aarch64 | kernel、glibc、OpenSSL、locale |
| automation | Pigsty v4.5.0 exact tag | inventory/schema/playbook behavior |
| PostgreSQL | 18.6 | patch rollout、major upgrade |
| HA | Patroni package/version | DCS/API/config compatibility |
| DCS | etcd package/version | quorum、snapshot、client compatibility |
| proxy/pool | HAProxy/PgBouncer | protocol, auth, routing semantics |
| backup | pgBackRest + repo format | restore target/version |
| extensions | per extension | PG ABI, SQL update scripts |
| clients | driver/pool version | protocol, SCRAM, TLS, type behavior |
| observability | exporter/dashboard rules | metric name/label changes |
矩阵必须标:
“支持 PG18”不等于“当前所有 extension build、driver 和 restore path 已在 PG18.6 测过”。
primary 与 standby 尽量保持同一 patch
PostgreSQL standby planning 指出 physical log shipping 不能跨 major,并建议尽量保持相同 release level。短暂 rolling patch 差异需要:
- 官方升级说明;
- package availability;
- replica-first 顺序;
- rollback/forward-only 判断;
- extension parity;
- promotion eligibility;
- 限定窗口与监控。
不能把“minor 通常磁盘格式兼容”扩大成无限期混跑承诺。
patch 与 major 使用不同 runbook
patch upgrade 常见路径:
major upgrade可能需要:
本章只建立矩阵;第 30 章执行版本升级。
maintenance window 不只是“可以重启”
窗口要写:
如果升级需要 40 分钟,而窗口只有 30 分钟,“尽量完成”不是计划。
source pinning 与 dirty working tree
本章部署没有直接使用维护者本地 dirty Pigsty checkout,而是:
这避免把未提交修改偷偷带进正式证据。
source pinning 仍不是 supply-chain 完整方案。生产还要验证:
文档与实验也要有版本范围
书中命令页应标:
本节验证日期是 2026-07-29。读者使用更新版本时,应先查官方 release note 和参数文档,不应因为 URL 仍可访问就假设行为不变。
勘误入口是一条可执行路径
发现差异时记录:
修订后要更新:
- 正文;
- lab requirements/baseline;
- validator 与 negative case;
- verified matrix/date;
- migration note。
只改一句 prose 而不改 validator,会让书和实验分叉。
初始化契约的 release gate
本章正式 validator 要求四个 PostgreSQL 成员都满足:
并运行反例:
这证明规则能拒绝一个已知错误,不只证明正常样本能被脚本读出来。
通过后的结论仍是:
上一节:操作系统与主机基线 · 返回本章目录 · 下一节:拓扑、命名与故障域 · 查看全书目录 · 查看索引中心
19.5 拓扑、命名与故障域
三台服务器不是高可用拓扑,除非我们知道三台分别会因什么而一起失败。 “主库、从库、VIP”也不是足够精确的词表,尤其在 promotion 之后。
19.5.1 主节点、同步副本、异步副本与仲裁
primary 是当前角色,不是机器身份
PostgreSQL physical replication 中:
promotion 后,原 standby 可以成为 primary。于是:
把 hostname 叫 prod-primary 会在第一次切换后撒谎。
原生证据:
它不单独证明 service routing 正确,也不证明另一台没有同时可写。
physical standby 重放同一条 WAL 历史
同一 cluster 的成员应共享:
官方 Log-Shipping Standby 强调 primary/standby 应尽量相似,physical log shipping 不跨 major。
本章用 system identifier 检查:
异步复制的 commit 不等待副本
streaming replication 默认异步。正常低延迟不等于零 RPO:
数据损失窗口由故障时实际 receive/write/flush/replay 位置决定,而不是 平时 dashboard 上“通常 0 MB”决定。
观察 primary:
本章只确认 replica 在 streaming;第 20 章才测 failure-time 数据包络。
同步复制把一部分 commit latency 换成确认
PostgreSQL synchronous replication 可以让 commit 等待一个或多个 standby 反馈。等待层次包括:
它们不是同义词。remote_apply 还等待 replay 可见,延迟与阻塞代价更高。
配置又分:
同步复制只有在以下条件下才提供期望保护:
- standby 是真实独立故障域;
- storage flush 语义可信;
- synchronous standby 处于 streaming;
- application 没有降级为更弱
synchronous_commit; - 超时/降级策略符合业务选择;
- 失联时是停止写还是降级继续写已经定义。
“有 sync replica”不能省略这些条件。
同步复制也可能让写入不可用
若要求一个同步确认,而没有任何合格 standby:
因此 RPO 与 availability 之间要由 owner 选择:
自动降级如果没有审计,就是悄悄改变服务合同。
“仲裁”不要与数据副本混淆
PostgreSQL core 没有一个保存业务数据的“仲裁节点”概念。Pigsty 的 HA 参考实现里:
etcd member:
- 不保存可 promotion 的 PostgreSQL data;
- 不确认每个业务 transaction 已持久化;
- 不替代 backup;
- 不依据最新 WAL 自动解决所有数据选择;
- 需要自己的 quorum 与 failure domains。
把一个 etcd 节点叫“第三票”,然后声称两台 PostgreSQL 已实现零数据丢失, 是错误模型。
DCS quorum 与数据库成员数分别计算
三节点 PostgreSQL + 单节点 etcd:
三个数字不能互相抵消。单 etcd failure 会影响自动 HA 控制面;同一 laptop failure 会同时失去全部 VM。
本章正式沙箱把这两项登记为:
所以它适合练拓扑与协议,不适合宣称 production availability。
offline replica 是 workload placement
Pigsty 支持专门的 offline instance,也支持在已有 replica 上设置
pg_offline_query: true,把它纳入 offline service。官方
Cluster / Instance
明确说明后一种是资源折中。
本章:
它仍是同一 pg-test physical replica,不是独立 analytical authority。
标记不自动限制 CPU/I/O,也不自动阻止用户绕过 service 直连。第 17、22、
26、30 章分别处理分析语义、服务接入、容量和资源治理。
本章拓扑不是第 20 章结论
本章可以证明:
不能证明:
这些必须用受控 fault drill 取得。
19.5.2 节点、实例、集群、服务的统一词表
同一个“集群”常指四种东西
先建立词表:
| 词 | 本书定义 | 稳定身份/证据 |
|---|---|---|
| host/node | OS 实体:物理机、VM 或明确容器边界 | machine ID、hostname、address |
| PostgreSQL instance | 一个 server process + data directory + port | member name、PGDATA、system ID |
| PostgreSQL database cluster | 一个 initdb 产生的数据目录及其中 databases |
system identifier |
HA cluster / Pigsty pg_cluster |
一组共享 physical history、可相互接管的 members | pg_cluster、Patroni scope |
| database | cluster 内的 SQL database | OID/name/owner/locale |
| service | 面向客户端的稳定访问语义 | name、port、selector、health check |
| pool | 连接复用/状态边界 | PgBouncer endpoint/mode |
| DCS cluster | Patroni 使用的 etcd 共识域 | etcd membership/quorum |
| platform deployment | inventory 管理范围 | exact inventory/release |
PostgreSQL 官方把一个 data directory 称为 database cluster;平台团队又常把 一个 HA group 称 cluster。文档必须说明上下文。
node 不等于 instance
一台 node 可以运行:
一个 PostgreSQL cluster 也可以跨多 node。
“node down”与“Postgres down”故障面不同:
instance identity 不能只用 IP
IP 可以复用,DNS 可以漂移,hostname 可以被自动化改写。本章组合:
实验中发现一个真实陷阱:工作站 ~/.ssh/config 将四个地址映射到本机不同
转发规则,最初所有连接看起来都是 meta。正式采集强制:
再验证四个不同 machine ID。连接成功不是目标身份成功。
service 是语义,不是 socket
Pigsty 默认 service 可能包括:
官方 Service/Access 记录这些默认映射。端口能建立 TCP 只证明 listener/reachability,不证明:
- primary service 一定落在 current primary;
- replica service 的 freshness;
- pool transaction/session 语义;
- role/database/HBA 正确;
- TLS hostname 验证;
- failover 后客户端恢复。
所以本章 endpoint probe 的解释字段明确写:
cluster name 不应含 current role
推荐稳定层次:
pg-test-primary 可以是 service 名,不应是固定 machine 名。
数据库里的 role 又是另一种 role
避免一句“role 是 replica”同时指:
表格、变量名和 evidence 都写全称。
声明角色与观察角色都要保存
inventory 声明:
观察:
第一次部署时二者应一致。发生 failover 后,inventory 的 bootstrap role 不应 被天真解释为永恒运行角色;第 20 章会定义 drift 语义。
19.5.3 环境、区域、租户与业务命名
命名要编码稳定属性
适合进入名字的:
不适合进入固定 host 名的:
稳定属性让名字在 role change 后继续真实。
一份可扩展命名模型
示例:
名字要满足:
- PostgreSQL identifier 限制;
- DNS label 限制;
- metric label cardinality;
- certificate SAN;
- log/search 可读性;
- automation group syntax;
- rename cost。
environment 是权限与数据边界
常见:
不能只靠名字隔离。还要有:
prod database 和 test database 放在同一个 cluster,通常仍共享 superuser、
WAL、storage、restart、capacity 和 incident blast radius。
region、zone、rack 必须对应可验证故障域
一个 label az-a 不等于独立 zone。登记:
然后为每个 component 画依赖。相同依赖会形成 correlated failure。
本章四台 VM:
所以 failure-domain count 不是四。
tenant 边界不等于 schema 名
租户可能通过:
隔离强度逐步变化,成本也变化。命名中加入 tenant 前,先定义:
- authentication/authorization;
- noisy-neighbor;
- backup/restore granularity;
- key ownership;
- data residency;
- deletion;
- metrics/audit;
- exit/migration。
本章 pg-meta 与 pg-test 是两个 service unit,不是两个 customer tenant。
业务名与技术名需要映射表
不要让应用团队猜:
| 业务对象 | 平台对象 |
|---|---|
| pg36_shop service | pg-test teaching service unit |
| canonical shop DB | future pg36_shop provisioning target |
| control/validation | pg-meta |
| OLTP write endpoint | primary service contract |
| analytical read | offline service contract |
正式 inventory 当前还会创建 template 自带的 test/meta database。它们是
部署示范对象,不表示上卷的 pg36_shop fixture 已迁入这个下卷沙箱。
name registry 要防冲突与复用
登记:
立即复用已退役 cluster 名可能让:
- old DNS/cache 指向新服务;
- backup stanza 混淆;
- monitoring time series 串联;
- client secret 意外生效;
- automation limit 选错目标。
本章拓扑清单
topology.mmd 表达:
它同时画出共享 hypervisor。架构图若只画 database arrows、隐藏共同依赖, 会高估 availability。
命名与拓扑验收
validator 交叉检查:
反例:
通过后的精确结论:
四个地址对应四个同构 Linux guest;
pg-meta与pg-test是两个不同 PostgreSQL system identifier;pg-test有一个 leader 和两个 streaming replica;全部 guest 仍共享一个物理故障域。
上一节:版本与数据库初始化契约 · 返回本章目录 · 下一节:用声明式清单交付两个服务单元 · 查看全书目录 · 查看索引中心
19.6 用声明式清单交付两个服务单元
声明式交付的核心不是“YAML 很先进”,而是让目标、作用域、版本、差异与 执行证据可以评审。inventory 是意图,不是已经发生的事实。
19.6.1 inventory、参数模板与主机分组
inventory 同时承载图和参数
Pigsty inventory 的结构大致是:
它表达两类信息:
混在一个文件里不表示它们的生命周期相同。host identity 可能多年稳定, password 要轮换,service definition 会迭代,初始化参数只在新 cluster 生效。
identity 参数必须明确
Pigsty 的参数层次包含 global、group/cluster、host/instance 等作用域。对 PostgreSQL member,核心 identity 至少有:
官方
Pigsty parameter model
将 pg_cluster、pg_seq、pg_role 等视为无默认值的 identity 参数。
没有 identity,不应让自动化猜:
- 这是哪个 HA group;
- member 名是什么;
- bootstrap primary 是谁;
- service/monitor/backup 如何命名。
参数优先级要可解释
同一参数可能来自:
最终值要能回答:
“我在 YAML 里搜不到”不等于它使用 PostgreSQL 默认;可能来自 role default 或 template。
本章 live inventory 没有重复声明每一个 role default。验收从 SQL 读取 checksum、locale、timezone 等结果,避免把“省略”误作“不确定”或 “一定是 PostgreSQL 默认”。
参数模板是起点,不是服务等级
pg_conf: oltp.yml 可提供合理 OLTP 起点,node_tune: oltp 可收敛主机
baseline。它们不包含业务 workload evidence。
模板不能自动知道:
第 26、27 章才通过压力与瓶颈证据调参。
从 exact release 生成配置
本章没有直接运行当前 dirty checkout,而是:
解到私有临时目录,并记录 tag commit:
然后:
参数含义:
ha/full 官方定位主要是演示与测试,不是生产拓扑模板;它把 infra、单 etcd、
MinIO 和 pg-meta 放在第一节点,pg-test 放在后面三节点。这正是本章
将其标为 sandbox 的原因。
generator output 必须 review
wizard 给出初稿后,逐项 review:
configure 成功只表示生成文件,不表示目标可部署。
live inventory 是 secret-bearing artifact
随机密码比模板默认密码安全,但文件仍含 secret。处理:
本次初始化过程中,首次生成的凭据曾出现在工具输出边界;因此立即重新生成, 正式 live inventory 使用另一组未打印的值。这个事件提醒我们:
redaction 不是最后一步;command output、debug、diff 和 CI log 都是泄露面。
只导出 allowlist projection
inventory_projection.py 读取 live
inventory,只保留:
它不使用“把已知 password 字段替换为星号后整份输出”的 denylist 模式。 denylist 容易漏掉新字段;allowlist 默认拒绝未知内容。
执行:
投影不是可用于部署的 inventory,故意不可逆。
sanitized example 只表示形状
inventory.example.yml 使用 sentinel
secret,帮助理解结构。它不应原样部署。
审阅 example 时也要防止:
inventory 是 code,但不等于把一切都放 Git
适合版本化:
需要受控 secret store:
需要外部事实系统:
声明式不是单文件崇拜,而是让 authority 可追踪。
19.6.2 生产服务与隔离验证服务
先澄清本节标题的边界
生产设计应该区分:
本章用 pg-test 和 pg-meta 演练这种分工,但两者都位于 disposable
sandbox:
所以 pg-test 是 production-shaped teaching service,不是生产服务;
pg-meta 也不是合格的生产 control plane。
服务单元一:pg-test
声明:
目的:
当前 bootstrap role:
future failover 后 runtime role 可能变化;inventory declaration 与 observed role 的解释要跟着 chapter 20 contract 更新。
服务单元二:pg-meta
声明:
同一 node 还承载:
它可用于控制与验证,但这个共置形成大 blast radius。不要把“组件齐全”解释为 “组件高可用”。
两个 service unit 必须有不同 system identifier
部署时如果错误 clone 同一 cluster,再改名字,表面可能出现两个 group。 因此验收要求:
同时:
这是 declaration 和 physical lineage 的交叉检查。
control data 与 application data 不要无意混合
真实平台需要决定:
本沙箱共置是资源选择,不是推荐生产答案。
offline 服务不等于隔离环境
.13 是 replica + offline query 标记。它仍:
要实现更强隔离,可能需要:
production topology 要替换六个例外
从本章沙箱走向生产,不是删除 sandbox 字样。至少解决:
并完成后续章节 gate。
service unit review 表
| 问题 | pg-meta |
pg-test |
|---|---|---|
| authority | control/validation | teaching application service |
| members | 1 | 3 |
| current primary | .10 |
.11 |
| replicas | 0 | .12, .13 |
| offline placement | no | .13 |
| system ID | own | shared within three members |
| production SLO | none | none |
| later fault target | limited | chapter 20 |
这张表应进入 design review,而不是从 Ansible recap 猜。
19.6.3 幂等部署、差异检查与失败重跑
幂等的正确含义
理想的 idempotent task:
但整套部署包含:
所以不能用“Ansible 是幂等的”替代每个 action 的语义。
Pigsty 官方
Playbooks
说明大多数 playbook 可重复运行,同时指出清理参数和 *-rm.yml 等有重要
caveat。
一次完整部署的受控顺序
本章实际流程:
Pigsty deploy.yml 是 core chain 的 one-pass deployment。它完成很多工作,
不意味着每个业务 policy 自动满足。
recap 是执行证据,不是服务验收
本次安全 recap:
| target | ok | changed | unreachable | failed |
|---|---|---|---|---|
10.10.10.10 |
326 | 248 | 0 | 0 |
10.10.10.11 |
174 | 134 | 0 | 0 |
10.10.10.12 |
159 | 120 | 0 | 0 |
10.10.10.13 |
159 | 120 | 0 | 0 |
localhost |
6 | 4 | 0 | 0 |
它证明 playbook 没报告 failed/unreachable。它不证明:
这些另行验收。
changed=0 也不是唯一幂等标准
一些 task 合理地每次:
也有 task 误报 changed。应关注:
二次执行前先查 playbook/tag 的语义,不以追求漂亮 recap 为目标。
失败重跑前先分类
失败类型:
| 类型 | 例子 | 下一步 |
|---|---|---|
| transient | repo timeout、短暂 DNS | 保存证据后 bounded retry |
| declaration | wrong group/var | 修 inventory,review diff |
| prerequisite | sudo/clock/disk | 修前提并重新 preflight |
| partial init | primary created, replica failed | 查 cluster state,按 role runbook |
| incompatible | package/extension/OS | 停止,修矩阵 |
| destructive drift | wrong target/data exists | 停止并升级决策 |
| secret exposure | log printed credential | 先 rotate/contain |
不要在不知道已执行到哪里时直接:
失败现场是证据。
限制 scope
Pigsty playbook 支持 Ansible -l 与 tags。例:
使用前必须确认:
-l pg-test 是作用域控制,不是安全沙箱。
diff 不能泄密
安全 diff 分层:
不要把 live inventory git diff、Ansible -vvv、template variables 或
.pgpass 直接上传。
automatic reboot 被刻意拒绝
bootstrap 观察到 meta 节点已安装 kernel 与当前 running kernel 有差异。这 可能要求 reboot 才完成 host baseline,但本章没有自动重启:
- reboot 会改变运行状态;
- control/infra/DB 共置;
- 尚未建立第 20 章 HA 行为证据;
- maintenance authority 未授予;
- 本章 deployment 成功不要求偷偷消除 warning。
正确做法是登记 exception/change,计划可观察的 reboot,而不是为了让 preflight 变绿直接执行。
normal lab 没有 deploy action
task.sh 只提供:
其中 all 只读。没有 deploy 是有意设计:
source-controlled 验收脚本不应在读者以为“检查环境”时顺便收敛 package、 重启服务或重建数据库。
部署需要独立变更窗口和 runbook。
removal playbook 是 destructive action
Pigsty pgsql-rm.yml 用于移除 cluster/instance。官方文档说明 production
应显式启用 pg_safeguard,并对 override 格外谨慎。
本章只提供多重 guard 的
reset-cluster.sh,没有运行它。它要求:
这是演示 destructive contract,不是授权。
交付完成定义
声明式交付完成需同时满足:
本章环境满足 sandbox L2,带六个 exception;production gate 仍 pending。
上一节:拓扑、命名与故障域 · 返回本章目录 · 下一节:实战:L2 部署验收 · 查看全书目录 · 查看索引中心
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 默认这样”,还没有完成环境基线。
上一节:用声明式清单交付两个服务单元 · 返回本章目录 · 下一章:狡兔三窟:高可用拓扑与容灾目标 · 查看全书目录 · 查看索引中心