基于OpenStack Ironic实现X86裸机自动化装机实践与优化

Posted by int32bit on March 18, 2019
本文阅读量:

本文转发自民生运维微信公众号,关注该公众号阅读更多云计算相关技术分享, 如需转发到其他公众号,请联系本人开通白名单。

1 X86裸机管理背景

目前我们测试环境存在大量分散在不同网络区域的各种型号的物理服务器,主要用于给需要物理机的开发人员临时测试使用,使用完毕后通过工单申请回收。这些服务器分布在不同的网络区域,大多数服务器的网络布线和接入均已经就绪。

用户在工单系统中申请一台物理服务器时,审批通过后由变更经理派发任务给实施人员,再由实施人员手动通过带外及ISO镜像安装操作系统。手动安装操作系统从ISO镜像准备到装机完成大概需要20多分钟的时间,交付时间长,并且均是重复的体力工作,实施人员工作繁重。

因此我们从2018年下半年开始思考如何优化如上流程,使用自动化工具替代之前的纯手动装机模式。

通过云平台、系统管理和开发部门深入分析,总结X86裸机装机的需求如下:

  1. 支持行内标准镜像,操作系统需要支持配置LVM并创建rootvg。
  2. 操作系统支持配置主机名,支持注入ssh密钥以及初始化root密码。
  3. 操作系统支持多网卡以及bond配置。
  4. 服务器支持分配在不同的网段及VLAN。
  5. 支持HBA卡识别过滤。
  6. 支持装机网卡和业务网卡复用。
  7. 能够支持裸机分布在不同的网段和VLAN,不需要调整服务器原有的网络布线和接入。

目前实现X86自动化装机的工具有很多,如Foreman、cloudboot等,我们调研发现能够同时满足如上需求并且易于与现有的云管集成的只有OpenStack Ironic方案。

于是我们从2018年12月开始对OpenStack Ironic进行完全自主技术预研,之所以没有找厂商方案,是因为厂商的方案大多数需要绑定特定服务器或者SDN交换机。

2 OpenStack Ironic简介

2.1 关于OpenStack Ironic项目

Ironic是OpenStack裸机管理组件,负责裸机资源的分配以及生命周期管理,通过该组件可以为OpenStack云平台提供裸机服务。Ironic通常会协同OpenStack其他服务一起工作,具体分工如下:

  • Keystone:认证与授权。
  • Glance:为裸机提供镜像服务。
  • Nova:负责裸机调度,提供裸机服务。
  • Ironic:裸机管理,包括裸机信息录入、硬件信息自动发现等。
  • Neutron:裸机网络管理和配置。
  • Swift:保存Ironic部署日志以及ConfigDrive数据

以上,比较容易混淆的是Ironic组件和Nova组件的分工,Ironic是裸机管理服务,可以类同为企业的IT资产管理系统,而Nova是提供裸机服务的,可以认为是给用户分配物理服务器的组件。底层技术实现上,Ironic是Nova其中一种Compute Driver接口实现,和Libvirt平行,一个裸机node对应Nova的一个Hypervisor实例。Nova会通过调用Libvirt的Driver实现虚拟机创建、开关机、重启等操作,通过调用PXE以及IPMI实现裸机的装机、开关机、重启等操作。

更多关于Ironic项目的介绍可参考官方文档

2.2 Ironic部署架构

我们的Ironic部署架构如图2-1:

图2-1 ironic部署架构

图2-1 Ironic部署架构

管理节点部署了Ironic相关组件、nova-compute服务(ironic driver)以及TFTP Server,它有两个逻辑网卡(均配置了bond),功能如下:

  • 网卡1(eth0):Trunk接入,部署(provision)网络与业务网络复用网卡,根据VLAN号区分不同的网络。每个网络会启动一个DHCP Server用于裸机的PXE引导。同时也是clean网络、rescue网络复用网卡。

  • 网卡2(eth1):Access接入,OpenStack管理网,用于OpenStack的各个组件通信。该网络三层打通了物理服务器的带外网络访问关系,用于调用IPMI指令。

裸机节点的网卡数量不等,根据业务需要,一般会配置4个网卡,其中一个网卡用于带外,两个网卡用于做bond,为用户的业务网络,另一张网卡做心跳网络(可选),这些网卡均提前做好了布线和接入。

由于服务器的布线和接入均是现成的,因此我们的Ironic没有管理交换机配置,只需要根据VLAN选择不同的Neutron Network即可,因此使用的网络模型是flat。

我们在2019年1月份部署了一个测试环境,其间也遇到了不少的”坑”,如Web console打不开、镜像不支持LVM、SLES操作系统不支持bond配置等问题,我们针对这些问题进行了大量的优化开发工作,本文下一节将详细介绍我们在实践过程中遇到的问题以及解决方案。

