Anaconda 部署接口

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

问题描述

我们希望 Ironic 提供以下功能(稍后在此规范中详细描述)

  • 创建 LVM 逻辑卷、卷组和物理卷

  • 创建 MD RAID 阵列

  • 创建 Linux 文件系统

  • 基于创建的 Linux 文件系统生成 Linux fstab 文件

在配置具有多个物理存储设备的节点时,支持创建 LVM 和 MD 结构具有重要用途。

用例包括

  • 用户希望使用 LVM 或 MD 功能

  • 用户希望在配置过程中创建的任何块设备上创建 Linux 文件系统

  • 用户希望拥有一个 fstab 文件,其中包含 Ironic 部署接口创建的所有文件系统的条目

虽然 Ironic 支持使用部署模板创建自定义 MD RAID 阵列,但这可能会导致 flavor 膨胀 [1]

提议的变更

本规范建议通过使用 Anaconda 创建新的“anaconda 部署接口”来实现这些块设备配置功能。 “anaconda 部署接口”允许用户选择一组预定义的 Kickstart 配置 [2]

这种方法的主要优势在于允许使用 Kickstart 命令 提供的所有功能自定义部署的操作系统。

本规范关注以下 Anaconda 功能。在大多数情况下,每个描述的功能与 Kickstart 命令之间存在 1:1 的关系

Feature

Ironic

Anaconda

清零 GPT/MBR 结构

在 GPT/MBR 中创建分区

安装引导加载程序 (GRUB)

创建 LVM 逻辑卷

no

创建 LVM 卷组

no

创建 LVM 物理卷

no

创建 MD RAID 阵列

有限

创建用户指定的的文件系统

no

fstab 条目生成器

no

如所示,本规范支持几个与块设备相关的特性。

此外,还有一些期望的伪特性,例如“MD RAID 阵列上的 LVM 物理卷”,这些特性是通过用户提供一系列(命令式)Kickstart 命令来实现的。

使用 Anaconda 部署驱动程序部署节点涉及解决以下问题

  1. 确定与 OS 镜像一起使用的正确 Anaconda 版本。

  2. 将用户提供的 kickstart 文件获取到 Anaconda 运行时。

  3. Operator/User 生成包含 Anaconda 支持的格式中必要工具的 OS 镜像。

  4. 通过在 PXE 引导驱动程序中生成它们,将正确的内核 cmdline 参数传递给 Anaconda。

  5. 在部署开始/结束或崩溃时将状态发送回 Ironic API。

  6. 处理在使用“Anaconda 部署驱动程序”时进行清理。

范围

Anaconda 部署接口的范围限制为

  • CentOS/RHEL >= 7 和 Fedora >=31

  • 将使用 Anaconda 部署 OS 镜像。

  • 支持 UEFI 和 Legacy BIOS 模式。

将 OS 镜像与正确的 anaconda 版本匹配

Anaconda 是一个两阶段安装程序,ramdisk 被认为是 stage1。 一旦 stage1 加载,它就会尝试通过网络下载安装程序的 stage2。 Stage2 是一个 squashfs [3] 镜像,并且可以使用 inst.stage2 内核命令行参数指定 stage2 的位置。

inst.stage2=http://<address>:<port>/httproot/<node-uuid>/squashfs.img

为了使用 anaconda 部署 OS,除了 OS 镜像、内核、ramdisk(stage1) 和 anaconda squashfs(stage2) 镜像之外,还需要所有这些工件都上传到 glance 并与 OS 镜像关联,由 operator 完成。

openstack image create --disk-format raw --container-format compressed \
    --file path/to/os_image.tar.bz2 \
    --property kernel_id=$MY_VMLINUZ_UUID \
    --property ramdisk_id=$MY_INITRD_UUID \
    --property stage2_id=$MY_ANACONDA_SQUASHFS_UUID \
    --property os_distro=RHEL \
    --property os_version=7 centos-7-base \
    --property ks_template=glance://uuid``

这与直接部署接口的工作方式不同,在直接部署接口中,kernel_id 和 ramdisk_id 要么在配置文件中,要么在 driver_info 中设置。 IPA 是 distro agnostic,Anaconda 不是。 没有一个 Anaconda 安装程序版本与所有主要版本的 CentOS 兼容。 CentOS 的每个主要版本都有其自身的 anaconda 安装程序版本。 因此,我们需要 operator 将正确的 PXE 内核、PXE ramdisk 和 Anaconda squashfs 与 OS 镜像关联为 Glance 中的属性。 Anaconda 部署接口应验证镜像属性并确保在部署之前设置了所有必需的属性。

