From 256b696865757d9e1f34353efc4caa27a716ab33 Mon Sep 17 00:00:00 2001 From: sqlrush <160627417+sqlrush@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:13:30 +0800 Subject: [PATCH 1/3] docs: explain external I/O fencing architecture --- docs/architecture/external-fencing/README.md | 678 +++++++++++++++++++ 1 file changed, 678 insertions(+) create mode 100644 docs/architecture/external-fencing/README.md diff --git a/docs/architecture/external-fencing/README.md b/docs/architecture/external-fencing/README.md new file mode 100644 index 0000000000..b0ebdeb983 --- /dev/null +++ b/docs/architecture/external-fencing/README.md @@ -0,0 +1,678 @@ +# 外部 I/O Fencing:从节点驱逐到可验证的恢复权限 + +本文解释共享存储集群中最容易被低估、也最不能含糊的一条安全链:**为什么“节点失联”不等于“节点已经不能写”,以及 PGRAC 如何把外部设备的终态读回转换成数据库可消费、可失效、可审计的恢复准入证明。** + +文中同时对照 Oracle Clusterware 的公开行为,但不会把 PGRAC 自己的协议字段、证书格式或进程划分描述成 Oracle 内部实现。 + +> **当前主干状态(2026-08-21)** +> +> PGRAC 公开主干已经包含 provider-neutral wire protocol、pgrac-fenced 守护进程骨架、终态分类器、持久日志、rejoin 控制消息以及数据库侧 NeedSet/AdmissionSet 验证框架;但是主干尚未注册经过生产认证的 fencing provider,数据库侧运行时激活条件仍返回关闭。因此,当前不能把外部 fencing 证书用于开放真实 takeover/recovery 写路径:缺少肯定证明时一律 fail closed。 + +## 1. 阅读地图与证据标签 + +本文使用四种标签,避免“目标相似”被误读成“内部实现相同”: + +| 标签 | 含义 | +|---|---| +| **Oracle 已验证事实** | 可由 Oracle 官方公开文档直接支持 | +| **PGRAC 已实现** | 当前公开主干中可定位到对应源码 | +| **PGRAC 适配设计** | 为 PostgreSQL/PGRAC 架构设计的公开实现,不声称是 Oracle 内部协议 | +| **当前边界** | 代码刻意保持关闭、尚不能作为生产能力宣称的部分 | + +建议按以下顺序阅读: + +~~~mermaid +flowchart LR + A[双写威胁] --> B[Oracle failure isolation] + B --> C[PGRAC 组件分层] + C --> D[终态证明与精确绑定] + D --> E[数据库侧准入] + E --> F[恢复与 rejoin 生命周期] + F --> G[部署认证与故障诊断] +~~~ + +## 2. 核心问题:失联不是写隔离 + +共享存储集群至少存在四种不同的事实。它们相关,但不能互相替代: + +| 事实 | 能回答什么 | 不能回答什么 | +|---|---|---| +| 心跳/liveness | 最近是否还能收到对端消息 | 对端是否仍可向共享存储发 I/O | +| membership/quorum | 哪些节点属于当前成员视图 | 被移出视图的旧进程是否已失去设备访问能力 | +| external write exclusion | 指定物理目标是否已到达不可写终态 | 当前恢复请求是否绑定了正确 epoch、incarnation 和资源集合 | +| recovery authority | 当前恢复者是否可对精确资源集合执行接管 | 被隔离节点是否可以直接重新加入 | + +危险窗口发生在“旧节点已经从成员视图消失,但它的操作系统、HBA、网络或缓存路径并未真正停止”时: + +~~~mermaid +flowchart TB + subgraph N0[幸存节点 node0] + A1[判定 node1 超时] + A2[准备恢复 node1 的资源] + A3[可能写共享页、日志或元数据] + end + + subgraph N1[失联但未真正停止的 node1] + B1[集群网络不可达] + B2[数据库或内核仍运行] + B3[仍可能写共享存储] + end + + S[(共享存储)] + + A1 --> A2 --> A3 --> S + B1 --> B2 --> B3 --> S + S --> X{两个故障域同时写} + X --> Y[页损坏、redo 顺序冲突、锁主权分裂] +~~~ + +安全关系不是: + +~~~text +peer heartbeat timeout ⇒ recovery may write +~~~ + +而是: + +~~~text +peer heartbeat timeout + ∧ current membership/quorum + ∧ exact victim incarnation + ∧ exact protected resource set + ∧ independently read-back write-excluded terminal state + ∧ fresh, durable and live-connection-bound proof + ⇒ recovery admission for that exact need +~~~ + +External fencing 不是替代 membership,而是补上 membership 无法证明的**物理写隔离**事实。 + +## 3. Oracle RAC 的公开责任划分 + +### 3.1 Oracle 已验证事实 + +Oracle 官方文档公开了以下行为: + +- Cluster Synchronization Services(CSS)控制集群成员关系,并在节点加入或离开时通知集群组件; +- cssdagent 监控集群并提供 I/O fencing; +- failure isolation 的目的,是阻止故障节点继续破坏数据; +- 外部隔离机制可以在不依赖受害节点操作系统或 Clusterware 配合的情况下重启问题节点; +- Oracle Clusterware 支持通过经过认证的 IPMI-over-LAN 与节点 BMC 交互;IPMI 配置要求节点一致,并建议使用独立管理网络; +- Oracle 还公开了 ASM 相关的 FENC 后台进程,但这是 Oracle ASM/RDBMS 体系中的具体事实,不能据此推导 PGRAC 必须复制相同内部协议。 + +Oracle 公共资料给出的关键语义可以概括为: + +~~~mermaid +flowchart LR + CSS[CSS membership 和 heartbeat] --> D[判定问题节点] + D --> AGENT[Clusterware failure-isolation actor] + AGENT --> BMC[BMC 或 IPMI 管理面] + BMC --> VICTIM[目标节点被外部重启或隔离] + VICTIM --> SAFE[故障节点不再威胁共享数据] + SAFE --> REC[集群恢复继续] +~~~ + +### 3.2 Oracle 没有公开的部分 + +Oracle 公开文档没有披露下列 PGRAC 所需内部细节: + +- target/protected-set digest 的字节布局; +- 数据库恢复请求与 fencing 证明之间的 wire frame; +- proof freshness 的具体时间窗口; +- daemon journal 的记录格式; +- backend 消费证书时逐字段校验的先后顺序。 + +因此,本文后续的 digest、nonce、journal、NeedSet/AdmissionSet 和 rejoin opcode 都属于 **PGRAC 适配设计或已实现代码**,不是对 Oracle 私有实现的猜测。 + +## 4. PGRAC 的进程与信任边界 + +PGRAC 把“判断集群需要什么”和“操作外部设备并验证终态”放在不同权限域: + +~~~mermaid +flowchart TB + subgraph DB[数据库权限域] + CSSD[cssd: liveness] + QV[quorum 和 membership] + LMON[lmon: recovery coordinator] + BE[recovery backend consumer] + ROOT[control-root feature state] + end + + subgraph HOST[root 管理权限域] + SOCK[Unix domain socket 和 peer credentials] + FD[pgrac-fenced] + CFG[root-owned config] + J[(durable journal)] + P[provider ABI v1] + end + + subgraph OOB[带外管理域] + MGT[management network] + DEV[BMC、power controller 或 provider target] + end + + CSSD --> LMON + QV --> LMON + LMON -->|NeedSet| BE + BE --> SOCK --> FD + CFG --> FD + FD <--> J + FD --> P --> MGT --> DEV + DEV -->|independent readback| P + P --> FD -->|typed proof| SOCK --> BE + BE -->|AdmissionSet| LMON + LMON --> ROOT + + classDef closed fill:#ffe6e6,stroke:#b30000,color:#5c0000; + class P,ROOT closed; +~~~ + +图中的红色节点表示当前公开主干的生产边界:provider ABI 和数据库激活门已经存在,但没有已注册、已部署认证的生产 provider,因而 control-root 不能把 external-fence 能力作为开放恢复写路径的依据。 + +这种拆分有三个安全目的: + +1. 数据库进程不直接持有 BMC/管理平面凭据; +2. 外部设备操作失败、超时或崩溃不会在数据库进程中被误解释为成功; +3. daemon 返回的只是候选证书,数据库仍必须用当前 formation、incarnation 和 protected set 重新验证。 + +## 5. 激活链:每一环都必须为真 + +生产激活不是“把一个布尔开关改为 true”,而是一条完整的可验证链: + +~~~mermaid +flowchart LR + A[已认证 provider] --> B[精确解析 target UUID] + B --> C[执行 OFF 或 write exclusion] + C --> D[独立 readback] + D --> E{OFF 且 I/O DRAINED?} + E -- 否或未知 --> Z[fail closed] + E -- 是 --> F[持久化 terminal proof] + F --> G[绑定 victim incarnation] + G --> H[绑定 protected-set digest] + H --> I[绑定 mapping generation] + I --> J[绑定 current formation 和 duty] + J --> K[校验 freshness 和 live connection] + K --> L[构造 AdmissionSet] + L --> M[数据库恢复路径可消费] + M --> N[功能状态允许开放] +~~~ + +任意一环缺失,结果都不是“较弱的成功”,而是不可用、未知、拒绝或超时。尤其要避免以下错误降级: + +| 错误做法 | 为什么不安全 | +|---|---| +| 只相信 power-off 命令返回 0 | 命令接受不代表目标已经关闭,也不代表 I/O 已排空 | +| 只相信节点不再心跳 | 网络隔离不等于存储隔离 | +| 只校验 node id | node id 可复用,必须同时绑定 incarnation | +| 只校验目标机器 | 恢复权限还必须绑定确切受保护存储集合 | +| 重启 daemon 后复用旧成功 | daemon boot、journal generation、freshness 和当前连接都可能已变化 | +| 把 OFF 证明用于 rejoin | rejoin 需要相反方向的 ON 且 DRAINED 终态及新的生命周期授权 | + +## 6. 两个不可混用的终态谓词 + +### 6.1 恢复侧:WRITE_EXCLUDED + +Provider 的一次动作分成三步:解析、执行、独立读回。只有最后的精确读回才可形成肯定结果: + +~~~text +resolved_target_uuid == configured_target_uuid +AND target_state == OFF +AND io_drain_state == DRAINED +AND provider_result == OK +~~~ + +下列结果全部不是肯定证明: + +| 读回结果 | 恢复准入 | +|---|---| +| 精确 target + OFF + DRAINED | 可以继续生成候选 proof | +| 精确 target + OFF + NOT_DRAINED | 拒绝,I/O 尚未排空 | +| 精确 target + ON | 拒绝,目标仍开启 | +| target UUID 不一致 | 拒绝,可能操作错设备 | +| TRANSITIONING / UNKNOWN | 未知,继续等待或失败关闭 | +| provider 超时、崩溃或不可达 | 不可用,失败关闭 | + +### 6.2 Rejoin 侧:ON_AND_DRAINED + +节点重新加入时,所需终态方向相反:目标必须已重新开启并完成 I/O 稳定化。公开代码使用独立分类器要求: + +~~~text +resolved_target_uuid == configured_target_uuid +AND target_state == ON +AND io_drain_state == DRAINED +AND provider_result == OK +~~~ + +恢复侧的 OFF + DRAINED 证明绝不能自动变成 rejoin 准入;同样,rejoin 的 ON + DRAINED 也不能授权恢复接管。 + +## 7. 证书到底绑定什么 + +pgrac_external_fence_protocol.h 定义的是 provider-neutral 语义对象和手工 codec。语义结构不能直接强转为 wire frame,避免编译器 padding、端序和 ABI 差异进入持久协议。 + +### 7.1 Need:数据库提出的精确问题 + +| 字段 | 作用 | +|---|---| +| system_identifier | 防止跨数据库集群复用证明 | +| canonical_duty_digest | 绑定当前恢复职责或 formation 语境 | +| victim_node_id | 指定被隔离节点 | +| victim_incarnation | 防止 node id 复用后的旧证明重放 | +| protected_set_digest | 指定本次恢复会触及的共享资源集合 | +| predicate_id/version | 区分 WRITE_EXCLUDED 与 rejoin 等谓词版本 | +| request_nonce | 绑定单次请求与响应 | +| timeout_ms | 给 provider 工作设置有界期限 | + +### 7.2 Binding:daemon 解析后的设备映射 + +Binding 在 Need 基础上加入 target_mapping_generation。它解决一个常见陷阱:配置中的 node id 到物理 BMC/设备 target 的映射可能变更。即使其它字段都相同,旧 generation 的证明也不能在新映射下继续使用。 + +### 7.3 Response:终态、持久性与新鲜度 + +肯定响应还携带: + +| 字段组 | 防御目标 | +|---|---| +| daemon_boot_id | daemon 重启后拒绝无条件继承旧进程语境 | +| journal_seq | 定位形成证明的持久事件位置 | +| verified_mono_ns / fresh_until_mono_ns | 限制终态证明的有效时间窗 | +| proof_generation | 区分同一 target 的后续操作或失效世代 | +| provider_id / provider_abi_version | 绑定已认证 provider 实现 | +| provider_result / provider_native_status | 保留规范化与原生诊断结果 | +| target_state_digest | 绑定 target、状态、I/O drain、映射与 proof 世代 | +| deny_reason | 给 fail-closed 路径提供结构化原因 | + +公开协议当前固定使用 160-byte request、256-byte response 和 256-byte rejoin frame,digest 为 32 bytes,nonce/operation id 为 16 bytes。 + +## 8. 从节点失联到恢复准入 + +下面是 provider 与部署认证完成后必须形成的完整调用链;当前公开主干会在生产 provider/激活门之前停止。时序强调“动作完成”与“数据库获得权限”不是同一事件: + +~~~mermaid +sequenceDiagram + participant C as cssd/quorum + participant L as LMON coordinator + participant B as DB recovery consumer + participant F as pgrac-fenced + participant P as provider worker + participant T as physical target + participant J as durable journal + + C->>L: node N lost / membership changes + L->>B: Need(N, incarnation, duty, protected set) + B->>F: ACQUIRE(request nonce + exact Need) + F->>J: REQUEST_ACCEPTED + fsync + F->>P: resolve exact target + P-->>F: target UUID + mapping generation + F->>J: ACTUATION_ISSUED + fsync + F->>P: actuate_off(deadline) + P->>T: provider-specific external isolation + P-->>F: action result + F->>J: ACTUATION_RESULT + fsync + F->>P: independent readback(deadline) + P->>T: query actual terminal state + T-->>P: OFF + DRAINED + exact target + P-->>F: typed readback + F->>J: READBACK_RESULT / PROOF_SERVED + fsync + F-->>B: candidate proof + freshness window + B->>B: revalidate formation, incarnation, set, generation and connection + B-->>L: Admission for this exact Need + L->>L: only now may the matching recovery action proceed +~~~ + +如果 provider 命令返回成功,但 readback 是 TRANSITIONING;或者读回是肯定的,但数据库收到时已超过 freshness;或者期间 formation 已变化,最后三步都必须停止。 + +## 9. Daemon 状态机:成功不是一个瞬时返回值 + +公开主干把操作生命周期表示为闭合状态机: + +~~~mermaid +stateDiagram-v2 + [*] --> UNAVAILABLE + UNAVAILABLE --> IDLE: capability ready + IDLE --> QUEUED: accepted but capacity unavailable + QUEUED --> RESOLVING: capacity ready + IDLE --> RESOLVING: request target free + RESOLVING --> ACTUATING: exact target resolved + RESOLVING --> REJECTED: mapping rejected + RESOLVING --> UNKNOWN: resolve unknown + RESOLVING --> UNAVAILABLE: provider unavailable + ACTUATING --> VERIFYING: actuation finished + VERIFYING --> PROVEN_DURABLE: exact OFF + DRAINED + VERIFYING --> REJECTED: OFF but I/O not drained + VERIFYING --> UNKNOWN: ON, transitioning or unknown + PROVEN_DURABLE --> INVALIDATED: mapping/proof invalidated + PROVEN_DURABLE --> REENABLING: authorized rejoin + REENABLING --> IDLE: exact ON + DRAINED + REENABLING --> UNKNOWN: failure or uncertain readback + QUEUED --> UNKNOWN: timeout + QUEUED --> INVALIDATED: request invalidated +~~~ + +PROVEN_DURABLE 表示形成证明所需事件已经进入可校验 journal,不等于数据库可以永久缓存该权限。数据库侧仍受 freshness、连接存活和当前集群事实约束。 + +## 10. 多受害节点:NeedSet 与 AdmissionSet + +一次重构可能涉及多个失联写者。PGRAC 不使用“集群已经 fence 完”这种全局布尔值,而是对精确 need 集合逐项收集 admission: + +~~~mermaid +flowchart TB + N[NeedSet for current recovery] + N --> N1[node1/inc7 + protected-set A] + N --> N2[node2/inc4 + protected-set A] + N --> N3[node3/inc9 + protected-set B] + + N1 --> P1{proof exact + fresh?} + N2 --> P2{proof exact + fresh?} + N3 --> P3{proof exact + fresh?} + + P1 -- yes --> A1[admission 1] + P2 -- yes --> A2[admission 2] + P3 -- no --> X[overall blocked] + + A1 --> AS[AdmissionSet] + A2 --> AS + AS --> X +~~~ + +这样可以防止: + +- 用 node1 的证明替代 node2; +- 用旧 incarnation 的证明覆盖新进程; +- 用 storage-set A 的证明授权 storage-set B; +- 因为大多数受害节点已隔离,就忽略最后一个未知节点。 + +AdmissionSet 是进程内、短生命周期对象;释放时清零,不能成为跨 formation 的永久权威缓存。 + +## 11. 持久 Journal 与崩溃恢复 + +### 11.1 为什么需要 journal + +外部电源操作有经典崩溃窗口: + +~~~text +发送 OFF 成功 + ↓ +daemon 在记录结果前崩溃 + ↓ +重启后究竟是“未执行”、“已执行”还是“状态又变化”? +~~~ + +安全答案不是猜测,也不是把最后一条命令的退出码当真值,而是通过 journal 恢复操作上下文,再执行独立 readback;只有当前可验证终态才能重新形成证明。 + +### 11.2 公开主干的 journal 约束 + +- 固定 256-byte records; +- sequence 单调递增; +- 每条记录带 CRC32C; +- 通过 SHA-256 previous-record digest 形成链; +- append 使用追加语义,写入后执行 fsync; +- 事件类型区分 config、request、action、readback、proof、invalidate、reenable 和 reconcile; +- 扫描时验证 sequence、前驱 digest 和 CRC; +- 完整性失败使服务进入 unavailable,而不是从损坏日志推导成功; +- 仅在严格条件下允许修复不完整尾记录,不能把任意损坏静默截断成绿色。 + +~~~mermaid +flowchart LR + R1[seq 101 CONFIG] -->|prev digest| R2[seq 102 REQUEST] + R2 -->|prev digest| R3[seq 103 ACTUATION] + R3 -->|prev digest| R4[seq 104 READBACK] + R4 -->|prev digest| R5[seq 105 PROOF] + R5 --> V{CRC + digest chain + seq valid?} + V -- yes --> C[可恢复上下文,仍需当前 readback/freshness] + V -- no --> U[UNAVAILABLE / fail closed] +~~~ + +## 12. Rejoin:恢复后重新加入不是反向播放 + +被隔离节点要重新加入,不能简单地“开机,然后把旧 fence 状态清掉”。公开协议把管理者准备和 LMON 授权拆开: + +~~~mermaid +sequenceDiagram + participant A as Administrator/control plane + participant F as pgrac-fenced + participant L as current LMON + participant T as target/BMC + participant J as joining instance + + A->>F: ADMIN_PREPARE(old node/inc, candidate inc) + F-->>A: ADMIN_PREPARE_RESULT / operation id + L->>F: LMON_CLAIM_NEXT(current gate digest) + F-->>L: LMON_OFFER + L->>F: LMON_AUTHORIZE_ON(exact operation) + F->>T: actuate_on + F->>T: independent readback + T-->>F: exact target ON + DRAINED + F-->>L: LMON_ON_RESULT + fresh proof + J->>L: membership/rejoin request with candidate incarnation + L->>F: optional REFRESH_ON + F-->>L: REFRESH_RESULT + L-->>J: admission only if current formation and identities still match +~~~ + +这条链解决四个问题: + +1. 管理操作不能自己授予数据库 membership; +2. LMON 只能接管为当前 gate/formation 准备的 operation; +3. 开机动作必须通过独立 ON + DRAINED 读回; +4. candidate incarnation 与旧 incarnation 明确分离,避免旧进程身份复活。 + +如果 formation、target mapping、operation ownership 或 candidate incarnation 在途中变化,操作应 invalidated 或 cancel,然后按新事实重新开始。 + +## 13. 数据库侧为什么还要重新验证 + +Daemon 是设备终态的权威观察者,但不是数据库恢复职责的最终权威。数据库收到 proof 后仍需验证: + +~~~mermaid +flowchart TD + R[收到候选 proof] --> V1{protocol + nonce 匹配?} + V1 -- no --> D[deny] + V1 -- yes --> V2{system + duty/formation 当前?} + V2 -- no --> D + V2 -- yes --> V3{victim node + incarnation 精确?} + V3 -- no --> D + V3 -- yes --> V4{protected set + writer set 精确?} + V4 -- no --> D + V4 -- yes --> V5{mapping/provider ABI 当前?} + V5 -- no --> D + V5 -- yes --> V6{journal/proof generation 单调?} + V6 -- no --> D + V6 -- yes --> V7{freshness 未过期且连接仍活跃?} + V7 -- no --> D + V7 -- yes --> V8{terminal predicate 肯定?} + V8 -- no --> D + V8 -- yes --> A[创建 process-local admission] +~~~ + +公开主干定义的最大 proof freshness 为 5 秒,默认 acquire timeout 为 120 秒。前者不是“设备五秒后一定重新开启”,而是限定数据库可以把某次观察当作当前事实的最长时间;过期必须重新获取或刷新。 + +## 14. 配置身份与映射世代 + +默认公开路径如下: + +| 用途 | 路径 | +|---|---| +| root-owned daemon 配置 | /etc/pgrac/pgrac-fenced.conf | +| 数据库客户端 socket | /var/run/pgrac/pgrac-fenced.sock | +| 管理 socket | /var/run/pgrac/pgrac-fenced-admin.sock | +| durable journal | /var/lib/pgrac-fenced/journal | + +配置绑定:cluster system identifier、storage backend/UUID、provider id/ABI、mapping generation、允许连接的数据库 uid/gid,以及每个节点的 node id、target UUID 和 provider adapter data。 + +mapping_generation 是配置变化的安全轴: + +- generation 回退必须拒绝; +- 同一 generation 下配置字节发生变化必须拒绝; +- generation 前进会使依赖旧 target map 的 proof 失效; +- 数据库 proof 和 rejoin operation 都必须携带并重新核对 generation。 + +这样即使运维人员把 node2 的 BMC 地址改成另一台设备,也不会让旧的“node2 已关闭”证明在新映射下继续生效。 + +## 15. 安全模型 + +### 15.1 权限分离 + +| 主体 | 应拥有 | 不应拥有 | +|---|---|---| +| PostgreSQL backend/LMON | 构造 Need、验证 proof、消费短期 admission | BMC 管理凭据、直接执行设备命令 | +| pgrac-fenced | root-owned config、journal、provider 调度 | 决定数据库 membership 或恢复资源范围 | +| provider worker | 单次、有 deadline 的设备操作能力 | 长期数据库状态、无限执行时间 | +| administrator | 发起受控 rejoin prepare | 绕过 LMON 直接授予 membership | + +### 15.2 本地 IPC + +Daemon 通过 Unix domain socket 获取 peer credential:管理 socket 只接受 root 语义,数据库 socket 依据 root-owned 配置中的 uid/gid 认证。仅知道 socket 路径不能获得 fencing 权限。 + +### 15.3 Provider 隔离 + +Provider 操作运行在短生命周期 worker 中,并受 deadline 约束。超时先终止,必要时强制结束;worker 崩溃映射为非肯定结果,不会把父 daemon 或数据库带入“可能成功”的模糊状态。 + +### 15.4 重放与陈旧证明 + +以下字段共同压缩重放空间:system id、duty digest、request nonce、victim incarnation、protected-set digest、mapping generation、daemon boot id、journal sequence、proof generation 和 freshness window。少校验一个维度,都可能把“过去对另一个对象成立”误当成“现在对这个恢复请求成立”。 + +## 16. Fail-closed 矩阵 + +| 场景 | daemon 结果 | 数据库行为 | +|---|---|---| +| provider 未注册 | UNAVAILABLE | 不创建 admission | +| 配置 target 无法精确解析 | REJECTED/UNKNOWN | 不创建 admission | +| OFF 命令退出 0,但读回仍 ON | UNKNOWN/REJECTED | 不创建 admission | +| 目标 OFF,但 I/O 未 drained | DENY_IO_NOT_DRAINED | 不创建 admission | +| 读回 target UUID 不符 | REJECTED | 不创建 admission,报告映射风险 | +| journal CRC/digest 链损坏 | DENY_JOURNAL / UNAVAILABLE | 停止服务能力,不复用旧 proof | +| config mapping generation 改变 | INVALIDATED | 旧 proof/operation 失效 | +| proof 超过 freshness | stale | 重新获取,不降级使用 | +| daemon 连接断开 | unavailable | process-local admission 不能继续扩张 | +| formation 或 victim incarnation 改变 | mismatch | 丢弃旧响应,按新 Need 重来 | +| rejoin 只看到 ON,未 drained | 非 READY | 不接纳节点 | + +## 17. 生产部署认证清单 + +代码中的 provider ABI 只是机制。要让 external fencing 真正成为生产恢复权威,至少需要完成以下部署级闭包。 + +### 17.1 带外管理面 + +- 每个节点具有不可由本机数据库进程篡改的外部控制目标; +- 对 Oracle/IPMI 类部署,BMC 支持 IPMI-over-LAN,并通过独立或受控管理网络到达; +- 凭据只存放在 root 可读配置或等价 secret store; +- 每个 node id 到 target UUID 的映射经过双人核验; +- 误操作测试能证明不会 fence 到错误节点。 + +### 17.2 Provider 认证 + +- resolve 能返回稳定、唯一的 target identity; +- actuate_offactuate_on 均有有界 deadline; +- readback 独立于动作返回值,能区分 ON、OFF、TRANSITIONING、UNKNOWN; +- I/O drain 语义与实际共享存储路径一致,而不只是电源状态; +- provider native errors 全部映射到闭合结果,不存在“未知码按成功处理”; +- worker timeout、kill、daemon crash 和设备离线均经过故障注入验证。 + +### 17.3 集群绑定 + +- system identifier 与目标数据库一致; +- protected-set digest 覆盖恢复会写到的全部共享资源; +- current writer set、victim incarnation 和 formation 在消费点重新读取; +- mapping generation 的升级、回滚和同世代篡改测试通过; +- 多受害节点必须逐一获得 admission,不允许多数替代全集。 + +### 17.4 持久性与安全运维 + +- journal 所在文件系统支持所依赖的追加和 fsync 语义; +- journal 损坏、部分尾写和磁盘满均有演练; +- daemon restart 后不会直接复用过期 admission; +- rejoin 必须走 ADMIN prepare + 当前 LMON 授权 + ON + DRAINED readback; +- 审计能关联 request nonce、operation id、target、journal sequence 和最终数据库 admission。 + +只有 provider、设备、网络、配置、journal 和数据库 consumer 一起通过认证,生产激活门才有安全意义。 + +## 18. 故障诊断:先看哪一层 + +~~~mermaid +flowchart TD + S[恢复未获得 external-fence admission] --> A{daemon 可连接?} + A -- no --> A1[检查进程、socket、peer uid/gid、配置权限] + A -- yes --> B{provider 可用?} + B -- no --> B1[检查 provider id/ABI、管理网络、凭据] + B -- yes --> C{target 精确解析?} + C -- no --> C1[核对 node-to-target map 与 generation] + C -- yes --> D{OFF + DRAINED?} + D -- no --> D1[区分 ON、TRANSITIONING、NOT_DRAINED、UNKNOWN] + D -- yes --> E{journal 完整?} + E -- no --> E1[停止恢复,处理 CRC/digest/磁盘持久性故障] + E -- yes --> F{proof 当前且精确匹配?} + F -- no --> F1[检查 freshness、boot id、incarnation、formation、protected set] + F -- yes --> G{NeedSet 全部满足?} + G -- no --> G1[定位尚无 admission 的 victim/resource] + G -- yes --> H[检查数据库功能激活与调用路径] +~~~ + +常见 deny reason 的解释: + +| 原因 | 优先检查 | +|---|---| +| PROTOCOL | frame version、长度、codec、nonce | +| PROVIDER_REJECTED | 目标映射、权限、设备明确拒绝 | +| PROVIDER_UNKNOWN | readback 不确定、设备转换中 | +| DAEMON_UNAVAILABLE | provider registry、配置、socket、worker | +| TIMEOUT | 管理网络、BMC 响应、deadline | +| JOURNAL | journal CRC、digest chain、fsync、磁盘空间 | +| MAPPING_CHANGED | mapping generation 或同世代配置变更 | +| REJOIN_INVALIDATED | rejoin gate、formation 或 operation ownership 改变 | +| IO_NOT_DRAINED | 目标虽 OFF,但共享存储 I/O 仍未证明排空 | + +## 19. Oracle RAC 与 PGRAC 的逐项对应 + +| 维度 | Oracle 已验证公开行为 | PGRAC 公开实现或目标 | 结论 | +|---|---|---|---| +| membership | CSS 管理成员关系与 join/leave 通知 | cssd/quorum/reconfiguration 提供集群事实 | 语义同类,不推断内部协议相同 | +| failure isolation owner | cssdagent 等 Clusterware 组件提供 I/O fencing | 独立 pgrac-fenced 权限域 | 责任分层相近,进程名与 wire 自研 | +| 外部控制 | 支持 IPMI/BMC,能不依赖受害 OS 重启节点 | provider ABI 支持外部 target;主干未含认证生产 provider | Oracle 行为已验证;PGRAC 当前仍 fail closed | +| 终态确认 | 公开文档强调 failure isolation 结果 | 精确 target + OFF/ON + DRAINED readback | PGRAC 显式适配,Oracle 未公开同样字段 | +| 恢复绑定 | RAC 在成员/恢复框架内隔离并恢复实例 | proof 绑定 formation、incarnation、protected set | 外部安全语义对齐,证书格式自研 | +| 持久证据 | Oracle 未公开 journal 格式 | CRC32C + SHA-256 链式 fixed-record journal | PGRAC 自研实现 | +| rejoin | Clusterware 管理节点或实例重新加入 | ADMIN/LMON 分权,要求 ON + DRAINED | 目标语义对齐,opcode 自研 | +| 当前生产可用性 | Oracle 产品提供相应 Clusterware 能力 | PGRAC 主干 provider-neutral 机制存在,生产激活关闭 | 不能宣称当前能力等价 | + +## 20. 公开源码导航 + +- 数据库侧 external-fence 消费与激活边界:[cluster_external_fence.c](../../../src/backend/cluster/cluster_external_fence.c) +- 数据库侧 NeedSet/AdmissionSet API:[cluster_external_fence.h](../../../src/include/cluster/cluster_external_fence.h) +- provider-neutral wire protocol:[pgrac_external_fence_protocol.h](../../../src/include/common/pgrac_external_fence_protocol.h) +- daemon 入口与当前生产边界:[pgrac_fenced.c](../../../src/bin/pgrac_fenced/pgrac_fenced.c) +- daemon FSM 与 peer credential:[pgrac_fenced_core.c](../../../src/bin/pgrac_fenced/pgrac_fenced_core.c) +- provider ABI、worker 与终态分类:[pgrac_fenced_provider.c](../../../src/bin/pgrac_fenced/pgrac_fenced_provider.c) +- 固定记录 journal:[pgrac_fenced_journal.c](../../../src/bin/pgrac_fenced/pgrac_fenced_journal.c) +- root-owned 配置与映射世代:[pgrac_fenced_config.c](../../../src/bin/pgrac_fenced/pgrac_fenced_config.c) +- control-root 功能激活:[cluster_control_root.c](../../../src/backend/cluster/cluster_control_root.c) + +相关架构文档: + +- [PGRAC 架构总览](../overview.md) +- [节点变化:rejoin、formation 与 recovery](../rac-node-change/README.md) +- [集群配置](../../user-guide/configuration.md) + +## 21. Oracle 官方资料 + +- [Introduction to Oracle Clusterware](https://docs.oracle.com/en/database/oracle/oracle-database/26/cwadd/introduction-to-oracle-clusterware.html) +- [Oracle Clusterware Administration — Node Failure Isolation](https://docs.oracle.com/en/database/oracle/oracle-database/26/cwadd/oracle-clusterware-administration.html) +- [Requirements for Enabling IPMI](https://docs.oracle.com/en/database/oracle/oracle-database/26/cwlin/requirements-for-enabling-ipmi.html) +- [Configuring the IPMI Management Network](https://docs.oracle.com/en/database/oracle/oracle-database/26/cwlin/configuring-the-ipmi-management-network.html) +- [Oracle Database Background Processes — FENC](https://docs.oracle.com/en/database/oracle/oracle-database/26/refrn/background-processes.html) + +## 22. 结论 + +External fencing 的价值不在于“能远程关机”,而在于把一个容易误判的物理世界事实,变成数据库能安全消费的短期权威: + +~~~text +外部 target 的精确终态 + × 正确的 node/incarnation + × 正确的 protected set + × 正确的 mapping/formation + × durable journal + × fresh proof + × live consumer validation += 仅对本次恢复需要有效的 admission +~~~ + +Oracle Clusterware 公开证明了 failure isolation、外部 BMC/IPMI 控制与集群恢复之间的必要关系;PGRAC 在这一语义边界上保持同样的安全方向,同时用 provider-neutral codec、链式 journal、精确 binding 和短期 AdmissionSet 完成 PostgreSQL 架构适配。 + +当前公开主干最重要的事实也同样明确:**机制框架已经存在,但没有认证生产 provider 就不开放恢复写权限。** 这不是功能缺陷的掩盖,而是共享存储集群在证据不完整时必须保持的 fail-closed 边界。 From 7aa5b1b64cde59c149a3aa14b1622bc85097649e Mon Sep 17 00:00:00 2001 From: sqlrush <160627417+sqlrush@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:15:05 +0800 Subject: [PATCH 2/3] docs: link external fencing deep dive --- docs/architecture/overview.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 3dd88145fa..5e22d16673 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -14,6 +14,9 @@ For the global resource services themselves, see the Chinese deep dives for For transaction recovery authority, PREPARED / in-doubt handling, and the `pg_xact_remote_v2` projection boundary, see [未决事务恢复:Oracle RECO 语义与 PGRAC 投影边界](transaction-recovery/pending-transaction-authority.md). +For external I/O failure isolation, terminal readback, recovery admission, +and safe rejoin, see +[外部 I/O Fencing:从节点驱逐到可验证的恢复权限](external-fencing/README.md). ## High-level component map @@ -375,6 +378,7 @@ strength and remaining boundaries are documented here: - [PGRAC GES 与 Oracle RAC Global Enqueue 图解](ges/README.md) - [Oracle RAC 与 PGRAC 节点变化机制图解](rac-node-change/README.md) - [未决事务恢复:Oracle RECO 语义与 PGRAC 投影边界](transaction-recovery/pending-transaction-authority.md) +- [外部 I/O Fencing:从节点驱逐到可验证的恢复权限](external-fencing/README.md) Do not infer that every registered wait event or protocol enum represents an end-to-end production path. Each deep dive distinguishes production wiring, From 59b985079ee01b389f5325ce9f698325e798837b Mon Sep 17 00:00:00 2001 From: sqlrush <160627417+sqlrush@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:15:37 +0800 Subject: [PATCH 3/3] docs: normalize external fencing guide