3 使用Ironic遇到的那些”坑”

3.1 Web终端bug修复

虚拟机可以通过VNC实现Web终端远程访问,用户可以通过Web页面远程访问操作系统。但显然裸机没有VNC的概念。

不过裸机的Web终端可通过SOL(IPMI Serial Over LAN))实现。我们知道虚拟机的VNC是通过nova-novncproxy代理转发到计算节点libvirt VNC的。裸机的console同理也是通过层层代理转发实现的,首先conductor节点通过socat把IPMI SOL转发到本地的一个端口中,如15900,然后通过nova-serialconsoleproxy负责转发到conductor节点绑定的端口15900,即用户访问裸机的console流程如下:

Web console -> nova-serialconsoleproxy -> ironic-conductor -> IPMI SOL

不过社区代码存在一个bug #1809418,nova-serialproxy校验ironic裸机console地址时,由于类型不一致导致校验100%失败,结果无法通过nova get-serial-console获取web console地址。我们发现这个问题并及时修复这个bug,目前已提交补丁代码到社区并合并到master分支及rocky分支。新版本的Nova不会再出现这个问题了。

3.2 标准化镜像问题

目前社区推荐制作Ironic镜像方式是通过disk-image-builder(DIB)工具,官方文档参考Build the ironic image

然而,通过DIB工具制作的镜像不符合行内的镜像模板要求,比如LVM配置、安全加固等。为了满足行内镜像规范要求,我们想到的最快捷的方法是直接使用行内的标准虚拟机镜像。

但是,物理服务器和虚拟机的镜像是有区别的,其中最明显的区别就是驱动问题。虚拟机只需要安装上virtio驱动即可启动,物理服务器则必须安装兼容的硬件驱动,否则可能由于硬盘不识别导致操作系统启动失败。

目前很多硬件驱动都是通过内核模块的形式动态加载,操作系统启动时通常会先加载一个临时文件系统initramfs,然后由initramfs挂载真正的root文件系统,加载内核模块。因此initramfs能够识别硬盘是操作系统启动成功的前提。因此当一个操作系统镜像从一台虚拟机或者一个物理机迁移到另一个物理机时,往往需要更新initramfs以适配目标物理机的硬件驱动。

我们调研了很多关于生成initramfs的方法,但往往适配了某一种机型后,启在另外一种机型又启动失败了,进入dracut shell界面发现根本识别不了块设备。目前我们的物理服务器机型和型号都非常多,如果针对每一个机型都制作一个镜像,则镜像会非常多,并且每来一个新的机型都需要重新制作镜像。

我们受DIB工具dracut-ramdisk element的启发(感谢社区),研究了其生成initramfs的方法,并结合我们对LVM的需求,实现了能够兼容我们目前使用到的所有机型(如Dell GW730XD、ProLiant DL380 Gen9)initramfs生成方法,部分脚本如下:

KERNEL_VERSION=1.2.3 # 内核版本
# 需要在initramfs安装的工具
BINARY_DEPS="tail head awk ifconfig cut expr route ping nc wget tftp grep" 
DRACUT_DRIVERS="virtio virtio_net virtio_blk" 
dracut -f -N \
     --install "$BINARY_DEPS" \
     --kernel-cmdline "rd.shell rd.driver.pre=ahci" \
     --kver "${KERNEL_VERSION}" \
     --add-drivers "$DRACUT_DRIVERS" \
     --add lvm \
     --mdadmconf \
     --lvmconf \
     -o "dash plymouth" \
     my-ramdisk

其中:

  • -N参数禁用Host-Only模式,这样会尽可能多的安装硬件驱动,而不是仅加载当前宿主机的硬件驱动。
  • rd.driver.pre预先加载ahci模块是为了能够识别系统的SATA硬盘。
  • --add添加的lvm驱动则是为了支持硬盘的LVM卷。

另外我们发现某些机型在开启了drucut-cmdlineresume选项后会随机启动失败,因此我们在grub中取消了resume选项。

3.3 网卡选择策略优化

虚拟机的网卡都是虚拟的tap设备,而裸机的网卡则不一样,它是真实的网卡并且关联了物理接入信息,因此这就存在一个Neutron port与裸机的网卡关联问题,更具体的说,假设创建一个新的裸机实例时,使用VLAN 100的VLAN类型网卡,分配了一个Neutron虚拟port,而物理服务器往往有8个甚至更多的网卡,ironic如何知道是哪个网卡的接入匹配port的配置呢?如果匹配不对,则网络必然不通,因为该网卡可能并没有接入交换机或者配置的VLAN不一致,相当于把IP地址配在了一个错误的网卡上。