Kickstart 模板

Anaconda 安装程序需要一个 kickstart 文件才能以非交互方式部署操作系统。 kickstart 文件用于自动化操作系统的部署。 如果 operator 未修改,则默认 kickstart 模板应自动使用 autopart 机制 [4] 分区可用的磁盘并部署 OS。 默认 kickstart 文件模板将命名为 default_kickstart.template,并通过 ironic.conf 下 [kickstart] 部分中的配置选项 default_ks_template 引用。

示例默认 kickstart 模板

lang en_US
keyboard us
timezone America/Los_Angeles --isUtc
#platform x86, AMD64, or Intel EM64T
text
install
cmdline
reboot
selinux --enforcing
firewall --enabled
firstboot --disabled
auth --passalgo=sha512 --useshadow


bootloader --location=mbr --append="rhgb quiet crashkernel=auto"
zerombr
clearpart --all --initlabel
autopart

在部署期间,以下强制部分将自动附加到所有 kickstart 模板

# Downloading and installing OS image using liveimg section is mandatory
liveimg --url={{ liveimg_url }}

# Following %pre, %onerror and %trackback sections are mandatory
%pre
/usr/bin/curl -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -d '{.."agent_status": "start"}' http(s)://host:port/v1/heartbeat/{{node_ident}
%end

%onerror
/usr/bin/curl -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -d '{"agent_status": "Error: Deploying using anaconda. Check console for more information."}' http(s)://host:port/v1/heartbeat/{{node_ident}
%end

%traceback
/usr/bin/curl -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -d '{.."agent_status": "Error: Anaconda crashed unexpectedly."}' http(s)://host:port/v1/heartbeat/{{node_ident}
%end

# Sending callback after the installation is mandatory
%post
/usr/bin/curl -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -d '{.."agent_status": "success"}' http(s)://host:port/v1/heartbeat/{{node_ident}
%end

kickstart 文件中可以存在多个 %pre、%post、%traceback 和 %error 部分。 这些部分将按遇到的顺序进行处理和执行 [5]

应将自定义 kickstart 模板上传到 glance 或托管在 conductor 或 conductor 文件系统可访问的 webserver 中。

operator 可以使用 URI 格式 glance://<uuid>http(s)://host:port/path/ks.cfgfile://path/to/ks.cfg 设置 kickstart 文件

如果 API 用户决定将 kickstart 文件存储在 glance 中,他们可以通过运行以下命令来执行此操作

openstack image create --file ks.cfg --container-format bare \
    --disk-format raw custom_kickstart_template

用户可以通过节点实例信息字段,键为“ks_template”来指定特定节点的 kickstart 模板。 例如

openstack baremetal node set $NODE_UUID \
    --instance_info ks_template=glance://uuid

or

openstack baremetal node set $NODE_UUID \
    --instance_info ks_template=http(s)://port:host/path/ks.cfg

or

openstack baremetal node set $NODE_UUID \
    --instance_info ks_template=file://path/to/ks.cfg

用户还可以将 kickstart 模板与 glance 中的 OS 镜像(image_source)关联。 实例信息中指定的模板将优先,然后是 OS 镜像上的 ks_template 属性。 最后,如果 instance_info 和 OS 镜像上都没有 ks_template 属性,则将使用配置文件中指定的默认 kickstart 模板。

自定义 kickstart 模板将被下载并存储在 httproot/<node-uuid>/ks.cfg 中。 其中 httprootironic.conf 配置文件的 [deploy] 部分下的 http_root 配置项中定义。 下载自定义 kickstart 模板后,它将根据 os_distroos_version 进行验证

ksvalidator -v RHEL7 ks.cfg

os_distro 和 os_version 是 image_source(OS 镜像)的属性。 API 用户需要设置 os_distro 和 os_version。 如果 image_source 上没有设置 os_distro 或 os_version,则 kickstart 文件将针对 DEVEL 版本的 kickstart 语法进行验证。 请参阅 ksvalidator -l 以获取支持的 kickstart 版本列表 [6]

