12-镜像制作

熊 1 阅读 虚拟化

通过KVM安装虚Centos 和Windwos 2008 R2_x86_64操作系统步骤并将磁盘文件作为镜像上传到openstack glance , 作为批量创建虚拟机的镜像文件 , 其中windowsn 2008安装virtio 半虚拟化驱动 , 以实现网络IO和磁盘IO的半虚拟化提升速度 , Centos 7默认即支持半虚拟化 , 不需要安装驱动 , Virtio最初由澳大利亚的一个天才级程序员Rusty Russell编写 , 是一个在hypervisor之上的抽象API接口 , 让客户机知道自己运行在虚拟化环境中 , 从而与hypervisor根据 virtio 标准协作 , 从而在客户机中达到更好的性能(特别是I/O性能) 。目前 , 有不少虚拟机都采用了virtio半虚拟化驱动来提高性能。

1. 环境准备

这里单独用一台服务器来实现镜像制作。这里镜像是通过kvm实现的

  1. 安装kvm

    [root@image ~]# qemu-kvm qemu-kvm-tools libvirt libvirt-client virt-manager virt-install
  2. 开机启动libvirtd

    systemctl enable libvirtd
    systemctl start libvirtd
  3. 单网卡绑定方法:

    eth0的配置:

    # cd /etc/sysconfig/network-scripts/
    # cp  ifcfg-eth0  ifcfg-br0
    # vim ifcfg-eth0
    BOOTPROTO=static
    DEVICE=eth0
    ONBOOT=yes
    BRIDGE=br0 #删除其他的IP/MAC/网关信息 , 增加一行桥接的配置
    NM_CONTROLLED=no #不使用NetworkManager管理服务

    br0的配置:

    # vim ifcfg-br0  #编辑桥接网卡配置
    TYPE=Bridge  #类型为桥接
    BOOTPROTO=static  #静态获取IP
    IPV4_FAILURE_FATAL=no
    NAME=br0  #网卡名称
    DEVICE=br0 #设备名称
    ONBOOT=yes
    IPADDR=192.168.10.128
    NETMASK=255.255.255.0
    GATEWAY=192.168.10.2
    DNS1=192.168.10.2
  4. 双网卡绑定方法:

    第一组配置 , 将eth1和eth5绑定为bond0:

    先创建bond0配置那文件步骤及内容如下:

    [root@linux-host1 ~]# cd /etc/sysconfig/network-scripts/
    [root@linux-host1 network-scripts]# cp ifcfg-eth0   ifcfg-bond0
    [root@linux-host1 network-scripts]# cat ifcfg-bond0 #内容如下:
    BOOTPROTO=static
    NAME=bond0
    DEVICE=bond0
    ONBOOT=yes
    BONDING_MASTER=yes
    BONDING_OPTS="mode=1 miimon=100" #指定绑定类型为1及链路状态监测间隔时间
    BRIDGE=br0 #桥接到br0

    配置br0:

    TYPE=Bridge
    BOOTPROTO=static
    IPV4_FAILURE_FATAL=no
    NAME=br0
    DEVICE=br0
    ONBOOT=yes
    IPADDR=X.X.X.X
    NETMASK=255.255.255.0
    GATEWAY=X.X.X.X

    eth1配置:

    [root@linux-host1 network-scripts]# vim ifcfg-eth1
    BOOTPROTO=static
    NAME=eth1
    DEVICE=eth1
    ONBOOT=yes
    NM_CONTROLLED=no
    MASTER=bond0
    USERCTL=no
    SLAVE=yes

    eth5的配置:

    [root@linux-host1 network-scripts]# cp ifcfg-eth1  ifcfg-eth5
    [root@linux-host1 network-scripts]# vim ifcfg-eth5
    BOOTPROTO=static
    NAME=eth5
    DEVICE=eth5
    ONBOOT=yes
    NM_CONTROLLED=no
    MASTER=bond0
    USERCTL=no
    SLAVE=yes

    重启网络服务:

    [root@linux-host1 network-scripts]``# systemctl restart network

2. 配置centos虚拟机

2.1 镜像准备

  1. 创建qcow2格式的磁盘

     qemu-img create -f qcow2 /var/lib/libvirt/images/CentOS-7.2.qcow2 10G
  2. 创建KVM 虚拟机

    virt-install --virt-type kvm --name CentOS7-7.2 \
       --ram 1024 \
       --cdrom=/usr/local/src/CentOS-7.2-x86_64-Minimal-1511.iso \
       --disk path=/var/lib/libvirt/images/CentOS-7.2.qcow2 \
       --network bridge=br0 \
       --graphics vnc,listen=0.0.0.0 \
       --noautoconsole

2.2 安装必要软件

  1. 启用电源管理工具,为了支持软操作

    yum install -y acpid
    systemctl enable acpid
  2. 安装cloud-init

    一个实例必须与元数据服务进行交互 , 以便在启动时执行几个任务。例如 , 实例必须获取ssh公钥并运行用户数据脚本。

    yum install -y cloud-init && systemctl start cloud-init && systemctl enable cloud-init
  3. 安装QGA , 为了支持OpenStack平台动态修改虚拟机密码功能 , 需要安装qemu-guest-agent:

    yum -y install qemu-guest-agent
  4. 安装 cloud-utils-growpart(注意约束条件)。以支持虚拟机系统盘自动扩容:

     yum install cloud-utils-growpart -y

    约束条件:

    • 根分区文件系统为xfs的镜像不支持自动扩容。

    • 有LVM分区的镜像不支持自动扩容。

    • 推荐使用文件系统为ext3、ext4的单分区镜像。

    • Windows系统不支持。

需要注意如果是centos6配置分区自动拉伸, 需要使用以下git项目

git下载地址 https://github.com/flegmatik/linux-rootfs-resize