当只分配一个网络时,Ironic会选择其中一个开启了PXE的网卡作为候选网卡,但如果存在多个网络时,则可能匹配失败,为什么会出现这种问题呢?我们分析了社区实现的网卡选择算法。代码位于ironic/drivers/modules/network/common.py模块的get_free_port_like_object方法:

def get_free_port_like_object(task, vif_id, physnets):
    # ... 省略部分代码
    def sort_key(port_like_obj):
        is_pg = isinstance(port_like_obj, objects.Portgroup)
        if is_pg:
            pg_physnets = network.get_physnets_by_portgroup_id(
                task, port_like_obj.id)
            pg_physnet = pg_physnets.pop()
            physnet_matches = pg_physnet in physnets
            pxe_enabled = True
        else:
            physnet_matches = port_like_obj.physical_network in physnets
            pxe_enabled = port_like_obj.pxe_enabled
        return (physnet_matches, is_pg, pxe_enabled)

    sorted_free_plos = sorted(free_port_like_objs, key=sort_key, reverse=True)
    return sorted_free_plos[0]

从代码中我们可以总结社区实现的网卡从高到低的优先选择顺序为:

  1. 匹配physical network的ironic port;
  2. 如果是portgroup,则优先选择portgroup;
  3. 选择开启了pxe的ironic port;
  4. 根据Ironic的node的录入顺序选择ironic port。

注意以上的physical network并不是对应neutron的network,而是对应ovs配置的bridge mapping中的physical network。而ironic port其实对应的就是裸机的物理网卡,portgroup即bond配置,在网卡调度时也被当作网卡的逻辑port,和物理port一同调度。

这里假设我们的physical network都是一样的,并且没有配置bond,所有的网卡都开启了PXE,则启动裸机实例时必须满足nova boot--nic参数选择的neutron network和ironic port的录入顺序一致,否则必然发生port映射错位。

我们的场景通常都需要配置多网络,但不能要求用户严格按照裸机网卡的录入顺序选择网络,因此我们对网卡选择策略进行了优化。在裸机port录入时,我们添加了segmentation_id标签(extra key),在物理网卡选择时,如果Neutron的port关联的network的segmentation_id与ironic port的segmentation_id一致,则优先选择该物理网卡,实现的部分代码如下:

def sort_key(port):
    port_seg_id = int(port.extra.get("segmentation_id", ""))
    network_seg_id = int(vif_network.get('provider:segmentation_id'))
    seg_match = port_seg_id == network_seg_id
    # ... 省略其它代码部分
    return (seg_match, physnet_matches, is_pg, pxe_enabled)

这样就可以通过物理网卡的segmentation_id与Neutron的虚拟port强制关联起来一一映射,解决了网卡匹配错位的情况。

3.4 引入新的裸机调度filter

Ironic的node会映射成Nova的一个hypervisor,虚拟机和裸机的调度都是通过nova-scheduler完成的。虚拟机调度时会选择其中一个超售后的剩余资源大于请求资源(flavor)的计算节点(hypervisor)作为虚拟机启动的宿主机,这种策略在调度虚拟机时是完全没有问题的。

然而在调度裸机时会出现一个明显的问题:当用户请求一个低配的裸机实例时,可能会调度到一台高配置的物理机。比如用户申请一台16C32G的裸机实例,可能调度到一台32C128G的物理机上,这显然不是我们所期望的。

因此我们自主研发实现了一个精确资源匹配的filter,该filter会精确匹配hypervisor的资源与用户申请选择的flavor资源,过滤掉资源不符合的hypervisor节点,核心代码如下:

def host_passes(self, host_state, spec_obj):
    # ...省略部分代码
    requested_vcpus = spec_obj.vcpus
    requested_ram = spec_obj.memory_mb
    if requested_vcpus != host_state.vcpus_total:
        return False
    if requested_ram != host_state.total_usable_ram_mb:
        return False
    return True

该filter会首先判断是否裸机调度,如果是则调度时完全匹配用户请求的资源与物理裸机的资源,如果没有匹配的物理机,则直接调度失败,有助于管理员快速地发现物理资源不足问题。

3.5 多网卡Bond支持优化

目前最新版本的cloud-init通过configdrive-2已经支持操作系统的高级网络配置,如bond配置,参考官方文档network config部分部分。但是支持的网络配置工具目前仅包含如下三种:

  • eni: Ubuntu早期版本使用的网络配置方法,通过修改/etc/network/interfaces文件配置网络。
  • sysconfig:rhel系列操作系统配置工具,如CentOS。通过修改/etc/sysconfig/network-scripts/ifdown-ethX文件配置网卡。
  • netplan:一种较新的网络配置工具,通过yaml文件配置网卡,目前ubuntu 18.04使用的该工具。

CentOS、Redhat等操作系统可以通过原生cloud-init配置bond。然而SUSE操作系统由于使用的是wicked网络配置工具,目前cloud-init尚不支持该类型的bond配置。

