OpenStack Ironic 裸金属服务搭建

实验室有二十台 R740 服务器给大家用,由于涉及到内核以及硬件方面的实验,时间一长的话不同服务器上的环境就很混乱,运维管理也很麻烦,并且虚拟化不适合我们的场景。之前 OSDI 22 AE 提供了 https://cloudlab.us 的账号,这个网站上面就是像虚拟机一样使用物理机器,可以自由组网、选择操作系统镜像直接启动、给服务器做镜像等,体验相当不错。但是他们的系统并不开源,所以我想看看有没有办法用开源的软件像他们一样直接管理裸金属服务器。

OpenStack Ironic 就是基于 OpenStack 生态的裸金属计算服务,但是网上资料比较少,虽然官方文档已经很详细了,但是针对到自己特定的需求的话还是有不少坑需要解决。这篇文章记录了搭建 Ironic 的过程,记录一下参数的配置以及如何调整具体服务的思路,也是给 Ironic 生态添加一点资料吧。

前提条件

想要搭建 Ironic,服务器必须支持带外管理 (Out-of-band management),也就是就算不运行操作系统也能对服务器的电源、BIOS 设置等进行调整。一般的商业服务器都提供了这种功能,例如 Dell 的 iDRAC。然后需要有一台服务器作为控制节点长期运行,并且能够连接远程管理接口,通过这个管理接口远程操作其他机器。控制节点由于需要设置网络等,需要有 root 权限。

元数据的话通过 HTTP API 提供,可以保存在其他机器,但是需要确保裸机的控制节点可以访问这个 API 服务器,裸机本身也需要访问。也可以 API 服务器和控制节点都在同一台服务器,按照自己需求确定拓扑。

参考网络拓扑

上图是一种可行的网络拓扑,其中 API 和 DB 服务器和普通的 Web 服务器一样,可以部署在任意的地方,只要裸机控制节点和裸金属服务器可以访问就行了,托管在云上也是 OK 的。裸机控制节点则建议提供 3 个不同的子网,一个用于访问 API 服务器,一个用于提供裸金属集群的业务网,一个用于连接管理接口的管理网。如果没有路由器,裸机控制节点也作为可以 NAT 网关,让裸金属集群访问 API。

当然如果嫌麻烦的话,业务网和其他网络合并,然后控制器和 API 跑在同一台服务器上也是可以的,后面进行 IP 地址分配的时候注意不要产生冲突即可。业务网 IP 是后面通过 DHCP 服务分配的。

服务器配置

因为 OpenStack 服务很多,而且全部是用 Python 写的,外加需要跑一个 SQL 数据库和消息队列,所以 API 服务器尽量使用配置高一点的,否则会爆内存或者 API 超时。在我的小型部署环境里,API 服务器实测常驻内存会到 16G 以上,建议选择 32G 的内存,CPU 可以选择 8 核或者 16 核。控制节点同样需要跑不少服务,以计算为主,内存建议 16G 以上,CPU 8 核以上。

服务运行总体流程

Ironic 由于是直接操作物理服务器,所以整体的流程比虚拟机要复杂一点。这里先介绍一下一台裸金属服务器是怎么启动和关闭的,先对服务之间的互动有个大局上的把握,后面填配置的时候不至于两眼一黑。

部署 Ironic 涉及到的服务有:

  • Keystone:鉴权服务
  • Nova:计算服务,包含 API Server、Scheduler,依赖 Placement
  • Placement:资源分配服务
  • Glance:镜像存储服务,用到的启动镜像、内核、RAMdisk 都存放在这
  • Neutron:网络服务,这个是最复杂的服务之一,除了 API Server,还有不同的 Agent 用来设置好网络
  • Ironic:包含 Neutron Agent 接收网络配置,绑定虚拟接口到服务器上,Conductor 负责接收 Nova 指令,完成服务器管理操作
  • Horizon:Web 管理界面,方便操作

