部署步骤框架

https://storyboard.openstack.org/#!/story/1753128

Ironic 社区希望支持可定制和可扩展的部署步骤,这将提供准备裸机节点(服务器)的能力,使其更好地满足使用这些节点的用户的需求。

为了支持这一点,我们建议将 Ironic 中现有的部署代码重构为一个部署步骤框架,类似于清理步骤框架。

问题描述

目前,Ironic 提供了一种在节点可用之前对其进行准备的方式(参见 状态图)。这是通过 清理 完成的。但是,在不知道节点用户需求的情况下,有时执行这些准备工作是不可能的、低效的或无效的。此外,可能有一些操作只有在知道用户需求后才能完成。

例如,在 清理 期间,可以配置节点以进行 RAID。但是,这可能不是节点用户想要的 RAID 配置。由于用户的需求只有在部署时才知道,因此在部署期间允许自定义 RAID 配置的机制是首选的。

诸如自定义 RAID 配置、BIOS 配置和自定义内核启动参数之类的功能,是可以通过在 Ironic 中部署时定义部署步骤来受益的一些用例。

通过部署步骤提供对此的支持是有意义的。这在概念上类似于 Ironic 已经支持的清理步骤。

提议的变更

本提案是提供基于用户需求执行不同部署操作的支持的第一步。(使用 traits 在部署时重新配置节点 的 RFE 是依赖于这项工作的示例。)

拟议的更改是实现一个部署步骤(或 deploy steps)框架,该框架与现有的自动化和手动 清理 框架非常相似。(这在 OpenStack Dublin PTG 上讨论并原则上达成一致。)

此更改是 Ironic 内部的。用户将无法比今天影响部署过程更多。

从概念上讲,清理步骤模型是一个简单的想法,操作员熟悉它。 具有类似的部署步骤可以提供一致性,并且由于操作员熟悉清理步骤,因此更容易采用。 它也很强大,因为最终(或者一年或两年后),一个步骤可以是清理步骤、部署步骤或两者兼而有之。

这包括重构代码以供清理和部署步骤使用。

现有的部署过程将实现为包含一个(或多个)部署步骤的列表。

什么是部署步骤?

与清理步骤类似,作为部署步骤的函数将用 @deploy_step 装饰,定义在 ironic/drivers/base.py 中,如下所示

