ADR 0006: CloudEdge Event Federation(routerd 間的型別化事件)

狀態
已核准。實驗性實作進行中 — 2026-05-30。
Phase 1、1.5、2、3 已在 event-federation 分支實作:
- Phase 1(事件信封 +
EventGroupKind + SQLite 本機儲存 +routerctl federation event emit/list)— 完成。 - Phase 1.5(
EventPeer/EventSubscriptionKind + 驗證)— 完成。 - Phase 2(經由 overlay 的 peer 投遞、
routerd-eventd、HMAC、重試、 retention 清理)— 完成。lab-smoke PASS (傳輸證據)。 - Phase 3(subscription → plugin →
RemoteAddressClaimDynamicConfigPart)— 完成。lab-smoke PASS (subscription 證據、 how-to)。
Phase 4(provider actionPlan 外掛、dry-run)下一階段尚未開始。
Phase 5(provider action 執行)不在 MVP 範圍內。
背景
SAM(參考、
里程碑)已在
Azure×PVE、AWS×PVE、OCI×PVE 上完成乾淨驗證(3 雲對等)。SAM 證明了
capture(provider 特定)/ delivery+claim(routerd 通用) 的分離。然而,
驅動它的 RemoteAddressClaim 目前仍是手動描述的。下一步是透過
事件驅動來發現、傳播和實體化 claim:
on-prem 的 routerctl 偵測到用戶端 IPv4(ARP/Clients/DHCP)→ 發出型別化事件 → federation 匯流排將其投遞到雲端 routerd → subscription 啟動 provider 外掛 → 外掛以
DynamicConfigPart形式傳回RemoteAddressClaim(+ provider secondary-IPactionPlan)→ 無需人工編輯雲端組態, 雲端即準備好執行provider-secondary-ipcapture。
現有資產(MVP 並非從零開始)
設計基於目前的程式碼樹。大多數建構區塊已經存在, 真正全新的工作是節點間 federation 傳輸和 事件→外掛 subscription 觸發器:
- 型別化事件信封:
pkg/daemonapi的DaemonEvent{Type,Time,Daemon,Resource, Severity,Reason,Message,Attributes}+NewEvent(...)。目前是 daemon→main 流程, 但已經是帶型別和 topic 的信封。 - daemon→routerd 傳輸模式: daemon 透過 UNIX 套接字上的
HTTP POST 到控制套接字(
cmd/routerd-dhcp-event-relay→controlapi.Prefix + /dhcp-lease-eventviaunix:/run/routerd/routerd.sock)。已有事件中繼 daemon 的先例。 - 分離的長生命週期 daemon 先例: 13 個
cmd/routerd-*daemon (routerd-bgp、routerd-ra-observer、routerd-dhcp-event-relay等)。 gobgp pivot(ADR 0004)確立了「為避免重啟導致的中斷,使用分離行程而非行程內嵌入」。 - Plugin → DynamicConfigPart 管線:
pkg/plugin/runner.go、pkg/plugin/dynamic_config.go、pkg/dynamicconfig/{types,merge}.go、PluginRequest/PluginResult。effective = startup + active dynamic − masks。 - 狀態: SQLite(
pkg/state/sqlite.go)。 - Provider profile + 外部驗證:
CloudProviderProfile、auth.mode=external-command(specs.go:1193)— provider 特定外掛的鉤子。provider: oci|aws|azure|gcp已通過驗證。
決策
將 CloudEdge Event Federation 作為下一個實驗性 MVP,建構在已合併的實驗性 SAM 之上的新分支中。不縮減範圍,而是分解為有序的、可獨立驗收的階段,每個階段作為一個工作流驅動。 每個階段交付一個可運作、可展示的切片,並作為下一階段的準入閘門。
設計原則
- 事件是觀測事實,不是組態。 節點傳送
routerd.client.ipv4.observed,而不傳送原始的RemoteAddressClaim。接收端的 受信任本機外掛決定是否以及如何將其轉換為型別化 claim + actionPlan。線路上不傳輸命令。 - at-least-once + 冪等,而非 exactly-once。儲存的冪等性以事件
id為鍵 (重複id為 no-op insert)。dedupeKey是 subscription 端的分組鍵, 用於彙總同一事實的重複觀測,在 Phase 1 中不是 DB 的唯一約束。動態資源名稱是確定性的 (onprem-10-88-60-9)。provider action 如已滿足則為 no-op。無共識、無 gossip、無全序。 - 複用,不重新發明。 複用
DaemonEvent信封、控制套接字 HTTP 傳輸慣例、 Plugin→DynamicConfigPart 管線、SQLite 狀態、CloudProviderProfile/Plugin(不需要新的CloudProviderPluginKind)。 - 新增 Kind 最少化。 MVP 引入 3 個:
EventGroup(匯流排識別符 + 驗證 + retention)、EventPeer(投遞目標 + 內聯的 push/receive 篩選器)、EventSubscription(接收事件 → 本機外掛觸發器)。原提案中獨立的EventFilter已合併到EventPeer,僅在需要跨 peer 共享篩選器時才提升為獨立 Kind。 - 分離的 daemon。 Federation 的收發放在新的
cmd/routerd-eventd長生命週期 daemon 中(遵循 ADR 0004 先例)。不在 reconcile 迴圈內。 僅繫結到 overlay(wg-hybrid)。 - MVP 中 provider mutation 保持 dry-run。 外掛發出
actionPlan。 執行放在後續階段,位於明確的 approval/auto-apply 策略之後。
傳輸與安全(MVP)
- 接收端 = 僅繫結到 WireGuard overlay 介面/位址的 HTTP 監聽器
(例:
169.254.x.y:9443)。WG 隧道是機密性邊界。為完整性和防止誤投遞新增 訊息層級 HMAC(來自檔案的共享密鑰)。 TLS 延後 — TLS 監聽器需要憑證佈建,會重新引入 SAM stocktake 指出的引導摩擦。(未來:mTLS / 每 peer 的 Ed25519 / 雲 KMS 簽章。) - MVP 中僅 push(
onprem→cloud的觀測,cloud→onprem的 claim/result ack)。 - 帶退避的重試。每 (event, peer) 的投遞狀態保存在 SQLite。
應在狀態機層面審查的關鍵不變量(而非僅審查差分)
遵循專案對行程外有狀態 daemon 的規則, 將正確性條件描述為不變量:
- 禁止回饋迴圈。 節點不得對自身正在 capture 的位址(provider-secondary-ip
或 proxy-arp)重新發出
*.observed。觀測以ownerSide+domain為範圍, capture 的/secondary 位址從觀察者的來源集合中排除。 否則,雲端自身的 secondary.9會被重新觀測 → 重新傳播 → 震盪。 - provision 與 de-provision 的不對稱性。 provisioning(claim 出現)可以即時。
de-provisioning(TTL 過期 /
*.expired)必須具有遲滯性 — 遠大於 300 秒 observe TTL 的寬限期 + 去抖。震盪的用戶端不得反覆驅動 雲端 secondary-IP 的 assign/unassign(API 速率限制 + 成本 + 資料平面擾動)。TTL→teardown 策略應明確且保守。 - (domain, address) 單寫入者。 擁有方具有權威。接收方僅為
ownerSide是傳送方的位址提議 claim。 - 冪等的 provider action。 "already assigned" ⇒ 跨 aws/azure/oci 均為 success/no-op。
Provider 外掛框架
呼叫 OS CLI 的本機可執行檔。不是將 SDK 靜態連結到 routerd (將 SDK 的變更/驗證排除在核心之外,啟用雲原生身分,便於除錯):
- AWS:
aws ec2 assign-private-ip-addresses— 驗證:優先 IAM 執行個體設定檔,AWS_PROFILE/env 回退。 - Azure:
az network nic ip-config …— 驗證:優先受控識別,az login/SP env 回退。 - OCI:
oci network private-ip create/vnic— 驗證:優先執行個體主體, OCI config profile 回退。
Plugin.capabilities 對外掛權限進行閘門控制
(observe.events/propose.dynamicConfig/propose.providerAction)。
階段分解(每階段 1 個工作流,依序執行)
每個階段 = 可獨立驗收的切片。後續階段以先行階段的驗收為閘門。 實作委託給 codex,claude 負責編排 + 審查。
- 已完成 — Phase 1 — 事件模型 + 本機儲存。
EventGroupKind。將DaemonEvent複用/擴充為外部Event信封(id, group, sourceNode, type, subject, ttl, dedupeKey, payload)。 SQLitefederation_events資料表。routerctl federation event emit/list。 驗收條件: emit→stored(帶 TTL)。重複 id 冪等。過期被忽略。 - 已完成(lab-smoke PASS)— Phase 1.5 —
EventPeer/EventSubscriptionKind + 驗證。 - 已完成(lab-smoke PASS)— Phase 2 — 經由 overlay 的 peer 投遞。
EventPeerKind。routerd-eventd接收端繫結到wg-hybrid。HMAC。push + 退避。event_deliveries。 驗收條件: on-prem 經由wg-hybridpush 到雲端。重複 push 冪等。錯誤 HMAC 被拒絕。routerctl event deliveries。routerd-eventd按EventGroupretention(maxAge/maxEvents) 定期清理federation_events。routerctl federation event prune --dry-run報告 待刪除項。 - 已完成(lab-smoke PASS)— Phase 3 — subscription 觸發的外掛 → DynamicConfigPart。
EventSubscriptionKind。事件批次 →PluginRequest。PluginResult→DynamicConfigPart(帶routerd.net/dynamic-source、event-id、event-group註解)。去抖/batchWindow。event_subscription_runs。 驗收條件: 雲端收到10.88.60.9/32的client.ipv4.observed→ 外掛 →RemoteAddressClaimDynamicConfigPart 可透過routerctl dynamic render確認。 actionPlan 僅顯示,不執行。 - 下一步(未開始)— Phase 4 — provider actionPlan 外掛(dry-run)。
aws/azure/oci-address-claim範例外掛。標準化actionPlan格式。執行個體 ID 驗證。 驗收條件: 外掛提議 assign-secondary-IP。無 mutation。計畫可透過routerctl plugin/dynamic確認。 - Phase 5 —(MVP 後)provider action 執行。 approval/auto-apply 策略、 action 日誌、盡力 undo、身分文件。不在 MVP 範圍內。
首個端對端冒煙測試為 手動 routerctl federation event emit →
federation → DynamicConfigPart(Phase 1-3)。ARP/Clients 觀察者外掛在
該冒煙測試之後引入(以 routerd-ra-observer 為模型),以便隔離故障。
MVP 事件類型
routerd.client.ipv4.observed、…ipv4.expired、…dynamic.part.accepted/rejected、
…provider.action.planned/succeeded/failed。首次冒煙測試僅需 observed+expired。
結論
- 正面影響: 將 SAM 從手動描述轉變為事件驅動。小而可展示的階段。 複用現有信封/傳輸/外掛/狀態。新增 Kind 不膨脹(3 個)。 provider mutation 有閘門控制。從第一天起支援雲原生身分。
- 負面影響 / 風險: 新增網路監聽器(透過 overlay 繫結 + HMAC 緩解)。 迴圈/震盪和 provision/de-provision 的不對稱性需作為不變量強制執行(見上文)。 at-least-once 將冪等性推到了外掛和命名上。TLS/mTLS 延後。 de-provisioning 的自動化被有意最後啟用。
- 不在 MVP 範圍內: 共識、exactly-once、gossip mesh、任意遠端命令執行、 provider mutation 自動化、完整的 IP 生命週期自動化、遠端外掛登錄檔、 跨節點組態重寫。
已知限制(實驗性)
routerd-eventd的 supervision 為 systemd 和 FreeBSDrc.d產生。 其他服務管理員需要渲染器的顯式支援才能自動管理 eventd。EventSubscription的batchWindow/debounce被接受但是粗粒度的。 欄位經過驗證,以輪詢粒度生效 — 控制器在 每次輪詢 tick 時批次處理事件,而不是以精確的亞 tick 計時器運作。短的去抖視窗 實際上會被向上取整到 tick 間隔。
範圍外 / 未來的開放問題
- 是否需要從
cloud→onprem傳遞 ack 以外的內容(例如:雲端 secondary 存在後 切換 on-prem proxy-arp 的 capture-ready 訊號)。 - 跨 peer 共享篩選器的功能(將
EventFilter提升為獨立 Kind)。 - 多 peer / 3 節點以上的 group(MVP 針對已驗證的成對拓撲)。