开关机的流程:

  1. 用户通过 API/Web 创建实例,指定裸金属服务器作为实例类型
  2. Scheduler 调用 Placement 服务寻找可用的服务器,指定给 Compute 服务进行启动
  3. Compute 服务标记占用服务器,调用 Ironic Conductor 开始启动过程
  4. Conductor 完成 Neutron 网络等配置,尤其是 DHCP 服务
  5. Conductor 从 Glance 镜像服务下载 Ironic Python Agent (IPA) 镜像,调用服务器管理接口从这个镜像启动
    • 这里的 IPA 镜像相当于一个 PE 预加载环境,就像平时安装操作系统那样启动一个纯内存的操作系统,然后通过这个操作系统完成后续的操作,所有硬件驱动必须打包在这个镜像里
  6. IPA 启动后,回调 Conductor,通知它下载用户镜像,然后 IPA 开始部署用户操作系统镜像
    • 这里的操作系统镜像分为全盘镜像和分区镜像,全盘镜像包含分区表等,分区镜像就是一个普通的虚拟磁盘
    • 实际上就是把镜像 dd 到硬盘里
  7. Conductor 再次调用远程管理接口,将服务器引导到用户的操作系统启动,部署完成
  8. 用户删除实例后,Compute 服务调用 Conductor 进行清理操作
  9. Conductor 调用服务器远程管理接口,重新引导到 IPA,清理磁盘
  10. 清理完成后,调用远程管理接口,关机

以上这些服务还依赖于一些数据库用于持久化和消息队列用于 RPC 通信:

  • MySQL
  • Memcache
  • RabbitMQ

OpenStack 整个都是用 Python 实现的,所以还需要安装 Python 3.6 以上版本。

下面所有操作都在 Yoga 版本验证过,如果没有特别说明的话,就是安装在 API 服务节点。需要注意的是,API 服务如果通过包管理安装,有的是以 systemd 系统服务形式,直接启动 Python 程序提供服务,有的是安装到 Apache 2 作为 WSGI 服务。

Keystone 鉴权服务

这个服务没什么坑,就是根据用户名密码检查权限,下发 Token 的 HTTP API 服务,直接按照官网文档安装就可以了。这个服务是其他所有服务的基础,必须最先安装好。如果有 LDAP 服务的话,建议先不接入,后续再使用其他 OpenID 服务实现统一鉴权。 这个是以系统服务形式安装的。

https://docs.openstack.org/keystone/yoga/install/index.html

Placement 资源分配服务

这个服务也没有什么坑,也直接按照官网教程操作,配置好 Keystone 服务地址和凭据即可。

https://docs.openstack.org/placement/yoga/install/index.html#installation-packages

我在 CentOS 8 上默认是以 WSGI 方式安装的,要配置一下 Apache 2,打开访问权限,否则其他服务无法访问 API,对比以下内容修改你的配置文件就可以了:

<VirtualHost *:8778>
  WSGIProcessGroup placement-api
  WSGIApplicationGroup %{GLOBAL}
  WSGIPassAuthorization On
  WSGIDaemonProcess placement-api processes=3 threads=1 user=placement group=placement
  WSGIScriptAlias / /usr/bin/placement-api
  <IfVersion >= 2.4>
    ErrorLogFormat "%M"
  </IfVersion>
  ErrorLog /var/log/placement/placement-api.log
  #SSLEngine On
  #SSLCertificateFile ...
  #SSLCertificateKeyFile ...
  <Directory /usr/bin>
    Require all denied
    # 添加以下内容
    <Files "placement-api">
      <RequireAll>
        Require all granted
        Require not env blockAccess
      </RequireAll>
    </Files>
    # 添加以上内容
  </Directory>
</VirtualHost>

Glance 镜像存储服务

这个也很简单,按照官网操作,需要注意存储镜像的路径选择一个硬盘空间足够的路径。这个默认也是安装成系统服务。

https://docs.openstack.org/glance/yoga/install/index.html

Horizon Web 界面服务

按照官网操作,并配置好和 Keystone 的 SSO 鉴权通信:

https://docs.openstack.org/horizon/yoga/install/index.html

CentOS 8 的包管理把这个安装成 WSGI,需要改 Apache 2 的配置文件,找到关于 OpenStack Dashboard 的,对比以下内容,添加好访问权限,否则访问会报 403

WSGIDaemonProcess dashboard
WSGIProcessGroup dashboard
WSGISocketPrefix run/wsgi
WSGIApplicationGroup %{GLOBAL}
WSGIScriptAlias /dashboard /usr/share/openstack-dashboard/openstack_dashboard/wsgi.py
Alias /dashboard/static /usr/share/openstack-dashboard/static