为了解决这个问题,我们自主研发了wicked网络配置驱动,实现了SLES系列wicked网络工具的高级网络配置功能,从而解决了行内SLES操作系统的bond配置问题。目前准备把这部分功能代码提交到社区。

4 使用Ironic实现自动化装机优势

4.1 自动化装机,缩短交付时间

在使用Ironic组件之前,我们的开发测试环境的物理服务器安装操作系统都是全手工完成的,实施工程师先要通过带外上传ISO镜像,手动启动物理服务器,通过BIOS配置启动方式,然后进行一系列的操作系统初始化配置,如设置语言、创建LVM、主机名等,最后安装grub后重启操作系统,如图4-1:

手动装机流程

图4-1 手动装机流程及预估时间

如上这些步骤几乎都是必须人工交互的重复体力工作,难以做到并行批量装机,一台物理服务器完成操作系统安装大概需要20多分钟的时间,耗费了工程师大量的精力和时间,并且难以实现快速交付。

通过OpenStack Ironic组件,我们开发测试环境的X86物理服务器操作系统安装从原来的纯手工模式转化为全自动模式,用户只需要选择安装的操作系统,无需人工干预即可自动完成操作系统安装。如下是我们测试部分机型实际装机的统计时间:

生产商 机型 装机时间(单位:秒)
长城 GW-R720 377
长城 GW-R730XD 362
惠普 HP-SL2500 484
联想 R525 G3 538

如上主要的时间花在物理服务器启动后的硬件检测时间。由于整个过程都是全自动的,因此可以进行批量装机完成操作系统预安装,相对于手动一台一台安装,大大节省了实施人员的时间。

4.2 标准化操作系统

之前实施工程师手动安装操作系统,不仅需要花费大量的时间,而且很容易配置出错,常见的问题如LVM配置不规范、网卡配置错误、镜像源地址错误等,出现配置错误需要实施人员返工,效率不高。

而通过Ironic自动化装机,使用的是标准化镜像模板,模板已完成操作系统的所有静态配置,如:

  • LVM配置,rootvg大小固定;
  • 镜像源配置;
  • 安全加固配置,如SSH访问权限控制;

而操作系统的动态配置则可通过公有云流行的cloud-init开源工具完成初始化启动配置,如:

  • 主机名配置;
  • 网卡配置,包括bond配置;
  • 密码初始化及SSH密钥注入;

操作系统升级、配置更新等操作,直接修改镜像模板即可,大大减少了工作量,提升了效率。

4.3 多网络区域统一管理

目前我们的X86物理服务器分布在不同的网络区域,不同网络区域的服务器的IP段和VLAN接入不一样。

装机工具为了能够管理多网络区域的物理服务器,通常有两种策略:

  • 部署节点配置多块网卡,手动在交换机上配置与待装机节点相同的网络接入,每个网卡启动DHCP服务。
  • 在物理服务器上添加一块部署网卡用于专门装机时使用,配置与部署节点相同的网络接入。

如上第一种方案非常不灵活,每当有一个新的网络区域加入时,部署节点需要手动再添加一块网卡。而第二种方案需要调整物理服务器原有的网络接入,并且当装机完成后,装机网卡就没用了,白白浪费交换机端口资源。

得益于OpenStack Neutron的多租户网络模型,使用Ironic方案使用Neutron的多租户网络功能,不仅不需要修改X86物理服务器的任何网络接入配置,而且部署节点只需要一个trunk接入网卡即可适配所有VLAN以及IP地址段,一个网络区域相当于一个逻辑VPC(Virtual Private Cloud),如图4-2。

图4-2 多网络区域服务器管理

图4-2 多网络区域服务器管理

物理服务器部署网络和业务网络可共享复用,当添加一个新网络区域服务器时,只需要创建一个新的VPC并指定新的VLAN号即可,无需任何的物理交换机配置修改。

4.4 标准API,便于云管集成

Ironic是OpenStack的标准组件,具有统一的API标准,便于云管平台的集成。

5 总结与展望

Ironic是OpenStack裸机管理组件,我们在开源的基础上修复了Ironic多个社区bug,优化了物理服务器的多网卡选择策略以及物理服务器的调度算法,开发了新的驱动实现SUSE系列操作系统的高级网络配置。通过OpenStack Ironic裸机管理组件实现开发测试环境的X86服务器自动装机和管理,替代原来的纯手动模式,从而缩短了物理服务器的装机时间,减少实施人员的工作量,提升装机效率。

然而,这还仅仅只是开始,未来我们还有许多工作需要做,如:

  • 进一步解决物理服务器的性能及硬件故障监控。
  • 作为物理机管理模块,与云管平台集成。