OS_DISTRO 应该是 ‘RHEL’ 之一,OS_VERSION 应该是 ‘7’ 或 ‘8’。

kickstart 文件通过内核 cmdline 参数 inst.ks 传递给 anaconda 安装程序

inst.ks=http(s)://<address>:<port>/httproot/<node-uuid>/ks.cfg

内核命令行参数

对于 anaconda 安装程序正常工作,需要两个重要的内核命令行参数。

  1. inst.stage2

  2. inst.ks

这两个内核命令行参数都将附加到 pxe_options 字典。 将添加一个类似于 get_volume_pxe_options() 的函数到 pxe_utils 以促进此操作。

OS 镜像格式

虽然 Anaconda 支持从远程服务器安装单个 RPM 包,但部署驱动程序将仅支持 liveimg [7] 描述的磁盘镜像格式。 liveimg 接受 tarball、squashfs 镜像和任何可挂载的磁盘镜像。 用户可以生成 squashfs 镜像和 tarball。

部署状态

Anaconda 安装程序不知道如何与 Ironic API 通信。 但是,我们可以让 kickstart 文件的 %pre 和 %post 部分进行 API 调用到 Ironic。 ramdisk 将使用 heartbeat API 与 Ironic API 通信。 在 conductor 渲染 kickstart 模板时,%pre %onerror、%traceback 和 %post 部分将填充 curl 调用 heartbeat API。

kickstart 文件的 %pre 和 %post 部分将按 anaconda 遇到的顺序执行。

在安装开始时,将使用 kickstart 文件的 %pre 部分从 anaconda ramdisk 发送以下状态到 lookup

POST {‘callback_url’: ‘’, ‘agent_token’: <token>, ‘agent_version’: ‘’,
‘agent_status’: ‘start’}

http(s)://<address>:<port>/v1/heartbeat/{{node_ident}}

在从 anaconda ramdisk 收到 ‘start’ 状态后,conductor 将设置 driver_internal_info[‘agent_status’] = ‘start’

在 OS 安装结束时,%post 部分将用于将以下消息发送回 Ironic

POST {‘callback_url’: ‘’, ‘agent_token’: <token>, ‘agent_version’: ‘’,
‘agent_status’: ‘success’}

http(s)://<address>:<port>/v1/heartbeat/{{node_ident}}

收到 ‘success’ 后,conductor 将根据当前状态将 Ironic 节点移动到 ‘active’ 状态,并设置 driver_internal_info[‘agent_status’] = ‘success’

如果在安装过程中出现错误,我们将使用 kickstart 文件中的 %onerror [8] 和 %traceback [9] 部分捕获这些错误,然后将 ‘error’ 状态发送到 Ironic

POST {‘callback_url’: ‘’, ‘agent_token’: <token>, ‘agent_version’: ‘’,
‘agent_status’: ‘Error: <msg>’}

http(s)://<address>:<port>/v1/heartbeat/{{node_ident}}

收到 ‘Error’ 状态后,conductor 将根据节点的当前状态将 Ironic 节点的 provision_state 设置为 ‘deploy_failed’ 并设置 Ironic 节点的 last_error 字段。

ramdisk 不会调用 /v1/lookup API。 agent token 将在渲染 kickstart 文件时生成。 agent token 将嵌入到 kickstart 文件中以供 heartbeat curl 调用使用。 驱动程序避免调用 lookup API,因为很难使用 ramdisk 中的脚本读取和提取 agent token。

清理

PXEAnacondaDeploy 驱动程序将继承 AgentBaseMixin 接口和 DeployInterface,类似于 PXERamdiskDeploy 驱动程序。 这意味着清理将由 agent Driver 完成,而不是由 Anaconda 部署驱动程序完成。

在部署期间,PXEAnacondaDeploy 驱动程序将使用与 image_source 关联的属性来确定 deploy_kernel 和 deploy_ramdisk。 但是,在清理期间,它将使用 driver_info[‘deploy_kernel’] 和 driver_info[‘deploy_ramdisk’] 字段来确定清理内核和 ramdisk。 这意味着 driver_info deploy_* 字段应引用 IPA 内核/ramdisk。 这种更改可能会引起混淆。

备选方案