<Directory /usr/share/openstack-dashboard/openstack_dashboard>
  Options All
  AllowOverride All
  Require all granted
</Directory>

<Directory /usr/share/openstack-dashboard/static>
  Options All
  AllowOverride All
  Require all granted
</Directory>

Neutron 网络服务

API 服务

这里裸机网络的配置和虚拟机的非常不同,先按照官网把 neutron-server 配置起来,是安装成系统服务的。网络插件按照 Ironic 的教程配,跳过 Configure networking options 和 Configure the metadata agent,不要配 linuxbridge 的部分,然后也不要配 Metadata Agent:

https://docs.openstack.org/neutron/yoga/install/controller-install-rdo.html

Configure networking options 这一节,用下面 Ironic 的步骤代替,配置到 3. Restart the neutron-server service, to load the new configuration. 服务器部分就算配完了了,后面的步骤是配置裸机控制服务的

https://docs.openstack.org/ironic/yoga/install/configure-networking.html

配好 ml2 服务器部分之后,回到 https://docs.openstack.org/neutron/yoga/install/controller-install-rdo.html ,把 Finalize installation 配完

裸机控制服务

需要在裸机的控制节点安装 Open vSwitch,安装之后不要用 Unix 套接字启动 Open vSwitch 的 server,要监听 TCP 的 6640 端口,因为 neutron 是用 TCP 去连接的。还有通过 ovs-vsctl br-add 添加的网桥默认是 down 的,不要急着把物理端口加进去,否则会断网,先用 ip link set dev br-xxx up 把它拉起来,然后设置好 IP,再把物理端口加进去。配置好后,启动 neutron-openvswitch-agent 和 neutron-dhcp-agent,这两个服务都需要用到 sudo 权限,需要提前给运行服务的用户配置好免密 sudo,否则服务会起不来

在裸机控制节点,配置 Ironic 的网络 agent,从 4. Create and edit /etc/neutron/plugins/ml2/ironic_neutron_agent.ini and add the required configuration. 开始配,做到执行 ovs-vsctl show,后面的命令是在 API 节点运行的。注意创建 subnet 的时候,如果你是用域名访问的 API 服务器,需要顺便填一下 DNS,否则后面裸金属启动的时候会因为没有办法通知 OpenStack 而导致整个启动过程卡住。

https://docs.openstack.org/ironic/yoga/install/configure-networking.html

启动 neutron-openvswitch-agent 如果报了 ModuleNotFoundError,需要编辑一下报错的包名下面的 __init__.py,在文件最后 import 一下报错的模块名。

接着配置一下 Metadata Agent,这个服务是用来给 cloud-init 提供初始化元数据的,按照 Configure the metadata agent 和 Configure the Compute service to use the Networking service 做,nova 的部分先跳过不做,后面装好 nova 了再把配置填上就行:

https://docs.openstack.org/neutron/yoga/install/controller-install-rdo.html

最后安装配置 DHCP Agent,看 DHCP agent setup: OVS plug-in 一节,这个是为了 PXE 启动和动态 IP 分配,需要在机器上安装 iproute2(ip 命令) dnsmasq dhcp_release haproxy:

https://docs.openstack.org/neutron/yoga/admin/archives/config-agents.html#dhcp-agent-setup-ovs-plug-in

最后,务必确认防火墙是否放行了 DHCP 的 UDP 端口,至少需要裸金属服务器是可以访问的。

Nova 计算服务

API 服务

官网的教程是安装虚拟机的,Ironic 安装的话需要按照 Ironic 的教程来,控制节点只需要启动 openstack-nova-api、openstack-nova-conductor 和 openstack-nova-scheduler 就行了,openstack-nova-novncproxy 用不到,不需要启动,也不需要配置。

https://docs.openstack.org/nova/yoga/install/controller-install.html

上面安装好 API 服务之后,配置按照下面这个链接填:

https://docs.openstack.org/ironic/yoga/install/configure-compute.html

裸机控制服务

感觉可以装在 API 服务器上,不过我不太确定,还是安装在了裸机控制节点上。

首先参考 https://docs.openstack.org/nova/yoga/install/compute-install.html 安装 compute 服务,但是配置的话按照下面这个链接填:

https://docs.openstack.org/ironic/yoga/install/configure-compute.html

可以偷懒直接用 API 服务器的配置文件。

Ironic 裸金属服务

API 服务

按照官网配置即可。只需要装 openstack-ironic-api,可以装成系统服务,也可以装成 WSGI:

https://docs.openstack.org/ironic/yoga/install/install.html

裸机控制服务

在裸机控制节点装 openstack-ironic-conductor,主要是那一堆 driver 比较麻烦,但是官网有给了示例配置,直接复制粘贴没有什么大问题。

https://docs.openstack.org/ironic/yoga/install/setup-drivers.html

装好之后,用下面的命令检查 conductor 是不是注册成功了,务必保证两台服务器时间同步

baremetal driver list

如果是空的,说明没注册成功,检查一下 API 服务器的防火墙是不是拦截了消息队列或者 API 服务的端口,也有可能是两台机器时间不同步。

配置 PXE 服务器

也是按照官网操作就行,可以直接下载需要的 deb、rpm 包等把引导文件解压出来,放到指定目录就可以了。

https://docs.openstack.org/ironic/yoga/install/configure-pxe.html

需要注意防火墙是否放行 TFTP 的 UDP 端口和 HTTP 服务器的 TCP 端口。

注册裸机节点

创建镜像

按照官网文档操作就行。

https://docs.openstack.org/ironic/yoga/install/configure-glance-images.html

一些参考命令:

通用软件包

export COMMON_ELEMENTS="cloud-init cloud-init-datasources mellanox growroot devuser dynamic-login baremetal dhcp-all-interfaces grub2"

# 下面这个不设置的话,会导致 cloud-init 不启动,密钥之类的注入不了
export DIB_CLOUD_INIT_DATASOURCES="Ec2, ConfigDrive, OpenStack"

CentOS 8 Stream

export DIB_RELEASE=8-stream
export DIB_DISTRIBUTION_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/centos/ 
export DIB_DEV_USER_USERNAME=username
export DIB_DEV_USER_PWDLESS_SUDO=true
export DIB_DEV_USER_PASSWORD="password"
disk-image-create centos $COMMON_ELEMENTS --ramdisk dracut-ramdisk -o centos-8-stream

Debian 11

export DIB_RELEASE=bullseye 
export DIB_DISTRIBUTION_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/debian/
export DIB_DEV_USER_USERNAME=username
export DIB_DEV_USER_PWDLESS_SUDO=true
export DIB_DEV_USER_PASSWORD="password"
disk-image-create debian $COMMON_ELEMENTS -o debian-11

Fedora 36

export DIB_RELEASE=36
export DIB_DEV_USER_USERNAME=username
export DIB_DEV_USER_PWDLESS_SUDO=true
export DIB_DEV_USER_PASSWORD="password"
export DIB_DISTRIBUTION_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/fedora/
disk-image-create fedora $COMMON_ELEMENTS -o fedora-36

从虚拟机镜像转换

先像平时一样把虚拟机安装好,然后装好硬件需要的驱动,打开所有网络接口的 DHCP 功能,安装 cloud-init 包和其他需要的软件,把 vmlinuz 和 initrd 拷贝出来,导出虚拟磁盘之后用 qemu-img 转换 qcow2

qemu-img convert -O qcow2 VMware-machine-disk1.vmdk VMware-machine-disk1.qcow2

上传镜像

for OS in centos-8-stream fedora-36 debian-11; do
    openstack image create ${OS}-kernel --public --disk-format aki --container-format aki --file ${OS}.vmlinuz 
    openstack image create ${OS}-initrd --public --disk-format ari --container-format ari --file ${OS}.initrd
    openstack image create ${OS} --public --disk-format qcow2 --container-format bare --property kernel_id=$(openstack image show -c id -f value ${OS}-kernel) --property ramdisk_id=$(openstack image show -c id -f value ${OS}-initrd) --file ${OS}.qcow2
done

注册裸机

如果是第一台机器,需要先创建 Flavor (也就是“机型”):

