如何在KubeVirt中使用 Cloud-init

作者: 王炳明 分类: Kubernetes 发布时间: 2021-06-09 18:35 热度:4,838

Cloud-init 官方支持云平台种类很多常见的公有云如 Aliyun、AWS、Azure,常见的私有云解决方案如 OpenStack、ZStack、OVF 等都有支持。

但是如果我不使用已经支持的私有云,而是自己通过 Libvirt 配合 Ceph 实现了一套虚拟化平台,想要使用 Cloud-init,则可以使用 ConfigDrive 或者 NoCloud 方式。

而这两种方法,都是 kubevirt 支持的数据源,更准确的说是,KubeVirt 目前仅支持这两种数据源。

从效果上来看,这两种方法,并没有区别,都能够在没有网络情况下,通过给虚拟机挂载一个 vfat 或者 iso9660 格式的文件系统到虚拟机里来,给 cloudinit 提供 metadata 和 userdata 达到虚拟机启动任务的定制。

第一种数据源:NoCloud

API 字段参考:cloudinitnocloud

创建出来的虚拟机,会固定生成一个 /dev/vdb 的设备,将它挂载到 /mnt 下,该设备下只有三个文件:

  • meta-data:固定只有 instance-id 、local-hostname 两个字段
  • network-config:关于网络的一些信息
  • user-data:用户自定义所需的信息

如何在KubeVirt中使用 Cloud-init

配置静态ip

networkData 有两种写法

第一种写法

version: 1
config:
   - type: physical
     name: eth0
     mac_address: "fa:16:3e:18:a6:a2"
     subnets:
        - type: static
          address: 192.168.10.10
          netmask: 255.255.255.0
          routes: 
            - network: 0.0.0.0
              netmask: 0.0.0.0
              gateway: 192.168.10.1

第二种写法

当前的cloudinit版本不支持 version2

version: 2
ethernets:
  interface0:
    match:
      mac_address: "fa:16:3e:18:a6:a2"
    set-name: eth0
    addresses:
      - 192.168.10.10/255.255.255.0
    gateway: 192.168.10.1

设置网卡多队列

与 ConfigDrive 一样。

注入密码和密钥

与 ConfigDrive 一样。

第二种数据源:ConfigDrive

API 字段参考:loudinitconfigdrivesource

创建出来的虚拟机会固定多出一个磁盘 vdb,将这个 vdb 挂载到 /mnt ,进入 /mnt/openstack/latest/目录,固定会有如下三个文件

[root@wbm-vm2 latest]# cd /mnt/openstack/latest/
[root@wbm-vm2 latest]# ls -l
total 2
-rw------- 1 root root 100 Jun  4 05:28 meta_data.json
-rw------- 1 root root   4 Jun  4 05:28 network_data.json
-rw------- 1 root root  40 Jun  4 05:28 user_data
  • meta_data.json:代码写死固定只有如下三项内容,不支持自定义
{"instance_id":"wbm-vm.default","hostname":"wbm-vm2","uuid":"0a2027fc-b600-44d5-a6e1-5336b50d352f"}
  • network_data.json:你要传入的网络信息(以下是参考 OpenStack 虚拟机的 network_data.json 的内容)
{"services": [{"type": "dns", "address": "119.29.29.29"}, {"type": "dns", "address": "114.114.114.114"}, {"type": "dns", "address": "223.5.5.5"}], "networks": [{"network_id": "84c285d6-286c-4701-870f-96f99589c9b3", "type": "ipv4", "netmask": "255.255.255.0", "link": "taped271a3f-c4", "routes": [{"netmask": "0.0.0.0", "network": "0.0.0.0", "gateway": "27.148.165.1"}], "ip_address": "27.148.165.25", "id": "network0"}], "links": [{"ethernet_mac_address": "fa:16:3e:18:a6:a2", "mtu": 1500, "type": "ovs", "id": "taped271a3f-c4", "vif_id": "ed271a3f-c436-4e73-bc80-d0fca6e2fc7d"}]}
  • user_data:你可以自定义传入内容,在 yaml 中,不管你有没有需不需要 userdata ,都至少要写入 #cloud-config

配置静态ip

要使用 configdrive,网卡上的 mac 地址,一定得提前指定好,并且和 cloudInitConfigDrive.networkData 里的 ethernet_mac_address 保持一致才可以。

          interfaces:
          - name: default
            bridge: {}
            macAddress: "fa:16:3e:18:a6:a2"

然后在 cloudinit 的 networkData 中写入如下信息

      - name: cloud-init
        cloudInitConfigDrive:
          userData: |
            #cloud-config
          networkData: |
            {"services": [{"type": "dns", "address": "119.29.29.29"}, {"type": "dns", "address": "114.114.114.114"}, {"type": "dns", "address": "223.5.5.5"}], "networks": [{"network_id": "84c285d6-286c-4701-870f-96f99589c9b3", "type": "ipv4", "netmask": "255.255.255.0", "link": "taped271a3f-c4", "routes": [{"netmask": "0.0.0.0", "network": "0.0.0.0", "gateway": "192.168.56.200"}], "ip_address": "192.168.56.223", "id": "network0"}], "links": [{"ethernet_mac_address": "fa:16:3e:18:a6:a2", "mtu": 1500, "type": "ovs", "id": "taped271a3f-c4", "vif_id": "ed271a3f-c436-4e73-bc80-d0fca6e2fc7d"}]}