[root@image ~]# cd /usr/local/src/
[root@image src]# yum install parted cloud-utils
[root@image src]#unzip /usr/local/src/linux-rootfs-resize-master.zip
[root@image src]# unzip linux-rootfs-resize-master.zip
[root@image src]# cd linux-rootfs-resize-master
[root@image linux-rootfs-resize-master]# ./install 
image-20241001033259731

2.3 调整系统配置

  1. 配置网卡开机自启动

    # vi /etc/sysconfig/network-scripts/ifcfg-ens3
    ...
    ONBOOT=yes
    systemctl restart network.service

    这里需要注意, centos6的网卡名默认就是eth0,如果需要自动修改网卡名称, 可以使用以下脚本来修改rule规则

    #!/bin/bash
    ifconfig eth2 &> /dev/null
    if [ $? -eq 0 ];then
      MAC=`ifconfig  eth2  | grep -F "HWaddr" | awk '{print $5}'`
      echo "# PCI device 0x1af4:0x1000 (virtio-pci)" >  /etc/udev/rules.d/70-persistent-net.rules  
      echo "SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="${MAC}", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"" >>  /etc/udev/rules.d/70-persistent-net.rules
    fi
    
    ifconfig eth3 &> /dev/null
    if [ $? -eq 0 ];then
      MAC=`ifconfig  eth3  | grep -F "HWaddr" | awk '{print $5}'`
      echo "# PCI device 0x1af4:0x1000 (virtio-pci)" >>  /etc/udev/rules.d/70-persistent-net.rules   
       echo "SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="${MAC}", ATTR{type}=="1", KERNEL=="eth*", NAME="eth1"" >>  /etc/udev/rules.d/70-persistent-net.rules
    reboot
    fi

  2. 如果云主机需要支持root ssh远程登录 , 需要开启root远程ssh登录功能 , 修改配置文件/etc/ssh/sshd_config中 的PermitRootLogin值为yes , 重启ssh服务生效:

  3. 关闭关闭防火墙及selinux:

    # systemctl disable firewalld.service
    # vi /etc/sysconfig/selinux
    将 SELINUX=enforcing 改为 SELINUX=disabled
  4. CentOS系列的系统 , 其虚拟机的主机名默认会自动生成xxx.novalocal格式的hostname。若要取消 , 则执行如下 命令进入cloudinit/sources文件夹。编辑文件 , 修改画圈的地方与图中一致:

    #vim /usr/lib/python2.7/site-packages/cloudinit/sources/__init__.py
    image-20241001031732214

    执行如下命令 , 删除init.pyc文件和优化编译后的init.pyo文件:

    #rm -f __init__.pyc
    #rm -f init.pyo
  5. 修改cloud-init的配置文件cloud.cfg 确保可以使用ssh密码登陆:

    vim /etc/cloud/cloud.cfg
    disable_root: 0
    ssh_pwauth: 1
  6. 检查Cloud-init工具相关配置是否成功。执行以下命令 , 无错误发生 , 说明Cloud-init配置成功:

    #cloud-init init --local

    正确安装的Cloud-init会显示Cloud-init的版本详细信息 , 并且无任何错误信息,执行如下命令 , 清理日志信息:

    #rm -rf /var/lib/cloud/*
    #rm -rf /var/log/cloud-init*
  7. 为了虚拟机能够访问metadata服务 , 必须禁止zeroconf服务:

    # echo "NOZEROCONF=yes" >> /etc/sysconfig/network
  8. 配置console , 修改/etc/default/grub文件添加如下内容:

    # vim /etc/default/grub
    GRUB_CMDLINE_LINUX="crashkernel=auto console=tty0 console=ttyS0,115200n8"
  9. 更新grub配置生效:

    # grub2-mkconfig -o /boot/grub2/grub.cfg
  10. 删除一些无用文件 , 清理history命令后执行关机:

    # history -c
    # poweroff

2.4 镜像上传

  1. 在制作镜像的服务器上进行下面操作

    • 清除虚拟机内部的MAC地址信息:

      # virt-sysprep -d centos
    • 使虚拟机脱离libvirt管理:

      # virsh undefine centos
    • 压缩镜像:

      qemu-img convert -c -O qcow2 <源文件> <目标文件>
      
      # qemu-img convert -c -O qcow2 centos.qcow2 Centos.qcow2
  2. 之后在openstack上传镜像即可

    openstack image create "centos" --file /root/centos72.qcow2 --disk-format qcow2 --container-format bare --public

3. cloud-init

cloud-init 是一个软件包 , 可在系统引导期间自动初始化云实例,cloud-init 可从 nova metadata 服务或者 config drive 中获取 metadata。您可以配置 cloud-init 来执行各种任务。可以通过 cloud-init 执行的一些任务示例包括:

  • 设置 default locale

  • 设置 hostname

  • 添加 ssh keys到 .ssh/authorized_keys

  • 设置用户密码

  • 配置网络

  • 安装软件包

3.1 cloud-init 配置

cloud-init 使用 YAML 格式的文件指令执行任务。您可以通过在 YAML 文件中提供说明来确定您希望 cloud-init 执行的初始配置。当实例启动时 , cloud-init 服务将启动并执行指令。根据 cloud-init 配置 , 在第一次引导或后续启动虚拟机时完成任务。

您可以通过配置 /etc/cloud/cloud.cfg 文件并在 /etc/cloud/cloud.cfg.d/ 目录下添加指令来定义任务。

  • cloud.cfg 文件包含指令 , 如用于用户访问和验证以及系统信息的指令。

    文件还包括 cloud-init 的默认和可选模块。模块在三个阶段内按顺序执行 , 这些阶段包括 cloud-init 初始化阶段、配置阶段和最终阶段。在 cloud.cfg 文件中 , 这三个阶段的模块分别列在 cloud_init_modules、cloud_config_modules 和 cloud_final_modules 下。

  • cloud.cfg.d 目录 , 您可以在其中为 cloud-init 添加附加指令。当您向 cloud.cfg.d 目录添加指令时 , 通常会将它们添加到名为 *.cfg 的文件中 , 您需要在每个文顶部包含 #cloud-config。

