| RFC-0110:重新啟動以終止重要元件 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | v2 元件功能,提供與 v1 critical_components 同等的功能 |
| Gerrit 變更 | |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 2021-05-26 |
| 審查日期 (年-月-日) | 2021-07-21 |
摘要
提案:在元件資訊清單的子項宣告中導入「reboot-on-termination」選項,與 sysmgr 的 critical_components 功能保持一致。
提振精神
在 Components v1 中,sysmgr 支援名為 critical_components 的功能,可讓系統服務元件將自己標示為「重要」。也就是說,如果元件因任何原因 (包括正常結束) 終止,sysmgr 就會觸發系統重新啟動。這項重新啟動是由 power_manager 驅動的正常重新啟動,會導致元件拓撲依序關機。正常重新啟動會以一致的方式關閉系統,並讓元件有機會完全關閉,從而保留診斷資訊,並完全關閉檔案系統。
如果用戶不確定元件故障時,系統是否能正常運作,通常會在元件上設定這個選項。不意外的是,這個選項通常會設定在服務在系統運作中扮演核心角色的元件上,例如:
netstackwlanstackomaha-client-servicesystem-update-checker
除了 critical_components 實作的相對簡單策略外,還有許多可能的當機復原策略。這項設計的重點在於解決該使用案例。超出 critical_components 提供的當機復原功能不在範圍內 (但請參閱「未來工作」)。
需求條件
主要需求是提供與 critical_components 同等的功能。
也就是說,core 或 core 的子領域中的元件,應該可以選擇在元件終止時觸發正常重新啟動。
為何現在發行?
動機中提及使用 critical_components 的元件,在提供同等功能前,都無法遷移至 Components v2。
設計
我們會在 ChildDecl 中新增 on_terminate 列舉 (相當於元件資訊清單的 children 區段),提供與 critical_component 等效的語意。有兩種選項:none (預設) 或 reboot。當子項元件因任何原因 (包括正常結束) 終止時,component_manager 會從 power_manager 公開的 fuchsia.hardware.power.statecontrol.Admin 通訊協定叫用 Admin/Reboot 方法,觸發系統正常重新啟動。on_terminate: reboot
這會導致 component_manager 和 power_manager 之間出現依附元件週期。不過,兩者都在 ZBI 中,因此沒有明顯的圖層問題。無論如何,由於重新啟動會導致裝置的電源狀態變更,而這屬於驅動程式庫的職責,因此無法避免某種程度的依附元件反轉。
如果呼叫 Admin/Reboot 失敗,component_manager 會改回引發恐慌,觸發不正常的重新啟動。
這項功能很敏感,我們不希望任意元件在終止時單方面決定觸發重新啟動。因此,component_manager 安全性政策中的許可清單會限制其使用方式,並在元件啟動時進行檢查。此外,我們可以使用 restricted_features GN 允許清單,在無權使用這項功能的領域中,於子項設定選項時產生建構時間失敗。
實作
on_terminate選項
我們需要在資訊清單的 child 區段中新增 on_terminate 選項。這需要變更 cmc、cmc_fidl_validator 和 cm_rust,才能將選項傳遞至管道。由於這是特殊功能,我們允許在 ComponentDecl 中將其設為 None (當然,預設為 on_terminate: none)。
我們會在 cmc 中為 on_terminate 新增 restricted_feature,只有許可清單中的 CML 檔案,才能在子項上設定 on_terminate: reboot。這份許可清單一開始會包含 core 和 network 領域。
我們也會在 component_manager 的設定中新增 reboot_on_terminate_enabled bool,以便為元件管理員的非根執行個體停用這項功能 (例如測試中的巢狀執行個體)。
偵測重新啟動終止元件的終止狀態
您必須在 component_manager 中新增邏輯,偵測何時終止「終止時重新啟動」元件。在 Stop 動作期間,component_manager 可以勾選 on_terminate 選項。如果已設定,且元件未關閉,則會component_manager 呼叫 Admin/Reboot。關閉表示元件正在停止,且不會再次啟動,這會在下列情況發生:
- 在系統關機期間,這項程序本身是由
Admin/Reboot通訊協定觸發。在這種情況下,系統已在關機,因此再次觸發關機沒有意義。 - 元件遭到刪除時。這可能是因為 (a) 明確呼叫
DestroyChild、(b)transient集合的父項停止,或 (c)single-run集合中的元件結束。在 (a) 和 (b) 的情況下,不觸發重新啟動似乎是正確的決定,因為導致元件停止運作的原因是元件外部的動作,而非元件內部的終止。如果是 (c) 的情況,只要我們謹慎實作這項功能,就能確保元件結束時會觸發重新啟動,方法是只在元件終止後觸發毀損程序。
呼叫 fuchsia.hardware.power.statecontrol.Admin 通訊協定
如要觸發正常重新啟動,請連線至通訊協定 fuchsia.hardware.power.statecontrol.Admin 並呼叫 Admin/Reboot。這個通訊協定是由 power_manager 元件實作。
(基於歷史因素,這項服務實際上是由 shutdown_shim 代理。)由於這個通訊協定是由元件實作,component_manager 如何存取該通訊協定?為此,我們可以讓 root 向父項公開通訊協定。#bootstrap也就是說,根節點會將通訊協定公開給根節點「上方」的節點,也就是 component_manager。如需這項反轉的詳細說明,請參閱「設計」。
原型
如要查看原型,請按這裡。
效能
這個設計沒有任何效能考量。只有在 on_terminate: reboot 元件實際終止時,component_manager 才會開啟與 fuchsia.hardware.power.statecontrol.Admin 的連線。
人體工學
這項設計符合簡單的人體工學:如要在元件上設定「終止時重新啟動」,只需執行下列操作:
- 在父項的
ChildDecl中設定on_terminate: reboot(CML 中的聲明)。children - 如果不在許可清單中,請將家長的 CML 新增至
on_terminate: reboot的cmcrestricted_features許可清單。 - 將元件的路徑名稱新增至政策許可清單,以便在終止時重新啟動。
由於 on_terminate 選項是由父項而非元件本身設定,因此在測試中,您可以使用應在正式環境中觸發重新啟動的元件,不必修改 CML。此外,這樣一來,您就能在不同產品設定中加入元件,並以不同方式設定選項,不必變更元件。
回溯相容性
這項變更不會破壞相容性。用戶端必須明確選擇在終止時重新啟動。
安全性考量
假設使用者將不應重新啟動的元件標示為「終止時重新啟動」,濫用這項功能,不當觸發重新啟動。不過,由於用途受到安全政策允許清單限制,因此新用途必須獲得明確核准。請注意,不可信的元件無法透過嵌入已加入許可清單的元件,欺騙 component_manager 授予重新啟動權限,因為元件是依據其路徑名稱 (拓撲路徑) 加入許可清單,而非網址。
隱私權注意事項
這項提案不會帶來新的隱私權考量。
測試
我們可以模擬 fuchsia.hardware.power.statecontrol.Admin 通訊協定,輕鬆整合測試這項功能。我們應記得測試不順利的路徑,例如通訊協定遺失或失敗時。
理想情況下,應為終止時重新啟動的元件新增 E2E 測試涵蓋範圍,以驗證終止作業確實會觸發正常重新啟動。
說明文件
必須進行下列文件變更:
- 新增
on_terminate選項的文件,以平行執行critical components。 - 更新遷移指南,說明如何遷移
critical_component
缺點、替代方案和未知事項
優點和缺點
福利
- 設定方式非常簡單。
- 與第 1 版直接對等,方便遷移。
- 由於這項功能完全位於
component_manager中,因此實作方式很簡單,而且不會有事件遺失等故障模式的風險。 - 可讓我們將部分
main_process_critical用途替換為on_terminate: reboot,後者嚴格來說更勝一籌。 - 讓用戶在正式環境中運用已設定
on_terminate: reboot的元件,不必進行修改。
缺點
- 並非以能力為基礎,與正統架構模型不同。
- 直接在
component_manager中編碼部分當機復原政策。雖然我們一般不鼓勵這種做法,但這個案例的政策很簡單,因此成本雖然不為零,但似乎很低。 - 在
power_manager上導入component_manager的反向依附元件。不過,兩者都位於 ZBI 中,因此並未嚴重違反分層規定。 - 由於這項功能涉及 CML 結構定義變更,因此需要透過多個位置進行整合:
cmc、cm_fidl_validator、cm_rust和cm_rust的用戶端,即使這項功能屬於利基功能也一樣。
替代:system_critical 位元 (program)
我們可以在元件資訊清單的 program 區段中新增選項,而不是新增至 ChildDecl。這種做法的主要差異在於,選項是設定在元件本身,而不是父項中的子項宣告。
將位元放在 program 中,可將專門功能保留在 ComponentDecl 之外,由於 program 從 ComponentDecl 的角度來看是任意形式的語法,因此我們不需要變更 cmc、驗證器或 Rust 繫結,即可納入新選項。我們只需要在 component_manager 本身新增邏輯,在元件停止時從 program 擷取選項 (判斷是否需要重新啟動)。
不過,這種做法有一個明顯的缺點:如果測試中使用了 system_critical 元件,就必須變更其 CML,移除 system_critical 位元 (因為測試領域不允許設定該位元,且我們不希望測試觸發系統重新啟動)。這會增加用戶端維護負擔,因為用戶端會編寫利用元件的整合測試。
替代做法:使用 main_process_critical
ELF 執行元件支援名為 main_process_critical 的功能,當元件以非零狀態結束或遭到終止時,會導致 component_manager 的根工作終止。這會導致系統非正常重新啟動。由於重新啟動並非正常程序,系統會因此不正常關機,無法儲存診斷資訊或指標。
main_process_critical 只能在無法觸發正常重新啟動的位置使用。舉例來說,power_manager 本身會標示為 main_process_critical。由於任何重要元件都不適用這種做法,因此這個選項並非可行的替代方案,但為了完整性,我們還是列在這裡。
替代方案:主管
我們可以在 core 領域中管理當機復原作業,而不必在 component_manager 中管理。這個替代方案分為兩部分。首先,請導入「元件範圍」事件,讓消費者監控單一元件例項範圍內的事件 (特別是 Started 和 Stopped 事件)。其次,導入名為「監管程式」的元件,該元件會消耗這些事件,監控異常終止或啟動失敗的情況,並據此重新啟動系統。
元件範圍事件
元件架構團隊討論過一項構想,就是提供一種方法,讓事件功能限定在單一元件執行個體,而非整個領域。這項設計為這個概念提供具體應用。主管只需要監控特定元件,因此接收這些元件的事件 (而非整個領域的事件) 比較合理。
就速度而言,我們建議盡可能減少 CML 的變更,以啟用這項功能。日後,我們可能會進行更大幅度的語法修訂,以不同方式指定事件的範圍 (請參閱元件事件 RFC)。我們會在 offer event 宣告中新增 scope 欄位,可指定 #child 或 realm (預設)。
// core.cml
offer: [
{
event: "started",
from: "framework",
scope: "#wlanstack",
to: "#supervisor",
as: "started-wlanstack",
},
{
event: "stopped",
from: "framework",
scope: "#wlanstack",
to: "#supervisor",
as: "stopped-wlanstack",
},
],
由於語法日後可能會修訂,我們可以cmc將 scope 功能加入core.cml和整合測試的允許清單。
元件範圍事件不會在酬載中攜帶元件身分識別資訊,例如路徑名稱或網址。一般而言,事件的酬載中可能含有敏感資訊,例如元件代號或網址,我們只會在有必要時公開這些資訊。由於主管不需要這項資訊,元件範圍事件不會提供產生事件的元件身分資訊。酬載中的其餘資訊為時間戳記和終止狀態,不屬於機密資訊。
監督者
監督程式本身很簡單,這是 core 下的元件,可執行下列操作:
- 使用靜態
event_stream,其中包含Started和Stopped事件清單。 - 如果在此 event_stream 中收到含有錯誤的
Started事件,或含有非 ok 狀態的有效負載的Stopped事件,請呼叫 fuchsia.hardware.power.statecontrol/Admin.Reboot,觸發正常重新啟動。
這是 critical_components 功能的簡單實作目標。日後,監督程式可能會支援更多用途,或出現多個監督程式,詳情請參閱「未來工作」。
將事件轉送給主管
元件範圍事件必須從每個重要元件傳送至監管程式。如為 core 的子項重要元件,則需要進行兩項變更:
- 修改 core.cml,將 Started 和 Stopped 事件從元件傳送至管理程序 (請參閱「元件範圍事件」)
- 修改主管的 CML,以便在靜態事件串流中取用事件。
如果重要元件巢狀內嵌於 core 的子領域下,則需要執行另一個步驟:
- 修改每個中介元件,將子項的事件公開給父項。
舉例來說,netstack 很可能就是這種情況,因為我們計畫讓 netstack 位於 core 下的 network 子領域中。netstack
以下是主管的 CML 範例:
// supervisor.cml
use: [
{
events: [
"netstack-started",
"netstack-stopped",
"wlan-started",
"wlan-stopped",
],
},
// The supervisor will trigger reboot under the following conditions:
// - It receives a `started` event with an error.
// - It receives a `stopped` event with a non-ok status.
{
event_stream: "EventStream",
subscriptions: [
{
event: [
"netstack-started",
"netstack-stopped",
"wlan-started",
"wlan-stopped",
],
on_receive: "start",
},
],
},
],
...
請注意,您不需要修改要監看的元件。這是刻意設計:監督功能是領域管理元件的方式,而非元件本身。換句話說,元件不負責決定是否或如何受到監督。
啟動管理員
我們必須確保監督程式一律及時啟動,才能接收事件。為此,我們建議在event_stream訂閱項目
中新增名為「on_receive: "start"」的選項。on_receive: "start",component_manager
會在收到該事件時自動啟動元件。這樣一來,component_manager 可確保事件絕不會遺失。預設選項 "dispatch_if_started" 只會在元件已在執行時,將事件分派至該元件 (預設行為)。
這需要變更事件傳送系統。具體來說,事件分派時,component_manager 必須遵循任何已路由事件功能,以免靜態事件串流耗用這些功能。否則,即使元件已標示 on_receive: "start",如果尚未解決,元件仍可能會錯過事件。
或許可以將 on_receive: "start" 設為靜態事件串流的預設行為,但這超出本提案的範圍。
優點和缺點
福利
- 避免在
component_manager中編碼當機復原政策。這有助於更妥善地分離關注事項,因為一般而言,我們並不十分瞭解哪些類型的當機復原政策足夠通用,可證明在component_manager中直接支援這些政策是合理的。 - 這個做法比
recovery選項更具彈性。在 basemgr 和 sessionmgr 中,必須為代理程式和工作階段本身實作與重新啟動復原不同的當機復原政策。
缺點
- 需要事件系統支援,但這類系統必須自行建構。這會增加事件系統的複雜度,而且可能需要比直接在
component_manager中實作解決方案更多時間和精力。 - 需要比
recovery更多的樣板。每個重要元件的事件都必須從各個重要元件傳送至監管員。 - 我們最終需要解決
basemgr/sessionmgr中的類似問題。 如果我們延後設計更通用的解決方案,屆時可能對問題空間有更深入的瞭解。
之後的作業
basemgr 和 sessionmgr 實作自己的當機復原策略,這類策略可採用類似監管程式替代方案的方法。
「fshost」和「archivist」目前使用「main_process_critical」。他們可能改用 terminate-on-reboot。這樣我們就能將 main_process_critical 限制為參與重新啟動程序的元件 (driver_manager 和 power_manager)。
部分路徑仍會觸發不正常的重新啟動:
- 這項設計會建立
component_manager對power_manager的反向依附元件,並間接建立對driver_manager的反向依附元件。因此,這些元件無法使用「terminate-on-reboot」,而是標示為main_process_critical,表示其中任一元件當機都會觸發不正常的重新啟動。 - 如果
Reboot呼叫本身失敗,component_manager會發生恐慌,這也會觸發不正常的重新啟動。
在這些情況下,我們可能會執行更優雅的關機程序;舉例來說,component_manager 可以執行正常的系統關機程序,然後結束。另一方面,由於 power_manager 和 driver_manager 對系統運作至關重要,因此如果當機,我們可能不希望系統繼續運作。
我們可能會重新檢討電源管理責任的分配方式;舉例來說,component_manager 可能可以自行啟動重新開機 (但仍需依賴 driver_manager 設定電源狀態)。
系統完全遷移至 Components v2 後,元件管理工具就有可能運用依附元件關係圖的知識,支援更智慧的復原策略。
既有技術和參考資料
critical_components 功能和事件 API 修訂版本有私人設計文件。