def deploy_step(priority, argsinfo=None):
   """Decorator for deployment steps.

   :param priority: an integer priority; used for determining the order in
       which the step is run in the deployment process. (See below,
       "When are deploy steps executed" for more details.)
   :param argsinfo: a dictionary of keyword arguments where key is the name of
       the argument and value is a dictionary as follows:

           ‘description’: <description>. Required. This should include
                          possible values.
           ‘required’: Boolean. Optional; default is False. True if this
                       argument is required.

另一种选择是使用一个装饰器来指定一个函数是清理步骤和/或部署步骤,例如:

@step(clean_priority=0, deploy_priority=0, argsinfo=None)

但是,清理步骤可以中止,而部署步骤则不能(尚未,参见下文),并且尚不清楚是否可能会为部署步骤装饰器添加其他参数。 因此,为部署步骤使用单独的装饰器似乎更安全、更简单。(将一个装饰器用于两种类型的步骤留作未来的练习。)

虽然 Ironic 允许中止清理,但 Ironic 不允许中止部署(尽管有一个 RFE 支持在 deploy_wait 中中止)。 因此,这不在本规范的范围内。

部署步骤可以由任何接口实现,而不仅仅是 DeployInterface。

何时执行部署步骤?

每个部署步骤都有一个优先级;一个非负整数。 在第一阶段,优先级将被硬编码。 将无法关闭或更改这些优先级。

步骤的执行顺序是从最高优先级到最低优先级。 优先级为零 (0) 的步骤将不会执行。 一个步骤必须完成,才能启动下一个步骤。

备选方案

可能还有其他方法来为每个用户/实例提供可定制的部署步骤支持,但似乎没有理由采用与清理步骤不同的设计。

我们可以选择不提供基于每个用户/实例的自定义部署步骤支持。 在这种情况下,一些当前解决此问题的解决方法包括:

  • 预先配置节点组(使用清理步骤)以用于每种所需的配置组合。 这可能会导致奇怪的容量规划问题。

  • 在每个节点部署后执行所需的配置步骤。 由于这些配置步骤是在部署后执行的,因此大多数步骤都需要重新启动节点,需要编排来正确执行这些重新启动,并且这会导致生产环境中不可接受的性能问题。 这种方法不适用于启动盘 RAID 等预部署步骤。

  • 用户可以为每个用例创建自己的镜像。 但是,限制在于镜像的数量可以呈指数级增长,并且无法将特定类型的硬件与特定镜像匹配。

  • 使用可定制的 DeployInterface,例如 ansible 部署接口(尽管 ansible 部署接口不建议用于生产环境)。 这可能无法实现对硬件或设置的相同级别的访问,从而产生相同的影响。

数据模型影响

与清理步骤类似,Node 对象将使用以下内容更新:

  • 一个新的 deploy_step 字段:这是当前正在执行的部署步骤,或者如果没有执行任何步骤,则为 None。 这需要更新数据库。

  • driver_internal_info['deploy_steps']:要执行的部署步骤列表。

  • driver_internal_info['deploy_step_index']:部署步骤列表中的索引(或如果没有执行任何步骤,则为 None);这对应于 node.deploy_step。

状态机影响

不会添加任何新的状态或转换。

节点的状态将在 states.DEPLOYING (deploying) 和 states.DEPLOYWAIT (wait call-back) 之间交替,用于每个异步部署步骤。

REST API 影响

不会有任何新的 API 方法。

GET /v1/nodes/*

返回节点信息的 GET /v1/nodes/* 请求将被修改,以同时返回节点的 deploy_step 字段以及节点 driver_internal_info 字段中的部署相关信息。

clean_step 字段类似,deploy_step 字段将是当前正在执行的部署步骤,或者如果没有正在进行的部署(或尚未开始),则为 None。

如果部署失败,deploy_step 字段将显示导致部署失败的步骤。

此更改需要一个新的 API 版本。 对于尚未使用部署步骤部署的节点,deploy_step 字段将为 None,并且 driver_internal_info 字段中将不会有任何部署相关条目。

对于旧的 API 版本,将不会提供此 deploy_step 字段,尽管 driver_internal_info 字段中的任何部署相关条目都将显示出来。

客户端 (CLI) 影响

唯一的更改(在指定新的 API 版本时)是,Node 的响应将包括新的 deploy_step 字段,并且在部署期间,节点 driver_internal_info 字段中的新的部署步骤相关条目。

“ironic” CLI

即使已弃用,响应也将包含上述更改。

“openstack baremetal” CLI

响应将包含上述更改。

RPC API 影响

无。

驱动程序 API 影响

与清理类似,这些方法将添加到 drivers.base.BaseInterface 类中

def get_deploy_steps(self, task):
    """Get a list of deploy steps this interface can perform on a node.

    :param task: a TaskManager object, useful for interfaces overriding this method
    :returns: a list of deploy step dictionaries
    """

def execute_deploy_step(self, task, step):
    """Execute the deploy step on task.node.

    :param task: a TaskManager object
    :param step: The dictionary representing the step to execute
    :raises DeployStepFailed: if the step fails
    :returns: None if this method has completed synchronously, or
        states.DEPLOYWAIT if the step will continue to execute
        asynchronously.
    """

实际的部署步骤将在编码阶段确定;我们将从一个大的部署步骤开始(以获得框架),然后将该步骤分解为更多步骤——由现有代码和约束(例如,对树外驱动程序的支持、发布 N 中的部署步骤在发布 N+1 中拆分为多个步骤时的向后兼容性)决定。

(一旦确定,本规范将使用实际的部署步骤进行更新。)

树外接口

虽然 conductor 仍然支持旧方式的部署(没有部署步骤),但这种支持将被弃用,并根据 标准弃用策略 移除。(如果供应商强烈要求,可以延长弃用期;我们很灵活。)

对于没有部署步骤的树外接口,conductor 将发出(记录)弃用警告,该树外接口应更新为使用部署步骤,并且所有使用旧方式部署的节点都需要完成部署,然后才能升级到不再支持旧方式的发布版本。

Nova 驱动程序影响

Ramdisk 影响

不应影响 ramdisk(IPA)。

将来,当我们允许配置和指定每个节点上的部署步骤时,我们可能会提供从 ramdisk 收集部署步骤的支持,但这不在本阶段的范围内。

安全影响

其他最终用户影响

无。

可扩展性影响

无。

性能影响

无。

其他部署者影响

无。

开发人员影响

DeployInterfaces(以及参与部署过程的任何其他接口)需要编写时考虑到部署步骤。

实现

负责人

主要负责人
  • rloo (Ruby Loo)

工作项

Ironic
  • 将部署步骤的支持添加到基本驱动程序

  • 将现有代码重构为一个或多个部署步骤

  • 更新 conductor 以获取部署步骤并执行它们

python-ironicclient:
  • 添加对 node.deploy_step 的支持

依赖项

无。

测试

  • 所有新代码和更改行为的单元测试

  • CI 作业已经测试了部署过程;这些更改后它们应该继续工作

升级和向后兼容性

  • 旧接口将与新的 BaseInterface 类一起工作,因为代码将在接口不支持 get_deploy_steps() 时干净地回退。 将记录弃用警告,我们将根据 OpenStack 的弃用和移除策略移除对旧方式的支持。

  • 同样,具有 get_deploy_steps() 的接口实现可以在旧版本的 Ironic 中工作。

  • 在冷升级中

    • 如果 agent 心跳并且 driver_internal_info['deploy_steps'] 为空,则以旧方式进行。

    • 如果部署由使用部署步骤(新代码)的 conductor 启动,这意味着所有 conductor 都使用新代码,因此部署可以在任何支持该节点的 conductor 上继续。

  • 在滚动升级中

    • 如果 agent 心跳并且 driver_internal_info['deploy_steps'] 为空,则以旧方式进行(类似于冷升级)

    • 如果新的 conductor 被固定到旧版本(通过 pin_release_version 配置选项),则不会使用部署步骤机制。 如果部署由使用部署步骤(新代码)的 conductor 启动,这意味着它未被固定,并且所有 conductor 都使用新代码,因此部署可以在任何支持该节点的 conductor 上继续。

文档影响

参考资料