3.2 cloud-init 以阶段形式运行

2750321-20220517164857394-1038711225

在系统引导期间 , cloud-init 分五个阶段运行。不同阶段会决定是否运行 cloud-init , 找到其数据源的位置 , 以及其他一些任务。这些阶段包括:

  1. cloud-init 生成器(generator)阶段 , 通过 systemd 服务决定是否在引导时运行 cloud-init。

  2. 在本地(local)阶段 , cloud-init 找到本地数据源并应用网络配置。

    作为 cloud-init 执行的第一个阶段 , 此时 instance 还不知道该如何配置网卡 , cloud-init 的任务就是从 config drive 中获取配置信息 , 然后写入 /etc/network/interfaces 文件(如果是 centos 则写入 /etc/sysconfig/network-scripts/ifcfg-xxx)。

    如果没有 config drive , 则将所有网卡配置成 dhcp 模式。这是非常关键的一步 , 只有当网卡正确配置后 , 才能获取到 metadata。

    openstack提供了命令行参数–config-drive 用于配置是否在创建虚拟机时使用 config drive 机制

    nova boot --config-drive=true --image image-name --key-name mykey --flavor 1 --user-data \
    ./my-user-data.txt myinstance --file /etc/network/interfaces=/home/myuser/instance-interfaces

    在/etc/nova/nova.conf中配置,直接使OpenStack计算服务在创建虚拟机时默认使用config drive 机制

    注意

    • 如果镜像没有设置cloud_init,即使在计算节点的 /etc/nova/nova.conf 中配置 force_config_drive = true,也不会调用元数据信息

    • 如果镜像设置了cloud_init,在计算节点的 /etc/nova/nova.conf 中没有配置 force_config_drive = true,依然会调用元数据信息

    而此时因为不知道怎么去调,所以会有一些报错信息,这样会导致开机比较久,而且每次开机都会读取元数据信息

    [DEFAULT]
    force_config_drive=true

  3. 在网络(network)阶段 , cloud-init 处理用户数据并运行 cloud.cfg 文件中的 cloud_init_modules 下列出的模块。您可以在 cloud_init_modules 部分启用、禁用或添加模块。

  4. 在配置(config)阶段 , cloud-init 运行 cloud.cfg 文件中的 cloud_config_modules 下列出的模块。您可以在 cloud_config_modules 部分启用、禁用或添加模块。

  5. 在最后(final)阶段中 , cloud-init 可以运行 cloud.cfg 文件中的 cloud_final_modules 中包含的内容。您可以包括通常在系统引导后运行的软件包安装 , 也可以包括配置管理插件和用户脚本。您可以在 cloud_final_modules 部分启用、禁用或添加模块

cloud-init 安装时会将这 4 个阶段执行的任务以服务的形式注册到系统中 , 比如在 systemd 的环境下 , 我们能够看到这4个阶段分别对应的服务:

  1. local - cloud-init-local.service

  2. init -cloud-init.service

  3. config - cloud-config.service

  4. final - cloud-final.service

f3dd1e16e88d4faf962b3f0a9352dcaa

3.3 cloud-init 模块分阶段执行

当 cloud-init 运行时 , 它会在三个阶段中按顺序执行 cloud.cfg 中的模块:

  • 网络阶段(cloud_init_modules)

  • 配置阶段(cloud_config_modules)

  • 最终阶段(cloud_final_modules)

当首次在虚拟机上运行 cloud-init 时 , 您配置的所有模块都会在相应的阶段中运行。在后续的 cloud-init 运行中 , 某个模块是否在一个阶段中运行取决于单独模块中的模块频率。有些模块在每次运行 cloud-init 时都会运行 , 一些模块只在 cloud-init 第一次运行时运行 , 即使实例 ID 发生了变化。

注意 : 用于唯一标识实例的实例 ID。当实例 ID 发生变化时 , cloud-init 将实例视为新实例。

可能的模块频率值如下:

  • Per instance 意味着该模块在实例的第一次引导时运行。例如 , 如果您克隆一个实例或从保存的镜像创建了一个新实例 , 指定为 per instance 的模块会再次运行。

  • Per once 表示该模块只运行一次。例如 , 如果您克隆实例或从保存的镜像创建新实例 , 指定为 “Per once” 的实例不会在这些实例上再次运行。

  • per always 表示该模块在每次引导时都运行。

3.4 模块

模块(Modules)是cloud-init运行的主体,所有我们需要的用来初始化云服务器的功能都是通过执行模块而实现的。以set_hostname模块为例,该模块的功能是设置主机名,当运行该模块时,它会读取cloud-config中的hostname参数,并将其中值设置为云实例的主机名。

2750321-20220517164945531-584645405

每个模块都有名称、运行频率、配置参数这三大要素,其中运行频率表示一个模块该在什么时候运行,通常有两种运行频率:1. once-per-instance,表示仅在实例首次启动时运行;2. always,表示实例每次启动都运行。

是否运行某个模块、何时运行模块、怎么运行模块,这些都可以在cloud-config中配置,下面截取部分cloud-config,可以看到在这个云实例中:

  • network阶段将运行ssh模块,config阶段将运行mounts、locale等模块,final阶段将运行scripts开头的一些模块;

  • set-passwords模块默认的运行频率是once-per-instance,可将其调整为always,表示每次启动云实例时都会设置密码;

  • ssh_pwauth是set-passwords模块的配置项,ssh_deletekeys、ssh_genkeytypes是ssh模块的配置项,它们描述了该如何运行模块。

# /etc/cloud/cloud.cfg
ssh_pwauth: 1
ssh_deletekeys: 1
ssh_genkeytypes: ['rsa', 'ecdsa', 'ed25519']

