RFC-0110:重新启动以终止关键组件

RFC-0110:重新启动以终止关键组件
状态已接受
领域
  • 组件框架
说明

v2 组件功能,提供与 v1 keyword_components 相当的功能

Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-05-26
审核日期(年-月-日)2021-07-21

摘要

引入“终止时重新启动”的提案组件选项 清单的子声明,其作用等同于 sysmgr 的 critical_components功能。

设计初衷

在组件 v1 中,sysmgr 支持 名为 critical_components 的功能, 系统服务组件会将其自身标记为“关键”组件。这意味着,如果 组件因任何原因(包括正常退出)终止,sysmgr 将会触发 重新启动系统。此次重新启动是一次安全重新启动power_manager 驱动,这会使组件拓扑 有序关停。优雅重新启动以一致的方式破坏系统 并给组件彻底关闭的机会 和文件系统来彻底关闭

如果客户端没有信心,通常会在其组件上设置此选项 如果组件出现故障,正常的系统行为可以继续进行。 不出所料,此选项往往设置在服务运行 在系统运行中发挥核心作用,例如:

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

除了 由 critical_components 实现的相对简单函数。这种设计是 应该专注于解决相应应用场景。崩溃恢复范围超出 critical_components提供的不在范围内(但请参阅 未来的工作)。

要求

主要要求是提供与 critical_components 相当的功能。 这意味着,core 下的组件或 core 的子领域,以选择在其组件时触发安全重新启动 终止。

为何此时推荐?

动机中提到的下列使用 critical_components 被阻止迁移到组件 v2,直到 可用的功能

设计

我们会将 on_terminate 枚举添加到 ChildDecl(等效 添加到组件清单children 部分),并提供 等效于 critical_component 的语义说明。有两个选项:none (默认)或 reboot。如果包含 on_terminate: reboot 的子组件 因任何原因(包括正常退出)终止,component_manager 将 从以下位置调用 Admin/Reboot 方法: fuchsia.hardware.power.statecontrol.Admin 协议公开了 power_manager 触发系统的安全重新启动。

这就需要 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_managerreboot_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。如需了解更多详情,请参阅设计 更多关于这种反转的说明。

原型

如需查看原型,请点击此处

性能

此设计无需考虑性能。component_manager 只会 如果fuchsia.hardware.power.statecontrol.Admin on_terminate: reboot 组件实际终止。

工效学设计

这种设计采用了简单的工效学设计:只需设置 组件的 restart-on-terminate 是执行以下操作:

  • 在父级的 ChildDecl (children) 中设置 on_terminate: reboot (以 CML 格式声明)。
  • 如果不存在,请将父级的 CML 添加到 cmc on_terminate: rebootrestricted_features 许可名单。
  • 将该组件的名称添加到政策许可名单中,以启动终止时重新启动。

由于 on_terminate 选项由父项(而非组件)设置 本身,可以在生产环境中利用应该触发重新启动的组件, 而无需修改 CML此外,这还使您可以 将组件添加到不同的产品配置中,以设置 而不必更改组件。

向后兼容性

此更改不会破坏兼容性。客户必须明确选择启用 “remind-on-terminate”

安全注意事项

假设用户通过将组件标记为 次重新启动,以不当方式触发重新启动。 不过,由于使用受到安全政策许可名单的限制,新用途 必须获得明确批准。请注意,不受信任的 该组件会诱使 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_rust 和客户端 但 cm_rust

替代方案:在 program 上使用 system_critical

我们无需将该选项添加到 ChildDecl 中,而是将其添加到 组件清单program 部分中定义。主要区别在于 那就是在组件本身上设置选项, 与父声明中的子声明相比。

将位放入 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。由于任何关键组件都不是这种情况 此选项并不是可行的替代方案, 完整性。

备选:主管

我们不用在 component_manager 中管理崩溃恢复,只需执行以下操作即可: core领域。此替代方案包括两部分。首先,介绍 "component-scoped"可让使用者监控事件(在 StartedStopped 事件)的范围限定为单个组件 实例。其次,引入一个名为监督器的组件,该组件使用 监控异常终止或启动和重新启动失败 进行响应

组件级范围的事件

组件框架团队已经讨论过一种想法,即: 将事件功能的范围限定为单个组件实例的方式, 而非整个领域此设计提供了一个具体的应用场景, 这个想法监控器只需监控特定组件, 特别是,接收这些组件的事件 整个领域。

为了提高速度,我们提议在 CML 上引入尽可能小的更改 才能启用此功能未来,我们可能会使更多 以不同方式指定事件范围的大量语法修订 (请参阅组件事件 RFC)。我们将添加一个 scope, 字段添加到 offer event 声明中,可以指定 #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
  • 如果通过此事件_stream,它会收到包含Started 错误,或者负载为“不正常”状态的 Stopped 事件触发, 通过调用 fuchsia.hardware.power.statecontrol/Admin.Reboot.

这是 critical_components 功能的简单实现目标。在 监督器可能会不断完善,以支持更多用例, 多位主管 -- 请参阅未来工作

将事件转送给监督者

组件级范围的事件必须从每个关键组件路由到 主管。对于作为 core 的子项的关键组件,这需要 两项更改:

  • 对 core.cml 进行了修改,以便从 组件添加到 Supervisor(请参阅 组件级范围的事件
  • 修改了监管者的 CML,以便在静态图片中使用事件 事件流。

如果关键组件嵌套在 core 的子领域下,则另一个步骤是 要求:

  • 修改每个中间组件,以将子项的事件公开给 其父级。

例如,Netstack 正是如此, 让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 更多的样板文件。每个关键事件对应的事件数 必须从每个关键组件以路由方式发送到 Supervisor。
  • 我们最终需要解决 basemgr/sessionmgr 中的类似问题。 如果我们推迟设计一个更通用的解决方案, 从而更好地了解问题空间。

未来工作

basemgrsessionmgr 会实施自己的崩溃恢复策略, 我们可以采取 supervisor 替代方法。

fshostarchivist目前使用main_process_critical。有可能 而是可以使用“terminate-on-reboot”这样,我们就可以 对重新启动过程中涉及的组件执行 main_process_critical 操作 (driver_managerpower_manager)。

某些路径仍会触发异常重新启动:

  • 此设计创建了 component_manager power_manager 和间接 driver_manager。因此,这些 组件无法使用 terminate-on-reboot,因此会将其标记为 main_process_critical,这意味着如果发生上述任一情况 组件就会触发异常重新启动。
  • 如果 Reboot 调用本身失败,component_manager panic,这也会 会触发非正常重新启动。

我们可以在上述区域 情况;例如,component_manager 可以执行常规的 关闭,然后退出。另一方面,由于 power_managerdriver_manager 对系统运行至关重要,我们可能不希望让 即使发生崩溃,系统也会持续运行任意时长。

我们可能会重新审视电力管理职责 分发;例如,component_manager 或许能够 驱动重新启动本身(仍需要依靠 driver_manager 来设置 电源状态)。

系统完全迁移到组件 v2 后, 组件管理器,以便利用 其对依赖关系图的了解。

先验技术和参考资料

存在适用于 critical_components 功能和 。