export RAM_MB=191736
export VCPU=40
export DISK_GB=500
# 上面的信息不重要,不影响调度
export FLAVOR_NAME=baremetal
export RESOURCE_NAME=CUSTOM_$(echo $FLAVOR_NAME | tr '.:-' '___' | tr '[:lower:]' '[:upper:]')
openstack flavor create --ram $RAM_MB --vcpus $VCPU --disk $DISK_GB $FLAVOR_NAME

# 必须设置下面全部选项,否则无法调度
openstack flavor set --property resources:$RESOURCE_NAME=1 $FLAVOR_NAME
openstack flavor set --property resources:VCPU=0 $FLAVOR_NAME
openstack flavor set --property resources:MEMORY_MB=0 $FLAVOR_NAME
openstack flavor set --property resources:DISK_GB=0 $FLAVOR_NAME

配置好之后,使用 OpenStack 命令行创建一个裸金属服务器,注意 --resource-class 要和上面 flavor 对应:

export SERVER_NAME=bm-server
export DRIVER=idrac
baremetal node create --driver $SERVER_NAME --name $SERVER_NAME --resource-class $FLAVOR_NAME

这时候可以对名称之类的信息修改,然后,指定远程管理 driver 所需要的信息:

export IPA_KERNEL_UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export IPA_INITRD_UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export DRAC_ADDRESS=1.2.3.4
export DRAC_USERNAME=root
export DRAC_PASSWORD=123456
export REDFISH_ADDRESS=https://$DRAC_ADDRESS/redfish/v1
export REDFISH_USERNAME=root
export REDFISH_PASSWORD=123456
baremetal node set $SERVER_NAME \
  --driver-info deploy_kernel=$IPA_KERNEL_UUID \
  --driver-info deploy_ramdisk=$IPA_INITRD_UUID \
  --driver-info agent_verify_ca=False \
  --driver-info drac_address=$DRAC_ADDRESS \
  --driver-info drac_username=$DRAC_USERNAME \
  --driver-info drac_password=$DRAC_PASSWORD \
  --driver-info redfish_address=$REDFISH_ADDRESS \
  --driver-info redfish_verify_ca=False \
  --driver-info redfish_username=$REDFISH_USERNAME \
  --driver-info redfish_password=$REDFISH_PASSWORD

设置之后,使用命令将节点转移到管理状态:

baremetal node manage $SERVER_NAME

这时候,你可以手动用 baremetal port create 命令创建网卡 MAC 和节点 UUID 对应的关系,也可以用 baremetal node inspect 命令让 Ironic 自动调取远程管理接口来填充信息。

之后,运行 baremetal node validate $SERVER_NAME 验证节点配置,没有问题之后,运行 baremetal node provide $SERVER_NAME ,节点就准备完成了。

添加完之后,一定延迟之后节点就可以被调度了,如果着急的话,可以用 nova-manage cell_v2 discover_hosts 扫描一次节点。

可以用下面的命令检查能不能被调度到,列表不为空就是可以:

openstack allocation candidate list --resource $RESOURCE_NAME='1'

如果是空的话,检查一下 baremetal node show $SERVER_NAME -c provision_state 有没有问题。

部署裸机系统

Web

直接点 Create Instance,按照向导填好资料就可以了。

命令行

看文档: https://docs.openstack.org/ironic/yoga/user/deploy.html

运维命令

BIOS 设置

看文档,因机器而异:https://docs.openstack.org/ironic/yoga/admin/bios.html

baremetal node bios setting list $SERVER_NAME

下架指令

baremetal node retire

https://docs.openstack.org/ironic/yoga/admin/retirement.html

然后 baremetal node delete

救急命令

有时候节点进入了错误的状态的话,就需要人工干预修改状态,具体参考 ironic 状态机文档:

https://docs.openstack.org/ironic/yoga/user/states.html#state-machine-diagram

常用 iDRAC RACADM 命令

查询网卡 MAC 和 PCI-E 地址

racadm get nic.VndrConfigPage.1

1 可以换成其他数字。

查询网卡配置

racadm get nic.niCconfig.1

主要看 LegacyBootProto 是不是 PXE ,不是的话用 set nic.niCconfig.1.LegacyBootProto PXE 设置,不会马上生效,需要用 jobqueue create NIC.Integrated.1-1-1 -r pwrcycle -s TIME_NOW 命令创建配置任务,注意这个命令会重启 iDRAC,有失联风险