cloud_init_modules:
 - ssh

cloud_config_modules:
 - mounts
 - locale
 - [set-passwords,always]

cloud_final_modules:
 - scripts-per-once
 - scripts-per-boot
 - scripts-per-instance
 - scripts-user

所有模块的参考文档:https://cloudinit.readthedocs.io/en/latest/topics/modules.html

3.5 文件配置说明

Cloud-init的配置文件通常为YAML格式 , 位于/etc/cloud/cloud.cfg或/etc/cloud/cloud.cfg.d/*等路径。配置文件包含多个模块的配置 , 如cloud_init_modules、cloud_config_modules、cloud_final_modules等。部分内容如下所示:

设置开放root密码远程登录并开启root用户的ssh权限。配置文件中的disable_root字段为1表示为禁用,为0表示不禁用。设置disable_root值为0,ssh_pwauth为1,lock_passwd设置为false,false表示不锁住用户密码。

users:
  - name: root
    ssh_authorized_keys:
      - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCzU9H5oJxgPpWhtXPwETEpFx425UdirDrTBRt6K/kfx7T6AcnuxzIQ6T64TmGhi72FTz7KtfYUtoIzq7specBthD8X8DTA8C9A6nIEwuHgAh12i0OugmdMmzs8g1QvKxZknFOiX32dlWQBZqU1z9V0NmaMRAxvgvYK6YR5GrHFr5QfSr3rPCMOE1qmZ7QQwfxcEFcmwz1xK65LK1VZh1TPzj7DmW97Clr0F/a11a86ekw8uXsMeowSbdGH546y9FbJlopAPZyprS7kEcwUOaj9VcfF+1LxNjjeHuPsfoquYPElgd52GIC0HE+sW5f+9otbZt2GKZUFBeuehC7g05f root@controller


disable_root: 0
ssh_pwauth:   1 

#datasource_list: [ 'OpenStack' ]
#datasource:
#  OpenStack:
#    metadata_urls: ["http://169.254.169.254"]
#    timeout: 5
#    max_wait: 60

preserve_hostname: flase
manage_etc_hosts: true

runcmd:
 - [ sh, -c, echo "=========Welcome To OpenStack'=========" >> /root/runcmd.log ]

mount_default_fields: [~, ~, 'auto', 'defaults,nofail,x-systemd.requires=cloud-init.service', '0', '2'] 
ssh_deletekeys:   1 
ssh_genkeytypes: ['rsa', 'ecdsa', 'ed25519'] 
syslog_fix_perms: ~ 
disable_vmware_customization: false 

cloud_init_modules: 
 - disk_setup
 - migrator
 - bootcmd
 - write-files
 - growpart
 - resizefs
 - set_hostname
 - update_hostname
 - update_etc_hosts
 - rsyslog
 - users-groups
 - ssh

cloud_config_modules: 
 - mounts
 - locale
 - set-passwords
 - rh_subscription
 - yum-add-repo
 - package-update-upgrade-install
 - timezone
 - puppet
 - chef
 - salt-minion
 - mcollective
 - disable-ec2-metadata
 - runcmd

cloud_final_modules: 
 - rightscale_userdata
 - scripts-per-once
 - scripts-per-boot
 - scripts-per-instance
 - scripts-user
 - ssh-authkey-fingerprints
 - keys-to-console
 - phone-home
 - final-message
 - power-state-change

system_info:
  default_user: 
    name: cloud-user
    lock_passwd: true
    gecos: Cloud User
    groups: [adm, systemd-journal]
    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
    shell: /bin/bash
  distro: rhel 
  paths:
    cloud_dir: /var/lib/cloud 
    templates_dir: /etc/cloud/templates 
  ssh_svcname: sshd 1

# vim:syntax=yaml
chpasswd:
   list: |
     root:XXXX@2019  #设置要修改的密码
   expire: False

目录或文件

描述

/etc/cloud/cloud.cfg

cloud.cfg 文件包含基本的 cloud-init 配置,您可以了解到模块会在哪个阶段运行。

/etc/cloud/cloud.cfg.d

cloud.cfg.d 目录,您可以在其中为 cloud-init 添加附加指令。

/var/lib/cloud

当 cloud-init 运行时,它会在 /var/lib/cloud 下创建一个目录布局。布局包括特定于您的实例配置的目录和文件。

usr/share/doc/cloud-init/examples

examples 目录包含多个示例。您可以使用它们来帮助建模您自己的指令。

/etc/cloud/templates

这个目录包括您可以在特定情况下,在 cloud-init 中启用的模板。模板提供启用的指示。

/var/log/cloud-init.log

cloud-init.log 文件提供了有助于调试的日志信息。

/run/cloud-init

/run/cloud-init 目录包含有关数据源和 ds-identify 脚本的日志信息。

3.5.1 数据源

metadata:cloud-init初始化依赖的数据 , 比如要设置成什么hostname , 网络详细信息如ip、掩码、dns , 密码等是由nova提供的 , 这些数据我们称之为元数据(metadata) 。

datasource:数据来源。元数据存在的载体我们叫做datasource。比如nova可以将这些元数据放到一个iso里 , 以cdrom的形式挂载到虚拟机上 , 我们把这样的iso叫做configdrive , configdrive就是一种datasource;同样的 , nova还支持通过网路地址的方式访问nova api获取到metadata , 这样的方式叫做metadata service , metadata service也是一种datasource。简而言之 , datasource就是包含metadata的一种媒介。

userdata: metadata是openstack根据虚拟机实际情况自动生成的 , 但有时候用户需要一些自定义的数据 , 例如创建虚拟机时 , 自动创建一个XXX用户等操作 , 就需要通过userdata的方式传到虚拟机里。userdata的载体也是datasource。

vendordata:云基座传入的数据(全局数据)

在openstack中,metadata、userdata均在用户创建实例时通过相应参数传入,而vendordata则由nova-api-metadata服务启动时指定。

2750321-20220517164713725-1424929008

这些数据可能来源于许多地方,cloud-init使用数据源一词表示实例数据的来源,目前内置的数据源有:OpenStack、ConfigDrive、Amazon EC2、Azure等等,不同的数据源也表明了不同的实例数据搜索方式。cloud-init在实例内部启动时并不知道从哪里才能够找到实例数据,它会根据预设的一个数据源的列表一个一个查找实例数据,而首个能够找到实例数据的数据源将成为这次启动的数据源。

2750321-20220517164832057-850124610

如果cloud-init指定了OpenStack数据源,那么实例数据将均通过HTTP API请求获取,不同类别的实例数据有着不同的获取方式:

而如果指定了ConfigDrive数据源,那么实例数据将均通过文件获取,cloud-init会搜索标记有CONFIG-2标签的分区,随后将该分区挂载至临时目录中,并读取其中包含实例数据的文件:

  • metadata:file://TMPDIR/openstack/latest/meta_data.json

  • userdata:file://TMPDIR/openstack/latest/user_data

  • vendordata:file://TMPDIR/openstack/latest/vendor_data.json、file://TMPDIR/openstack/latest/network_data.json

3.5.2 cloud-init缓存目录

cloud-init在运行过一次之后 , 会在/var/lib/cloud/生成缓存目录。目录里用来记录datasource和metadata信息、模块的运行记录以及用户的自定义脚本等等。

64084e8f2d464ccf9ca912a0f073e442

3.5.3 cloud-init如何设置网络

cloud-init是通过cloud-init-local.service中配置网络的 , 网络的配置依赖于configdrive , 如果没有configdrive , cloud-init-local.service会生成一个dhcp方式的网卡eth0的配置文件 , 以保证后面的初始化过程可以访问nova metadata service。

当使用configdrive时 , cloud-init会读取network_data.json,并根据mac地址和本地的网卡设备互相匹配 , 然后生成网卡配置文件。注意:cloud-init配置网络只会在每个实例首次开机时配置一次 , 后续不会再配置网络。

如果想禁止cloud-init修改网络配置文件(比如用户作镜像时已经配死了网络 , 不希望别人修改) , 那么可以在/etc/cloud.cfg.d/里新建一个名为"99-disable-network-config.cfg"的配置文件 , 内容如下:

99-disable-network-config.cfg network: {config: disabled}

或者在/etc/cloud/cloud.cfg中配置

network:  
  config: disabled

3.5.4 cloud-init如何进行自动扩盘和文件系统

cloud-init扩展分区是通过"growpart"模块来做的 , 分区扩展后 , 文件系统的扩展是通过"resizefs"模块来做的 , 这两个模块都位于/etc/cloud/cloud.cfg的"cloud_init_modules"部分。

说明:

  1. "growpart"模块依赖于growpart的命令 , 对centos7以上的系统 , 需要安装cloud-utils-growpart包;对centos6而言 , 要安装"linux-rootfs-resize-master.zip"包;对ubuntu16.04而言 , 要安装cloud-utils包,没有这些包 , 无法对系统分区进行扩展。

  2. growpart默认只能扩展"/"分区 , 而且"/"分区必须位于磁盘的最末一个分区。否则也无法扩展磁盘 , 或者需要手动修改/etc/cloud/cloud.cfg文件。

该功能依赖growpart,所以需要在开始前,在镜像中安装包:

yum install –y cloud-utils-growpart

查看需要扩容的磁盘分区

# mount  |grep /dev/vd
/dev/vda1 on / type ext4 (rw,relatime,seclabel,data=ordered)
如上所示:/dev/vda1为我们需要进行扩容的磁盘分区。
cloud-init配置
cloud-init配置开机启动命令:
# cat /etc/cloud/cloud.cfg
….
bootcmd:
  - [ cloud-init-per, once, grow-partition, growpart, /dev/vda, 1]
  - [ cloud-init-per, once, resize-filesystem, xfs_growfs, /dev/vda1 ]
简单来说就是,在系统第一次启动的时候,执行命令:
growpart /dev/vda 1
xfs_growfs /dev/vda1
即可。

lvm类型的系统盘扩容脚本

如果是lvm类型的系统盘,可以通过如下脚本扩容:

# cat extend_root_fs
echo "[] linux-rootfs-resize ..."
lvm vgchange --sysinit -an
#lvm_lv_root=$( echo $(df -h | grep '/$') | sed " ")
lvm_lv_root=$(echo $(df -h | grep '/$') | awk -F ' ' '{print $1}')
lvm_pv_path=$(lvm pvs --noheadings |awk '{print $1}')
lvm_pv_temp=$(echo ${lvm_pv_path}|sed "s/dev//g")
lvm_pv_dev=$(echo ${lvm_pv_temp}| sed "s/[^a-z]//g")
lvm_pv_part=$(echo ${lvm_pv_temp}| sed "s/[^0-9]//g")
growpart -v /dev/${lvm_pv_dev} ${lvm_pv_part}
partprobe -s /dev/${lvm_pv_dev}
lvm pvresize -v ${lvm_pv_path}
lvm vgchange --sysinit -ay
lvm lvextend -v -l +100%FREE ${lvm_lv_root}

3.6 基础用法

我们在使用cloud-init时一般不会特别地去执行cloud-init相关的命令,在云实例的启动过程中操作系统会按正常流程去执行cloud-init,不过当需要开发/调试cloud-init的时候,这些命令会带来很大的帮助。

执行cloud-init的四个阶段:

# local阶段
cloud-init init --local
# network阶段
cloud-init init
# config阶段
cloud-init modules --mode=config
# final阶段
cloud-init modules --mode=final

查询相关:

# 查询cloud-id
cloud-id
# 查询cloud-init执行状态
cloud-init status -l
# 查询metadata
cloud-init query <variable>
cloud-init query -l

其他命令:

# 创建一个包含cloud-config(config.yaml)和shell脚本(script.sh)的userdata
cloud-init devel make-mime -a config.yaml:cloud-config -a script.sh:x-shellscript > userdata

3.7 使用案例

3.7.1 重置密码

cloud-config方式:

##template: jinja
#cloud-config

runcmd:
 # OpenStack数据源在local阶段无法获取数据,network阶段由于是trust模式,直接从缓存读取数据,无法获取更新后metadata,因此需要事先删除缓存。
 - DIR=/var/lib/cloud/scripts/per-boot/ && mkdir -p $DIR && cd $DIR && echo '#!/bin/bash' > 99-clean-cloudinit-cache.sh && echo 'rm -f /var/lib/cloud/instance/obj.pkl' >> 99-clean-cloudinit-cache.sh && chmod +x 99-clean-cloudinit-cache.sh
 # ConfigDrive数据源无法获取更新后metadata,需在首次启动后修改为OpenStack数据源。
 - echo 'datasource_list: [OpenStack]' > /etc/cloud/cloud.cfg.d/90_openstack_datasource.cfg
 # 首次启动后禁止写入网络配置,防止ConfigDrive中的bond配置被覆盖。
 - touch /var/lib/cloud/data/upgraded-network

{% if ds.meta_data.meta.admin_pass is defined %}
chpasswd:
  list: |
    root:{{ds.meta_data.meta.admin_pass}}
  expire: false
  ssh_pwauth: [true]

phone_home:
  url: http://169.254.169.254/openstack/latest/phone_home?unset=admin_pass
  post:
    - instance_id
  tries: 5

cloud_config_modules:
 - [set-passwords,always]
 - [phone_home,always]
 - ssh
 - runcmd
{% endif %}
{% endif %}

获取更新后metadata的方法,除删除缓存之外,也可实现setup方法,在该方法中获取metadata并填充进数据源中(参考azure数据源)。

将set-passwords和phone_home模块设为always的方法,出了在vendor-data中指定外,也可在activate方法中实现,删除执行这两个模块的信号(参考exoscale数据源)。

3.7.2 ssh公钥注入

使用如下userdata注入ssh公钥:

##template: jinja
#cloud-config

ssh_authorized_keys:
  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDIe6oWYfMB4m9TDSmyVlONj2UakYKkAnpTsZnYXWHUd6zLQDvCLkeXmLiBTuJ51Dh6nolL4rZETj1wLJpXqcMvDhhOZyA0Ji3ENbeJj0URWNjDpRUc3eUApDOzpa3gHm8yRwXDZsZbFMpqcL5vQjlZJ9K+SmLCvbfk6sG04uJWLsIFrjSkxlRxdWwHwdvMH9tAo3UeQGnzRxdnF4zxrob+vAV7PPqJ9u4J6GwXWykTtS+cmvoPOBWQHL5gULxp2FVt/xTF5xY2IRHpzfNmJlCd4G7HGow/PVD+jsIN57iNwvxU7dvTzg0PVUXGQImJvQ8pkHD6//ghXcLleUQz3f+7Brlc6yEZuDdEy2NFEnYW8CoEymPSXVZaBykiggP2XSGp6gQa1gtT7yy44EEB5kjX9epi+jAwiCRaLDrgSLBWQmJp8435dtoOALZOXb1Br0uOA0a3G3KZr+7v11f09NZgFPzJTrY2d4ZNtOUILOcQkUAhtW6yOwuCiuxVeKM8cjE= webssh@hikcloud

3.7.3 用户密码过期设置

可以强制 cloud-user 在第一次登录时更改 cloud-user 密码。执行以下步骤使密码过期。

  • 根据数据源的要求,打开您的 user-data 文件进行编辑,或者在 cloud.cfg.d 目录中添加以下指令。

    所有用户指令都包括文件顶部的 #cloud-config,以便 cloud-init 将文件识别为包含用户指令。当您在 cloud.cfg.d 目录中包含指令时,将该文件命名为 *.cfg,且始终在文件的顶部包含 #cloud-config。

  • 将行 chpasswd: {expire: False} 更改为 chpasswd: {expire: True}。

#cloud-config
password: mypassword
chpasswd: {expire: True}
ssh_pwauth: True
ssh_authorized_keys:
  - ssh-rsa AAA...SDvz user1@yourdomain.com
  - ssh-rsa AAB...QTuo user2@yourdomain.com

这可让密码过期,因为除非您另有说明,否则 password 和 chpasswd 只针对默认的用户执行操作。

这是一个全局设置。当您将 chpasswd 设置为 True 时,您创建的所有用户都需要在登录时更改其密码。

3.7.4 更改默认用户名

您可以将默认用户名更改为 cloud-user 以外的名称。

  • 根据数据源的要求,打开您的 user-data 文件进行编辑,或者在 cloud.cfg.d 目录中添加以下指令。

    所有用户指令都包括文件顶部的 #cloud-config,以便 cloud-init 将文件识别为包含用户指令。当您在 cloud.cfg.d 目录中包含指令时,将该文件命名为 *.cfg,且始终在文件的顶部包含 #cloud-config。

  • 添加 user: <username> 行,将 替换为新的默认用户名。

    #cloud-config
    user: username
    password: mypassword
    chpasswd: {expire: False}
    ssh_pwauth: True
    ssh_authorized_keys:
      - ssh-rsa AAA...SDvz user1@yourdomain.com
      - ssh-rsa AAB...QTuo user2@yourdomain.com

3.7.5 设置root密码

要设置根密码,创建一个用户列表。

  1. 根据数据源的要求,打开您的 user-data 文件进行编辑,或者在 cloud.cfg.d 目录中添加以下指令。

    注意 : 所有用户指令都包括文件顶部的 #cloud-config,以便 cloud-init 将文件识别为包含用户指令。当您在 cloud.cfg.d 目录中包含指令时,将该文件命名为 *.cfg,且始终在文件的顶部包含 #cloud-config。

  2. 在文件的 chpasswd 部分中创建用户列表。格式在以下示例中显示。

    注:空格很重要。请勿在您的用户列表的冒号之前或之后包括空格。如果您包含空格,密码会被设置为包括空格。

#cloud-config
ssh_pwauth: True
ssh_authorized_keys:
  - ssh-rsa AAA...SDvz user1@yourdomain.com
  - ssh-rsa AAB...QTuo user2@yourdomain.com
chpasswd:
  list: |
     root:myrootpassword
     cloud-user:mypassword
  expire: False

如果使用这个方法设定用户密码,您必须在这个部分中设置 all passwords。

3.7.6 添加用户和用户选项

您可以在 users 部分中创建和描述用户。您可以修改这个部分,以在初始系统配置中添加更多用户,也可以设置其他用户选项。

如果添加 users 部分,还必须在本节中设置默认用户选项。

  1. 根据数据源的要求,打开您的 user-data 文件进行编辑,或者在 cloud.cfg.d 目录中添加以下指令。

    所有用户指令都包括文件顶部的 #cloud-config,以便 cloud-init 将文件识别为包含用户指令。当您在 cloud.cfg.d 目录中包含指令时,将该文件命名为 *.cfg,且始终在文件的顶部包含 #cloud-config。

  1. 添加或修改 users 部分以添加用户。

    如果您希望 cloud-user 与您指定的其他用户一起创建的默认用户,请确保将 default 添加为这个部分中的第一个条目。如果这不是第一个条目,则不会创建 cloud-user。

    默认情况下,如果没有 selinux-user 值,用户会被标记为 unconfined_u。

#cloud-config
users:
  - default
  - name: user2
    gecos: User N. Ame
    selinux-user: staff_u
    groups: users,wheel
    ssh_pwauth: True
    ssh_authorized_keys:
      - ssh-rsa AA..vz user@domain.com
chpasswd:
  list: |
    root:password
    cloud-user:mypassword
    user2:mypassword2
  expire: False

将用户 user2 放入两个组,即 users 和 wheel。

3.7.7 运行第一个引导命令

您可以使用 runcmd 和 bootcmd 部分在启动和初始化过程中执行命令。

bootcmd 部分在初始化过程早期执行,并且在每次引导时默认运行。runcmd 部分在进程末尾附近执行,且仅在第一次引导和初始化时执行。

  1. 根据数据源的要求,打开您的 user-data 文件进行编辑,或者在 cloud.cfg.d 目录中添加以下指令。

    所有用户指令都包括文件顶部的 #cloud-config,以便 cloud-init 将文件识别为包含用户指令。当您在 cloud.cfg.d 目录中包含指令时,将该文件命名为 *.cfg,且始终在文件的顶部包含 #cloud-config。

  2. 添加 bootcmd 和 runcmd 部分;包含您希望 cloud-init 执行的命令。

#cloud-config
users:
  - default
  - name: user2
    gecos: User N. Ame
    groups: users
chpasswd:
  list: |
    root:password
    fedora:myfedpassword
    user2:mypassword2
  expire: False
bootcmd:
 - echo New MOTD >> /etc/motd
runcmd:
 - echo New MOTD2 >> /etc/motd

3.7.8 添加额外的 sudoers

您可以通过在 users 部分添加 sudo 和 groups 条目,将用户配置为 sudoer。

  1. 根据数据源的要求,打开您的 user-data 文件进行编辑,或者在 cloud.cfg.d 目录中添加以下指令。

    所有用户指令都包括文件顶部的 #cloud-config,以便 cloud-init 将文件识别为包含用户指令。当您在 cloud.cfg.d 目录中包含指令时,将该文件命名为 *.cfg,且始终在文件的顶部包含 #cloud-config。

  2. 添加 sudo 条目并指定用户访问权限。例如,sudo: ALL=(ALL)NOPASSWD:ALL 允许用户不受限制的用户访问。

  3. 添加 groups 条目并指定包含用户的组。

#cloud-config
users:
  - default
  - name: user2
    gecos: User D. Two
    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
    groups: wheel,adm,systemd-journal
    ssh_pwauth: True
    ssh_authorized_keys:
      - ssh-rsa AA...vz user@domain.com
chpasswd:
  list: |
    root:password
    cloud-user:mypassword
    user2:mypassword2
  expire: False

3.7.9 仅配置 root 用户

您可以配置用户数据,以便您有一个 root 用户,而没有其他用户。

  1. 根据数据源的要求,打开您的 user-data 文件进行编辑,或者在 cloud.cfg.d 目录中添加以下指令。

    所有用户指令都包括文件顶部的 #cloud-config,以便 cloud-init 将文件识别为包含用户指令。当您在 cloud.cfg.d 目录中包含指令时,将该文件命名为 *.cfg,且始终在文件的顶部包含 #cloud-config。

  2. 在 users 部分中,为 root 用户创建一个条目。

    以下示例中包含了一个 users 部分,其中仅包含 name 选项。

    users:
      - name: root
    chpasswd:
      list: |
        root:password
      expire: False
  3. 另外,还可为 root 用户设置 SSH 密钥。

    users:
      - name: root
        ssh_pwauth: True
        ssh_authorized_keys:
          - ssh-rsa AA..vz user@domain.com

3.7.10 使用 container-storage-setup 设置存储

您可以通过引用 write_files 模块中的 container-storage-setup 实用程序来设置存储。

  1. 根据数据源的要求,打开您的 user-data 文件进行编辑,或者在 cloud.cfg.d 目录中添加以下指令。

    所有用户指令都包括文件顶部的 #cloud-config,以便 cloud-init 将文件识别为包含用户指令。当您在 cloud.cfg.d 目录中包含指令时,将该文件命名为 *.cfg,且始终在文件的顶部包含 #cloud-config。

  2. 添加或修改 write_files 模块,使其包含 container-storage-setup 实用程序的路径。

    下面的例子将根逻辑卷大小设定为 6GB,而不是默认的 3GB。

    write_files:
      - path: /etc/sysconfig/docker-storage-setup
        permissions: 0644
        owner: root
        content: |
        ROOT_SIZE=6G

在 RHEL 7.4 之前,container-storage-setup 的名称为 docker-storage-setup。如果您使用 OverlayFS 进行存储,从 RHEL 7.4 开始,您现在可以使用 SELinux 处于 enforcing 模式的文件系统。

3.7.11 更改系统区域设置

您可以使用 locale 模块配置系统位置。

  1. 根据数据源的要求,打开您的 meta-data 文件进行编辑,或者在 cloud.cfg 文件或 cloud.cfg.d 目录中添加以下指令。

  2. 添加 locale 指令,指定位置。以下示例将 locale 设置设置为使用 UTF-8 编码的 ja_JP (日本)。

#cloud-config
locale: ja_JP.UTF-8

3.7.12 cloud-init 和 shell 脚本

您可以将列表值或字符串值添加到 bootcmd 或 runcmd。您还可以在 userdata 中提供一个 shell 脚本。

  • 如果您的 bootcmd 或 runcmd 使用一个列表值,则每个列表项都会依次运行,使用 execve。

  • 如果您使用字符串值,则整个字符串将作为 shell 脚本运行。

  • 如果要使用 cloud-init 运行 shell 脚本,您可以提供一个 shell 脚本(使用 shebang(#!)完成),而不是提供带有一个 .yaml 文件的 cloud-init。

#cloud-config

# boot commands
# default: none
# This is very similar to runcmd, but commands run very early
# in the boot process, only slightly after a 'boothook' would run.
# - bootcmd will run on every boot
# - 'cloud-init-per' command can be used to make bootcmd run exactly once
bootcmd:
  - echo 192.168.1.130 us.archive.ubuntu.com >> /etc/hosts
  - [ cloud-init-per, once, mymkfs, mkfs, /dev/vdb ]
#cloud-config

# run commands
# default: none
# runcmd contains a list of either lists or a string
# each item will be executed in order at rc.local like level with
# output to the console
# - runcmd only runs during the first boot
# - if the item is a list, the items will be properly executed as if
#   passed to execve(3) (with the first arg as the command).
# - if the item is a string, it will be simply written to the file and
#   will be interpreted by 'sh'
#
# Note, that the list has to be proper yaml, so you have to quote
# any characters yaml would eat (':' can be problematic)
runcmd:
 - [ ls, -l, / ]
 - [ sh, -xc, "echo $(date) ': hello world!'" ]
 - [ sh, -c, echo "=========hello world=========" ]
 - ls -l /root
 # Note: Don't write files to /tmp from cloud-init use /run/somedir instead.
 # Early boot environments can race systemd-tmpfiles-clean LP: #1707222.
 - mkdir /run/mydir
 - [ wget, "http://slashdot.org", -O, /run/mydir/index.html ]

3.7.13 防止 cloud-init 更新配置文件

从备份镜像创建或恢复实例时,实例 ID 会改变。实例 ID 中的更改可能会导致 cloud-init 更新配置文件。

执行以下步骤以确保 cloud-init 在从备份创建或恢复时不会更新某些配置文件。

  1. 打开 /etc/cloud/cloud.cfg 文件进行编辑。

  2. 注释或删除在恢复实例时不需要 cloud-init 更新的配置。

    例如,为了避免更新 SSH 密钥文件,请从 cloud_init_modules 部分删除 -ssh。

cloud_init_modules:
 - disk_setup
 - migrator
 - bootcmd
 - write-files
 - growpart
 - resizefs
 - set_hostname
 - update_hostname
 - update_etc_hosts
 - rsyslog
 - users-groups
 # - ssh

验证

您可以检查查看 cloud-init 已更新的配置文件。为此,请检查 /var/log/cloud/cloud-init.log 文件。在实例启动期间记录更新的文件,消息以 Writing to 开始。例如:

2019-09-03 00:16:07,XXX - util.py[DEBUG]: Writing to /root/.ssh/authorized_keys - wb: [XXX] 554 bytes
2019-09-03 00:16:08,XXX - util.py[DEBUG]: Writing to /etc/ssh/sshd_config - wb: [XXX] 3905 bytes

3.7.14 在 cloud-init 运行后修改从 KVM 客户机镜像创建的虚拟机

本节提供了在重新运行 cloud-init 前要修改 cloud-init 配置时的步骤示例。当您启动包含安装并启用 cloud-init 软件包的虚拟机时,cloud-init 会在虚拟机的初始引导时以默认状态运行。

  1. 登录到您的虚拟机。

  2. 添加或更改指令,例如修改 /etc/cloud 目录中的 cloud.cfg 文件,或在 /etc/cloud/cloud.cfg.d 目录中添加指令。

  3. 运行 cloud-init clean 命令以清理目录,以便 cloud-init 可以再次运行。您也可以以 root 身份运行以下命令来清理虚拟机:

    `rm -Rf /var/lib/cloud/instances/*`
    `rm -Rf /var/lib/cloud/instance`
    `rm -Rf /var/lib/cloud/data/*`

    您可以将清理的镜像保存为新镜像,并将该镜像用于多个虚拟机。新虚拟机使用更新的 cloud-init 配置运行 cloud-init。

  4. 重新运行 cloud-init 或重新引导虚拟机。

    cloud-init 重新运行,实现您所做的配置更改。


参考连接

云实例初始化工具cloud-init简介 - frankming - 博客园 (cnblogs.com)

Module reference - cloud-init 24.3.1 documentation (cloudinit.readthedocs.io)

cloud-init 中文手册,最全手册,比官网讲得好_cloud-init 文档-CSDN博客