RFC-0110:終止重要元件並重新啟動

RFC-0110:關閉關鍵元件時需重新啟動
狀態已接受
區域
  • 元件架構
說明

與 v1 的 critical_components 同等的 v2 元件功能

Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2021-05-26
審查日期 (年-月-日)2021-07-21

摘要

這項提案旨在為元件資訊清單的子項宣告引進「reboot-on-termination」選項,以便與 sysmgr 的 critical_components 功能保持一致。

提振精神

在 Components v1 中,sysmgr 支援名為 critical_components 的功能,可讓系統服務元件將自己標示為「critical」。也就是說,如果元件因任何原因 (包括正常結束) 而終止,sysmgr 就會觸發系統重新啟動。這項重新啟動作業是由 power_manager 驅動的正常重新啟動,會導致元件拓撲經過有序關機。優雅重新啟動會以一致的方式拆解系統,並讓元件有機會徹底關閉,以便保留診斷資訊,並徹底關閉檔案系統。

如果客戶不確定系統在元件發生錯誤時,是否能繼續執行正常行為,通常會在元件上設定這個選項。毫不意外,這個選項通常會設在服務在系統運作中扮演關鍵角色的元件上,例如:

  • netstack
  • wlanstack
  • omaha-client-service
  • system-update-checker

除了由 critical_components 實作的相對簡單的策略之外,還有許多可能的當機復原策略。這項設計著重於解決該用途。critical_components 提供以外的當機復原功能超出範圍 (但請參閱未來工作)。

需求條件

主要要求是提供與 critical_components 同等的功能。也就是說,如果元件終止,corecore 的子領域下的元件應可選擇觸發平穩重新啟動。

為何現在發行?

動機中提及的元件 (使用 critical_components) 會在相等功能推出前,無法遷移至 Components v2。

設計

我們會將 on_terminate 列舉新增至 ChildDecl (等同於 元件資訊清單children 部分),提供與 critical_component 等同的語意。您有兩種選擇:none (預設) 或 reboot。當含有 on_terminate: reboot 的子項元件因任何原因 (包括正常結束) 而終止時,component_manager 會從 power_manager 公開的 fuchsia.hardware.power.statecontrol.Admin 通訊協定中叫用 Admin/Reboot 方法,以觸發系統重新啟動。

這會導致 component_managerpower_manager 之間出現依附元件循環。不過,兩者都位於 ZBI 中,因此沒有重大的層疊問題。無論如何,都無法避免某種程度的依附元件反轉,因為重新啟動會導致裝置的電源狀態發生變化,而這項作業是驅動程式庫的責任。

如果呼叫 Admin/Reboot 失敗,component_manager 會改為進入恐慌狀態,觸發不正常重新啟動。

這是一項敏感功能,我們不希望任意元件在終止時單方面決定觸發重新啟動。因此,其使用方式會受到 component_manager 安全性政策中許可清單的限制,系統會在元件啟動時於執行階段檢查該清單。此外,如果在未授權使用該功能的領域中,將選項設為子項,我們可以使用 restricted_features GN 許可清單產生建構時間失敗。

實作

on_terminate選項

我們需要將 on_terminate 選項新增至資訊清單child 區段。這項作業需要變更 cmccmc_fidl_validatorcm_rust,才能將選項傳送至系統。由於這是特殊功能,我們會允許在 ComponentDecl 中將其設為 None (預設為 on_terminate: none)。

我們會為 on_terminatecmc 中新增 restricted_feature。只有這個許可清單中的 CML 檔案才能在子項上設定 on_terminate: reboot。一開始,這份許可清單會包含 corenetwork 領域。

我們也會在 component_manager 的設定中新增 reboot_on_terminate_enabled 布林值,以便為元件管理員的非根目錄執行個體停用 (例如測試中的巢狀執行個體)。

偵測終止「終止時重新啟動」元件的情況