Anaconda 部署驱动程序特定于 Red Hat 基于的发行版。 此部署驱动程序不支持 Anaconda 不支持的发行版。 例如,ubuntu 不受此部署驱动程序的支持。 可以实现一个类似的驱动程序来支持 ubuntu 使用 preseed [10] 文件。

另一种选择是定义一种通用的分区配置格式,并使用该配置代替 kickstart 文件。这种新的通用分区配置将由 conductor 验证,并在部署期间发送到 Ironic python agent。IPA 将读取通用分区配置,并使用 blivet 等库 [11] 来分区/格式化磁盘。使用这种方法,部署驱动程序不再与特定的发行版或供应商绑定。我们探索了这种方法,但发现它过于复杂,并且重新发明了很多发行版安装程序已经做的事情。

在当前方案中,kickstart 文件要么上传到 glance,要么托管在 webserver 中。或者,我们可以向 nodes 表中添加一个名为 kickstart 的新字段,该字段接受原始 kickstart 文件

openstack baremetal rebuild --kickstart path/to/ks.cfg <node-uuid>

数据模型影响

状态机影响

REST API 影响

向 v1/heartbeat API 添加一个可选字段 agent_status,该字段可用于接收来自 anaconda 部署驱动程序的部署状态。

POST {.. ‘agent_status’: <status>} /v1/heartbeat/{{node_ident}}

客户端 (CLI) 影响

“openstack baremetal” CLI

“openstacksdk”

RPC API 影响

需要更新 RPC API 以处理 heartbeat API 中的新 agent_status

驱动程序 API 影响

Nova 驱动程序影响

目前对 nova 的 Ironic 驱动程序没有影响。

Ramdisk 影响

对 Ironic-python-agent 没有影响。

安全影响

驱动程序实现的 heartbeat 方法必须是未认证的,以便 anaconda 能够 POST 到状态 API 而无需 token。由于 API 未认证,攻击者可能会通过发送无效/不正确的状态到 Ironic 节点来造成有针对性的拒绝服务攻击。这个问题可以通过强制的 agent token 验证来缓解。

其他最终用户影响

应将可通过 liveimg kickstart 命令部署的操作系统镜像上传到 glance,以及相关的 anaconda 安装程序的 PXE kernel、ramdisk 和 squashfs 镜像。PXE kernel、ramdisk 和 squashfs 需要与操作系统镜像关联。

openstack image set IMG-ID --property kernel_id=$MY_VMLINUZ_UUID \
    --property ramdisk_id=$MY_INITRD_UUID --property \
    squashfs_id=$MY_ANACONDA_SQUASHFS_UUID

最终用户可以通过与 Operator 合作,在部署期间使用他们自定义的 kickstart 模板。Operator 可以将 instance_info ks_template 键设置为用户提供的 kickstart 模板的路径。kickstart 模板可以是 glance glance://uuid、webserver http(s)://host:port/path/ks.cfg 或文件系统 file://etc/ironic/ks.cfg 的 conductor。

openstack baremetal node set $NODE_UUID --instance_info ks_template=<TMPL>

可扩展性影响

性能影响

其他部署者影响

Operator 必须在 Ironic 配置文件中的 [kickstart] 部分设置默认 kickstart 模板。

[kickstart]
default_ks_template=$Ironic_CONF_DIR/kickstart/default_ks.template

必须在节点上设置 kickstart 部署接口 .. code:: bash

openstack baremetal node set <NODE> –deploy-interface kickstart

开发人员影响

实现

负责人

主要负责人

zer0c00l, sagarun@gmail.com

工作项

  1. 定义默认 kickstart 模板以及与 kickstart 部署模板相关的配置项。

  2. 实现核心部署驱动程序,该驱动程序从 glance 获取工件,生成 PXE 配置文件,将 kickstart 模板渲染到 httproot

  3. 一个 CI 作业来测试 anaconda 部署驱动程序

  4. 操作员和用户的文档

依赖项

测试

  • 此驱动程序应该可以在 gate 中进行测试。可能需要对 gate 进行增强才能使其正常工作。

  • 将为该驱动程序添加 Devstack 支持,以便可以轻松地进行测试。

升级和向后兼容性

文档影响

需要添加关于 kickstart 部署接口以及如何使用它的清晰的操作员和用户文档。

参考资料