设置网卡多队列

先 cloudinit 的对应的插件 cc_ws_virtio_net_multiqueue.py

    net_macs = get_mac_list()
    mac_map_eth_get = mac_map_eth(net_macs)

    userdata = yaml.load(cloud.datasource.userdata_raw)
    int_vhost_queues = int(userdata.get('vhost_queues'))

    if int_vhost_queues and mac_map_eth_get:
        for get_eth in mac_map_eth_get:
            os.popen('ethtool -L %s combined %s'%(get_eth,int_vhost_queues))
            LOG.warning('Multi-queue network deploy success %s queues:%s',get_eth,int_vhost_queues)
    else:
        LOG.error("metadata Nic queue number %s Not for the int type",int_vhost_queues)

在 yaml 中的 spec.domain.devices 开启多队列

        devices:
          networkInterfaceMultiqueue: true

然后在 cloudinit 中的 userData 指定队列数:

      - name: cloud-init
        cloudInitConfigDrive:
          userData: |
            #cloud-config
            vhost_queues: 2

注入密码和密钥

先修改 cloudinit 中的对应的插件代码 cc_ws_set_login_way.py

在yaml 中的 userData 里指定 admin_pass ,需要使用密码加密后的字符串,而不是密码。

      - name: cloud-init
        cloudInitConfigDrive:
          userData: |-
            #cloud-config
            ssh_pwauth: True
            admin_pass: "6nWR5NgAU/F/mc$DwJeiQk6Um0vEq26tZ8MYVTMWa39M5FCQzlHJU5fM7lkxfN1Dfj/3Zxfw4DfiwLzUDZAmWLkl.YNdmfqvwQZz."

也可以指定密钥

      - name: cloud-init
        cloudInitConfigDrive:
          userData: |-
            #cloud-config
            ssh_pwauth: True
            keys:
             - data: "172.20.56.201 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN7Vww9EXHoIibiN11nkacySCyCrYQua036LIbF2qCRhnw1YWo2jq5iWJTRK/I5pXaa9ikkfTL9fCerhkRj8b6g="
             - data: "172.20.56.202 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBD02DW3hnVMIDS5A0MTIEEowr5EaxXOJYkh5zCzNLCWwjG7Bi2l3e8ZjS7dRE/ZgiCYggqzLyEE0ceBQlKcjqvI="

第三种数据源:NoCloud-Net

根据 cloudinit – nocloud 的介绍,cloudinit 的 nocloud-net 数据源,支持从 metadata server 上获取 metadata 和 userdata。

但是 future-metadata-server-based-data-sources 中提到,目前 kuevirt 还不支持 nocloud-net 这种数据源,未来有支持的可能。

使用 nocloud-net ,是通过传递给 qemu 命令如下参数来告诉虚拟机从哪个url获取的 metadata 和 userdata。

-smbios type=1,serial=ds=nocloud-net;s=http://10.10.0.1:8000/
  • 如果要获取 metadata ,就请求 http://10.10.0.1:8000/meta-data
  • 如果要获取 userdata,就请求 http://10.10.0.1:8000/user-data
  • 如果要获取 vendor-data,就请求 http://10.10.0.1:8000/vendor-data

数据的三种形式

userdata 和 networkdata 等数据有三种形式:

  1. 指定明文:简单直观,上面为了方便举例,都使用的这种方法
  2. base64 编码的字符串
  3. 使用 K8S 的 Secret

为了方便类比学习,下面使用三种方式展示相同的配置

第一种:明文

      - name: cloud-init
        cloudInitNoCloud:
          userData: |
            #cloud-config
            vhost_queues: 2
            admin_pass: "6nWR5NgAU/F/mc$DwJeiQk6Um0vEq26tZ8MYVTMWa39M5FCQzlHJU5fM7lkxfN1Dfj/3Zxfw4DfiwLzUDZAmWLkl.YNdmfqvwQZz."
          networkData: |
            version: 1
            config:
               - type: physical
                 name: eth0
                 mac_address: "fa:16:3e:18:a6:a2"
                 subnets:
                    - type: static
                      address: 192.168.10.10
                      netmask: 255.255.255.0
                      routes: 
                        - network: 0.0.0.0
                          netmask: 0.0.0.0
                          gateway: 192.168.10.1

第二种:base64

分别将上面的 userdata 和 network data 写入文本文件中

# 将信息写入userdata文件中
cat << END>userdata
#cloud-config
vhost_queues: 2
admin_pass: "6nWR5NgAU/F/mcDwJeiQk6Um0vEq26tZ8MYVTMWa39M5FCQzlHJU5fM7lkxfN1Dfj/3Zxfw4DfiwLzUDZAmWLkl.YNdmfqvwQZz."
END

