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 部署驱动程序部署节点涉及解决以下问题
确定与 OS 镜像一起使用的正确 Anaconda 版本。
将用户提供的 kickstart 文件获取到 Anaconda 运行时。
Operator/User 生成包含 Anaconda 支持的格式中必要工具的 OS 镜像。
通过在 PXE 引导驱动程序中生成它们,将正确的内核 cmdline 参数传递给 Anaconda。
在部署开始/结束或崩溃时将状态发送回 Ironic API。
处理在使用“Anaconda 部署驱动程序”时进行清理。
范围¶
Anaconda 部署接口的范围限制为
CentOS/RHEL >= 7 和 Fedora >=31
将使用 Anaconda 部署 OS 镜像。
支持 UEFI 和 Legacy BIOS 模式。
将 OS 镜像与正确的 anaconda 版本匹配¶
Anaconda 是一个两阶段安装程序,ramdisk 被认为是阶段 1。 一旦加载了阶段 1,它就会尝试通过网络下载安装程序的阶段 2。 阶段 2 是一个 squashfs [3] 镜像,并且可以使用 inst.stage2 内核命令行参数指定阶段 2 的位置。
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 文件系统上的 web 服务器中。
operator 可以使用 URI 格式 glance://<uuid> 或 http(s)://host:port/path/ks.cfg 或 file://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 中。 其中 httproot 在 ironic.conf 配置文件的 [deploy] 部分下的 http_root 配置项中定义。 下载自定义 kickstart 模板后,它将根据 os_distro 和 os_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 安装程序正常工作,需要两个重要的内核命令行参数。
inst.stage2
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 通信。 %pre %onerror、%traceback 和 %post 部分将使用 curl 调用 heartbeat API 填充,在 conductor 渲染 kickstart 模板时。
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’
如果在安装过程中出现错误,我们将使用 %onerror [8] 和 %traceback [9] kickstart 文件中的部分捕获这些错误,然后将 ‘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,要么托管在 web 服务器中。 或者,我们可以将名为 kickstart 的新字段添加到 nodes 表中,该字段接受原始 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 可以在没有 token 的情况下 POST 到状态 API。 攻击者可能会通过向 Ironic 节点发送无效/不正确的状态来造成有针对性的拒绝服务攻击,因为 API 未经身份验证。 此问题通过强制 agent token 验证来缓解。
其他最终用户影响¶
一个可以使用 liveimg kickstart 命令部署的 OS 镜像应上传到 glance,以及相关的 anaconda 安装程序的 PXE 内核、ramdisk 和 squashfs 镜像。 PXE 内核、ramdisk 和 squashfs 需要与 OS 镜像关联。
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、web 服务器 http(s)://host:port/path/ks.cfg 或 conductor 文件系统上的 file://etc/ironic/ks.cfg。
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
工作项¶
定义默认 kickstart 模板和与 kickstart 部署模板相关的配置项。
实现核心部署驱动程序,该驱动程序从 glance 获取工件,生成 PXE 配置文件,将 kickstart 模板渲染到 httproot
一个 CI 任务,用于测试 anaconda 部署驱动
操作员和用户文档
依赖项¶
无
测试¶
该驱动应该可以在 gate 中进行测试。可能需要对 gate 进行增强才能使其正常工作。
将为该驱动添加 Devstack 支持,以便可以轻松进行测试。
升级和向后兼容性¶
无
文档影响¶
需要添加清晰的操作员和用户文档,说明 kickstart 部署接口以及如何使用它。