您必須在 component_manager 中新增邏輯,才能偵測到「終止時重新啟動」元件何時終止。在 Stop 動作期間,component_manager 可以檢查 on_terminate 選項。如果已設定,且元件未關閉,component_manager呼叫 Admin/Reboot。關閉表示元件已停止,且不會再啟動,這種情況會發生在下列情況:

  1. 系統關機期間,系統會觸發 Admin/Reboot 通訊協定。在這種情況下,系統已關機,因此無需再次觸發關機。
  2. 元件遭到銷毀時。這可能發生在 (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: rebootcmc restricted_features 許可清單。
  • 將元件的路徑名稱加入政策許可清單,以便在終止時重新啟動。

由於 on_terminate 選項是由父項而非元件本身設定,因此可在測試中利用應在正式版中觸發重新啟動的元件,而無須修改 CML。此外,這也讓您可以在希望以不同方式設定選項的不同產品設定中加入元件,而無須變更元件。

回溯相容性

這項變更不會破壞相容性。用戶端必須明確啟用「終止時重新啟動」功能。

安全性考量

理論上,使用者可以濫用這項功能,將不應標示為「終止時重新啟動」的元件標示為「終止時重新啟動」,進而觸發不當的重新啟動。不過,由於使用方式受到安全政策許可清單的限制,因此必須獲得明確核准才能使用。請注意,不受信任的元件無法透過嵌入許可清單中的元件,欺騙 component_manager 授予重新啟動權限,因為元件是根據其路徑名稱 (拓樸路徑) 而非網址列入許可清單。

隱私權注意事項

這項提案並未引入新的隱私權考量。

測試

我們可以模擬 fuchsia.hardware.power.statecontrol.Admin 通訊協定,輕鬆整合測試這項功能。我們應記得測試不理想的路徑,例如當通訊協定遺失或失敗時。

理想情況下,應為終止時重新啟動的元件新增 E2E 測試涵蓋率,以便驗證終止作業是否確實會觸發平順重新啟動。

說明文件

必須進行下列文件變更:

  • on_terminate 選項的文件新增至平行 critical components
  • 更新遷移指南,說明如何遷移 critical_component

缺點、替代方案和未知事項

優點和缺點

福利

  • 設定方式非常簡單。
  • 與 v1 版本完全相同,可輕鬆遷移。
  • 由於這項功能完全位於 component_manager 中,因此實作起來相當簡單,而且不會有事件遺失等失敗模式的風險。
  • 這樣一來,我們就能將 main_process_critical 的部分用途替換為 on_terminate: reboot,後者優於前者。
  • 讓用戶端能夠利用在實際環境中設定 on_terminate: reboot 的元件,而無需修改。

缺點

  • 不以能力為準,這與傳統架構模型不同。
  • 直接在 component_manager 中編碼部分當機復原政策。雖然這不是我們一般鼓勵的做法,但在本例中,政策很簡單,因此成本雖然不為零,但似乎很低。
  • 透過 component_manager 引入 power_manager 的反向依附元件。不過,兩者都位於 ZBI 中,因此並未違反重大重疊規定。
  • 由於涉及 CML 結構定義變更,因此這個選項需要透過以下幾個地方進行管線處理:cmccm_fidl_validatorcm_rustcm_rust 的用戶端,即使這是一個小眾功能也一樣。

替代方案:program 上的 system_critical

我們可以將選項新增至元件資訊清單program 區段,而非新增至 ChildDecl。這項做法的主要差異在於,選項是在元件本身上設定,而非父項中的子項宣告。

將位元放在 program 中的優點是,可將專門功能保留在 ComponentDecl 中。從 ComponentDecl 的角度來看,program 具有自由格式語法,因此我們不需要變更 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 中管理。這個替代方案分為兩個部分。首先,我們會引入「元件範圍」事件,讓使用者能夠監控範圍限定為單一元件執行個體的事件 (特別是 StartedStopped 事件)。第二,引入稱為supervisor 的元件,該元件會使用這些事件監控異常終止或啟動失敗,並在發生時重新啟動系統。

元件範圍事件

元件架構團隊討論過的想法是,提供一種方法,讓事件功能的範圍限定在單一元件執行個體,而非整個領域。這項設計為這項概念提供了具體應用方式。監控器只需要監控特定元件,因此只會接收有關這些元件的事件,而非整個領域。

針對速度,我們建議您只在必要時才對 CML 進行變更,以便啟用這項功能。日後,我們可能會進行更多重大的語法修訂,以不同方式指定事件的範圍 (請參閱「元件事件 RFC」)。我們會在 offer event 宣告中新增 scope 欄位,可指定 #childrealm (預設)。

// 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",
    },
],