# 将网络信息写入 network-config 文件中
$ cat << END > network-config
version: 1
config:
   - type: physical
     name: eth0
     mac_address: "fa:16:3e:18:a6:a2"
     subnets:
        - type: static
          address: 192.168.10.10
          netmask: 255.255.255.0
          routes: 
            - network: 0.0.0.0
              netmask: 0.0.0.0
              gateway: 192.168.10.1
END

按照官方文档中的示例, vm 的 yaml 如下

      - name: cloud-init
        cloudInitNoCloud:
          userDataBase64: (cat userdata | base64 -w0)
          networkDataBase64:(cat network-config | base64 -w0)

经验证,在 kubevirt.io/v1alpha3 这个版本下,会报如下错误

The request is invalid: 
* spec.template.spec.volumes[1].cloudInitNoCloud.userDataBase64: spec.template.spec.volumes[1].cloudInitNoCloud.userDataBase64.cloudInitNoCloud.userDataBase64 is not a valid base64 value.
* spec.template.spec.volumes[1].cloudInitNoCloud.networkDataBase64: spec.template.spec.volumes[1].cloudInitNoCloud.networkDataBase64.cloudInitNoCloud.networkDataBase64 is not a valid base64 value.

因此需要先将 base64 算出来

$ cat userdata | base64 -w0
I2Nsb3VkLWNvbmZpZwp2aG9zdF9xdWV1ZXM6IDIKYWRtaW5fcGFzczogIi9GL21jLzNaeGZ3NERmaXdMelVEWkFtV0xrbC5ZTmRtZnF2d1Faei4iCg==
$ cat network-config | base64 -w0
dmVyc2lvbjogMQpjb25maWc6CiAgIC0gdHlwZTogcGh5c2ljYWwKICAgICBuYW1lOiBldGgwCiAgICAgbWFjX2FkZHJlc3M6ICJmYToxNjozZToxODphNjphMiIKICAgICBzdWJuZXRzOgogICAgICAgIC0gdHlwZTogc3RhdGljCiAgICAgICAgICBhZGRyZXNzOiAxOTIuMTY4LjEwLjEwCiAgICAgICAgICBuZXRtYXNrOiAyNTUuMjU1LjI1NS4wCiAgICAgICAgICByb3V0ZXM6IAogICAgICAgICAgICAtIG5ldHdvcms6IDAuMC4wLjAKICAgICAgICAgICAgICBuZXRtYXNrOiAwLjAuMC4wCiAgICAgICAgICAgICAgZ2F0ZXdheTogMTkyLjE2OC4xMC4xCg==

再将算出来的值传入

      - name: cloud-init
        cloudInitNoCloud:
          userDataBase64: "I2Nsb3VkLWNvbmZpZwp2aG9zdF9xdWV1ZXM6IDIKYWRtaW5fcGFzczogIi9GL21jLzNaeGZ3NERmaXdMelVEWkFtV0xrbC5ZTmRtZnF2d1Faei4iCg=="
          networkDataBase64: "dmVyc2lvbjogMQpjb25maWc6CiAgIC0gdHlwZTogcGh5c2ljYWwKICAgICBuYW1lOiBldGgwCiAgICAgbWFjX2FkZHJlc3M6ICJmYToxNjozZToxODphNjphMiIKICAgICBzdWJuZXRzOgogICAgICAgIC0gdHlwZTogc3RhdGljCiAgICAgICAgICBhZGRyZXNzOiAxOTIuMTY4LjEwLjEwCiAgICAgICAgICBuZXRtYXNrOiAyNTUuMjU1LjI1NS4wCiAgICAgICAgICByb3V0ZXM6IAogICAgICAgICAgICAtIG5ldHdvcms6IDAuMC4wLjAKICAgICAgICAgICAgICBuZXRtYXNrOiAwLjAuMC4wCiAgICAgICAgICAgICAgZ2F0ZXdheTogMTkyLjE2OC4xMC4xCg=="

第三种:Secret

先创建第一个 Secret 并将网络信息传进去

# 创建 Kubernetes Secret 对象,并将 network-config 的信息传进去
$ kubectl create secret generic vmi-network-secret --from-file=networkdata=network-config

再创建一个Secret 并将 userdata 的信息传进去

# 创建 Kubernetes Secret 对象,并将 userdata 的信息传进去
$ kubectl create secret generic vmi-userdata-secret --from-file=userdata=userdata

然后在 vm 的 yaml 中指定这两个 secret

      - name: cloud-init
        cloudInitNoCloud:
          secretRef: 
            name: vmi-userdata-secret
          networkDataSecretRef: 
            name: vmi-network-secret

使用哪种数据源比较好?

从使用效果上来看,NoCloud 和 ConfigDrive 没有区别。但官方文档上 NoCloud 的例子更多,更倾向于使用 NoCloud。

参考阅读

  1. NoCloud – cloudinit 21.2
  2. CloudInitNoCloudSource 字段
  3. v1.CloudInitConfigDriveSource
  4. KubeVirt CloudInit 使用说明 – Github
  5. KubeVirt CloudInit 使用说明 – KubeVirt User-Guide

文章有帮助,请作者喝杯咖啡?

发表评论