由於語法未來可能會有所修訂,我們可以讓 cmcscope 功能加入 core.cml 和整合測試的許可清單。

元件層級事件不會在酬載中攜帶元件身分的相關資訊,例如路徑名稱或網址。一般來說,事件的酬載可能會包含機密資訊,例如元件別名或網址,而我們希望只在必要時才公開這類資訊。由於監控器不需要這項資訊,元件層級事件不會提供產生事件的元件身分資訊。酬載中的其餘資訊是時間戳記和終止狀態,這些資訊並非機密資訊。

監督者

監控器本身很簡單,這是 core 底下的元件,可執行以下操作:

  • 使用含有 StartedStopped 事件清單的靜態 event_stream
  • 如果透過這個 event_stream 接收的 Started 事件有錯誤,或是 Stopped 事件的酬載含有非正常狀態,則會呼叫 fuchsia.hardware.power.statecontrol/Admin.Reboot 觸發正常重新啟動。

這是 critical_components 功能的簡單實作目標。未來,監督程式可能會演進,以支援更多用途,或是可能會有多個監督程式。請參閱「未來工作」。

將事件轉送至主管

元件範圍事件必須從每個重要元件路由至監視器。對於 core 的子項,也就是關鍵元件,這需要兩項變更:

  • 修改 core.cml,將「Started」和「Stopped」事件從元件路由至 supervisor (請參閱「元件層級事件」)
  • 修改主管的 CML,以便使用靜態事件串流中的事件。

如果關鍵元件巢狀在 core 的子領域下,則需要執行另一個步驟:

  • 修改每個中介元件,將子項事件公開至父項。

舉例來說,這很可能是網狀堆疊的情況,因為 netstack 預計會位於 core 底下的 network 子領域。

以下是主管的 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",
            },
        ],
    },
],
...

請注意,您不需要修改要監控的元件。這是有意為之:監管功能是領域管理其元件的方式,而非元件本身。換句話說,決定是否要監控,以及如何監控,並非元件的責任。

啟動監督程式

我們需要確保 supervisor 一律在接收事件時啟動。為達成這項目標,我們建議在 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 中的類似問題。如果我們將設計更通用的解決方案延後到那個時候,屆時或許就能更瞭解問題範圍。

日後的作業

basemgrsessionmgr 會實作各自的當機復原策略,可利用supervisor 替代方案的做法。

fshostarchivist 目前使用 main_process_critical。他們可以改用重新啟動時終止。這樣一來,我們就能將 main_process_critical 限制在重新啟動程序中涉及的元件 (driver_managerpower_manager)。

部分路徑仍會觸發不正常的重新啟動:

  • 這項設計會在 power_manager 上建立 component_manager 的反向依附元件,並間接建立 driver_manager。因此,這些元件無法使用重新啟動時終止功能,因此會標示為 main_process_critical,這表示這兩個元件中的任何一個元件發生當機時,都會觸發不正常的重新啟動。
  • 如果 Reboot 呼叫本身失敗,component_manager 會進入恐慌狀態,並觸發不正常的重新啟動。

在這些情況下,我們可能可以執行更妥善的關機程序;舉例來說,component_manager 可以執行正常的系統關機程序,然後退出。另一方面,由於 power_managerdriver_manager 對系統運作至關重要,因此如果這兩個服務發生當機,我們可能不希望系統繼續運作一段時間。

我們可能會重新檢視電源管理責任的分配方式;舉例來說,component_manager 或許可以自行啟動重新啟動作業 (但仍需要依賴 driver_manager 設定電源狀態)。

系統完全遷移至 Components v2 後,元件管理工具就能利用相依關係圖的知識,支援更智慧的復原策略。

既有技術與參考資料

critical_components 功能和事件 API 修訂版本的私人設計文件。