为在 Azure 中创建映像准备 Linux

适用于: ✔️ Linux 虚拟机 ✔️ 灵活规模集

Azure 平台服务级别协议 (SLA) 仅适用于运行 Linux 操作系统的虚拟机 (VM),前提是使用某个认可的分发版。 对于认可的分发版,Microsoft Azure 市场提供预配置的 Linux 映像。 有关详细信息,请参见:

在 Azure 上运行的其他所有分发版(包括社区支持的分发版和非认可的分发版)都有一些先决条件。

本文重点介绍有关在 Azure 上运行 Linux 分发版的一般准则。 本文无法详尽无遗,因为每个分发版不同。 即使满足本文介绍的所有条件,也可能需要显著调整 Linux 系统,使其正常运行。

常规 Linux 安装说明

  • Azure 不支持 Hyper-V 虚拟硬盘 (VHDX) 格式。 Azure 仅支持固定 VHD。 可以使用 Hyper-V 管理器或 Convert-VHD cmdlet 将磁盘转换为 VHD 格式。 如果使用 VirtualBox,请在创建磁盘时选择“固定大小”,而不要选择默认(动态分配)大小。

  • Azure 支持 Gen1(BIOS 引导)和 Gen2(UEFI 引导)虚拟机。

  • 必须在内核中启用虚拟文件分配表 (VFAT) 内核模块。

  • VHD 允许的最大大小为 1,023 GB。

  • 在安装 Linux 系统时,建议使用标准分区而不是逻辑卷管理器 (LVM)。 对于许多安装,LVM 是默认值。

    使用标准分区可避免 LVM 名称与克隆的 VM 发生冲突,特别是在 OS 磁盘曾经连接到另一台相同的 VM 进行故障排除的情况下。 可以在数据磁盘上使用 LVMRAID

  • 需要对装载用户定义的函数 (UDF) 文件系统的内核支持。 在 Azure 上首次启动时,预配配置会通过附加到客户机的采用 UDF 格式的介质传递给 Linux 虚拟机。 Azure Linux 代理必须装载 UDF 文件系统才能读取其配置和预配 VM。

  • 低于 2.6.37 的 Linux 内核版本不支持具有更大 VM 大小的 Hyper-V 上的非一致性内存访问 (NUMA)。 此问题主要影响使用上游 Red Hat 2.6.32 内核的较旧分发版。 它已在 Red Hat Enterprise Linux (RHEL) 6.6 (kernel-2.6.32-504) 中修复。

    运行自定义内核版本低于 2.6.37,或基于 RHEL 的内核版本低于 2.6.32-504 的系统,必须在 numa=off 中的内核命令行上设置启动参数 。 有关详细信息,请参阅 Red Hat KB 436883

  • 不要在 OS 磁盘上配置交换分区。 可以将 Linux 代理配置为在临时资源磁盘上创建交换文件,如本文稍后所述。

  • Azure 上的所有 VHD 的虚拟大小都必须按 1 MB(1024 x 1024 字节)对齐。 从原始磁盘转换为 VHD 时,请确保在转换前的原始磁盘大小是 1 MB 的倍数,如本文稍后所述。

  • 使用最新的分发版本、包和软件。

  • 删除用户和系统帐户、公钥、敏感数据、不必要的软件和应用程序。

注释

Cloud-init 版本 21.2 或更高版本会删除 UDF 要求。 但如果未启用 udf 模块,CD-ROM 将不会在预配期间装载,这会阻止应用自定义数据。 解决方法是应用用户数据。 但是,与自定义数据不同,用户数据不会加密。 有关详细信息,请参阅 cloud-init 文档中的用户数据格式

在没有 Hyper-V 的情况下安装内核模块

Azure 在 Hyper-V 虚拟机监控程序上运行,因此 Linux 需要某些内核模块才能在 Azure 中运行。 如果具有在 Hyper-V 外部创建的虚拟机,Linux 安装程序可能无法在初始 RAM(initrd 或 initramfs)中包含 Hyper-V 的驱动程序,除非 VM 检测到它正在 Hyper-V 环境中运行。

使用不同虚拟化系统(如 VirtualBox 或 KVM)来准备 Linux 映像时,可能需要重新生成 initrd,以确保至少 hv_vmbushv_storvsc 内核模块可在初始 RAM 磁盘上使用。 在基于上游 Red Hat 分发版的系统上(可能还包括其他系统),这是一个已知问题。

重新生成 initrd 或 initramfs 映像的机制可能会因分发版而有所不同。 请查阅你的发行版文档或联系其支持渠道,以获取正确的操作步骤。 下面是使用 mkinitrd 实用工具重新生成 initrd 的示例:

  1. 备份现有 initrd 映像:

    cd /boot
    sudo cp initrd-`uname -r`.img  initrd-`uname -r`.img.bak
    
  2. 使用 hv_vmbushv_storvsc 内核模块重新生成 initrd:

    sudo mkinitrd --preload=hv_storvsc --preload=hv_vmbus -v -f initrd-`uname -r`.img `uname -r`
    

调整 VHD 大小

Azure 上的 VHD 映像其虚拟大小必须按 1 MB 对齐。 通常,通过 Hyper-V 创建的 VHD 会正确对齐。 如果 VHD 未正确对齐,则尝试从 VHD 创建映像时,可能会收到类似于以下示例的错误消息:

The VHD http://<mystorageaccount>.blob.core.chinacloudapi.cn/vhds/MyLinuxVM.vhd has an unsupported virtual size of 21475270656 bytes. The size must be a whole number (in MBs).

在这种情况下,可使用 Hyper-V 管理器控制台或 Resize-VHD PowerShell cmdlet 调整 VM 大小。 如果您不是在 Windows 环境中操作,我们建议使用 qemu-img 对 VHD 进行转换(如有需要)并调整其大小。

注释

QEMU 2.2.1 版本及某些后续版本中的 qemu-img 存在一个已知缺陷,会导致生成的 VHD 格式不正确。 此问题已在 QEMU 2.6 中修复。 建议使用版本 2.2.0 或更低版本,或使用 2.6 或更高版本。

  1. 使用 qemu-imgvbox-manage 等工具直接调整 VHD 大小可能会导致 VHD 无法启动。 建议先使用以下代码将 VHD 转换为原始磁盘映像。

    如果 VM 映像是作为原始磁盘映像创建的,则可以跳过此步骤。 在一些虚拟机监控程序(如 KVM)中,默认情况下会将 VM 映像创建为原始磁盘映像。

    sudo qemu-img convert -f vpc -O raw MyLinuxVM.vhd MyLinuxVM.raw
    
  2. 计算磁盘映像所需的大小,使虚拟大小调整为 1MB。 以下 Bash shell 脚本使用 qemu-img info 来确定磁盘映像的虚拟大小,然后将大小计算到下个 1 MB:

    rawdisk="MyLinuxVM.raw"
    vhddisk="MyLinuxVM.vhd"
    
    MB=$((1024*1024))
    size=$(qemu-img info -f raw --output json "$rawdisk" | \
    gawk 'match($0, /"virtual-size": ([0-9]+),/, val) {print val[1]}')
    
    rounded_size=$(((($size+$MB-1)/$MB)*$MB))
    
    echo "Rounded Size = $rounded_size"
    
  3. 使用 $rounded_size 调整原始磁盘的大小:

    sudo qemu-img resize MyLinuxVM.raw $rounded_size
    
  4. 将原始磁盘转换回固定大小的 VHD:

    sudo qemu-img convert -f raw -o subformat=fixed,force_size -O vpc MyLinuxVM.raw MyLinuxVM.vhd
    

    或者,对于 2.6 之前的 QEMU 版本,删除 force_size 选项:

    sudo qemu-img convert -f raw -o subformat=fixed -O vpc MyLinuxVM.raw MyLinuxVM.vhd
    

Linux 内核要求

Hyper-V 和 Azure 的 Linux 集成服务 (LIS) 驱动程序会直接影响上游 Linux 内核。 包括最新 Linux 内核版本(例如 3.x)在内的许多分发版已提供这些驱动程序,或以其他方式为其内核提供了这些驱动程序的向后移植版本。

LIS 驱动程序在上游内核中不断更新,并具有新的修补程序和功能。 如果可能,我们建议运行认可的分发版,其中包括这些修补程序和更新。

如果你正在运行 RHEL 版本 6.0 到 6.3 的一个变体,则需要安装适用于 Hyper-V 的最新 LIS 驱动程序。 从 RHEL 6.4+(和派生产品)开始,LIS 驱动程序已包含在内核中,因此,无需其他安装包。

如果需要自定义内核,我们建议使用最新的内核版本(例如 3.8+)。 对于维护自己内核的分发版或供应商,需要定期将 LIS 驱动程序从上游内核回移植到自定义内核中。

即使已运行相对较新的内核版本,我们也强烈建议跟踪 LIS 驱动程序中的任何上游修复,并根据需要向后移植这些修复。 LIS 驱动程序源文件的位置在 Linux 内核源树中的 MAINTAINERS 文件中指定:

    F:    arch/x86/include/asm/mshyperv.h
    F:    arch/x86/include/uapi/asm/hyperv.h
    F:    arch/x86/kernel/cpu/mshyperv.c
    F:    drivers/hid/hid-hyperv.c
    F:    drivers/hv/
    F:    drivers/input/serio/hyperv-keyboard.c
    F:    drivers/net/hyperv/
    F:    drivers/scsi/storvsc_drv.c
    F:    drivers/video/fbdev/hyperv_fb.c
    F:    include/linux/hyperv.h
    F:    tools/hv/

VM 的活动内核必须包含以下修补程序。 此列表并不完整,并未包括所有分发版。

Azure Linux 代理

Azure Linux 代理 (waagent) 在 Azure 中预配 Linux 虚拟机。 可以在 Linux 代理 GitHub 存储库中获取最新版本、报告问题或提交拉取请求。

下面是使用 Azure Linux 代理的一些注意事项:

  • 根据 Apache 2.0 许可证发布 Linux 代理。 许多分发版已为代理提供 .rpm 或 .deb 包。 可以轻松安装和更新这些包。
  • Azure Linux 代理需要 Python v2.6 以上版本。
  • 代理还需要 python-pyasn1 模块。 大多数分发版提供此模块作为可安装的单独包。
  • 在某些情况下,Azure Linux 代理可能与 NetworkManager 不兼容。 分发版提供的许多包(.rpm 或 .deb)都将 NetworkManager 配置为与 waagent 包冲突。 在这些情况下,安装 Linux 代理包时,代理将卸载 NetworkManager。
  • Azure Linux 代理必须至少是支持的最低版本

注释

确保已启用 udfvfat 模块。 禁用 udf 模块将导致预配失败。 禁用 vfat 模块会导致预配和启动失败。 如果这两个条件都存在,Cloud-init 版本 21.2 或更高版本可以预配 VM,而无需 UDF:

  • 使用 SSH 公钥而不是密码创建了 VM。
  • 您未提供任何自定义数据。

常规 Linux 系统要求

  1. 修改 GRUB 或 GRUB2 中的内核引导行并包含以下参数,以便将所有控制台消息发送到第一个串行端口。 这些消息可以协助Azure技术支持来调试并解决任何问题。

    GRUB_CMDLINE_LINUX="rootdelay=300 console=ttyS0 earlyprintk=ttyS0 net.ifnames=0"
    

    我们还建议删除以下参数(如果存在):

    rhgb quiet crashkernel=auto
    

    图形界面式引导和安静引导在云环境中不适用,因为云环境中需要将所有日志都发送到串行端口。 如果需要,可以保留对 crashkernel 选项的配置,但此参数会将虚拟机中的可用内存量至少减少 128 MB。 对于较小的 VM 大小,降低可用内存可能会有问题。

  2. 完成编辑 /etc/default/grub 后,重新生成 GRUB 配置。 仅运行适用于你的发行版的命令。

    对于基于 Debian 或 Ubuntu 的发行版,请运行:

    sudo update-grub
    

    对于提供的 grub2-mkconfig分发版(如 RHEL、Oracle Linux 和 SUSE),请运行:

    sudo grub2-mkconfig -o /boot/grub2/grub.cfg
    

    对于提供 grub-mkconfig 的发行版,请运行:

    sudo grub-mkconfig -o /boot/grub/grub.cfg
    

    对于 UEFI 安装,GRUB 配置路径可能有所不同。 如果你的发行版使用的是其他路径,请使用该发行版文档中记录的路径。

  3. 将 Hyper-V 模块添加到初始 RAM 磁盘。 仅运行与您的发行版所使用的工具相对应的步骤。

    1. 对于基于 dracut 的发行版,请运行:
    cd /boot
    sudo cp initramfs-<kernel-version>.img <kernel-version>.img.bak
    sudo dracut -f -v initramfs-<kernel-version>.img <kernel-version> --add-drivers "hv_vmbus hv_netvsc hv_storvsc"
    
    1. 对于使用 mkinitramfs 的发行版,请执行以下过程,而不要执行 dracut 步骤:
    cd /boot
    sudo cp initrd.img-<kernel-version> initrd.img-<kernel-version>.bak
    sudo mkinitramfs -o initrd.img-<kernel-version> <kernel-version> --with=hv_vmbus,hv_netvsc,hv_storvsc
    
  4. 仅当为远程 NVMe 磁盘控制器创建映像时,配置并验证 NVMe 启动支持。 否则,请跳过此步骤。 确切的命令可能因分布而异。

    1. 使用适用于您的发行版的方法,重新构建包含 NVMe 驱动程序的 initramfs。

      对于基于 dracut 的发行版,使 NVMe 驱动程序在今后生成的 initramfs 镜像中永久保留,并重建当前的 initramfs:

      sudo mkdir -p /etc/dracut.conf.d
      printf '%s\n' 'add_drivers+=" nvme nvme_core "' | sudo tee /etc/dracut.conf.d/azure-nvme.conf
      sudo dracut --force
      

      对于基于 initramfs 工具的分发版,使 NVMe 驱动程序持久且重新生成 initramfs:

      printf '%s\n' nvme nvme_core | sudo tee -a /etc/initramfs-tools/modules
      sudo update-initramfs -u -k all
      
    2. nvme_core.io_timeout=240 添加到 /etc/default/grub 中现有的内核命令行,然后使用适用于您的发行版的命令重新生成 GRUB 配置。

    3. 验证系统上是否提供了这两个 NVMe 驱动程序模块。 运行这两个命令:

      modinfo nvme
      modinfo nvme_core
      

      每个命令必须返回有关其模块的信息。 如果未找到任一模块,请在继续之前按照分发的文档安装或启用它。

    4. 验证这两个 NVMe 驱动程序是否都包含在 initramfs 中。 只运行适用于你的发行版的命令。

      对于基于 dracut 的发行版,请运行:

      sudo lsinitrd /boot/initramfs-$(uname -r).img | grep -E 'nvme(_core)?\.ko'
      

      对于基于 initramfs 工具的分发版,请运行:

      sudo lsinitramfs /boot/initrd.img-$(uname -r) | grep -E 'nvme(_core)?\.ko'
      

      输出内容必须同时显示 nvmenvme_core 驱动程序。 如果不是这样,请先查阅您的发行版文档,并在继续之前重新构建初始 RAM 磁盘。

  5. 请确保已安装 SSH 服务器且已将其配置为在引导时启动。 此配置通常是默认值。

  6. 安装 Azure Linux 代理。

    Azure Linux 代理是在 Azure 上设置 Linux 映像所必需的。 许多分发版以 .rpm 或 .deb 包的形式提供代理。 包通常称为 WALinuxAgentwalinuxagent。 还可以按照《Azure Linux 代理指南》中的步骤手动安装代理。

    注释

    确保已启用 udfvfat 模块。 删除或禁用它们将导致预配或启动失败。 Cloud-init 版本 21.2 或更高版本会删除 UDF 要求。

    安装 Azure Linux 代理、cloud-init 和其他必要的实用工具。 仅运行系统上可用的包管理器的命令。

    对于 dnf,请运行:

     sudo dnf install -y WALinuxAgent cloud-init cloud-utils-growpart gdisk hyperv-daemons
    

    如果您的系统提供的是 yum 而不是 dnf,请在前面的命令中将 yum 替换为 dnf

    对于 apt,请运行:

    sudo apt install walinuxagent cloud-init cloud-utils-growpart gdisk hyperv-daemons
    

    对于 zypper,请运行:

    sudo zypper install python-azure-agent cloud-init cloud-utils-growpart gdisk hyperv-daemons
    

    然后在所有发行版上启用代理程序和 cloud-init:

    sudo systemctl enable waagent.service
    sudo systemctl enable cloud-init.service
    
  7. 不要在 OS 磁盘上创建交换空间。

    可以使用 Azure Linux 代理或 cloud-init 通过本地资源磁盘配置交换空间。 Azure预配后将此资源磁盘附加到 VM。 本地资源磁盘是临时的,在取消预配 VM 时可能会清空。

    重要

    这些说明适用于远程磁盘控制器,无论其是 SCSI 还是 NVMe。 它们需要包含本地临时资源磁盘的 VM 大小。 远程磁盘控制器类型无法确定资源磁盘是否可用。

    1. 选择并配置一种方法来管理交换空间。

      若要使用 Azure Linux 代理,请修改 /etc/waagent.conf 中的以下参数:

      ResourceDisk.Format=y
      ResourceDisk.Filesystem=ext4
      ResourceDisk.MountPoint=/mnt/resource
      ResourceDisk.EnableSwap=y
      ResourceDisk.SwapSizeMB=2048    ## NOTE: Set this to your desired size.
      

      若要改用 cloud-init,请配置 cloud-init 来处理预配,并阻止 Azure Linux 代理格式化资源磁盘或创建交换空间:

      sudo sed -i 's/Provisioning.Agent=auto/Provisioning.Agent=cloud-init/g' /etc/waagent.conf
      sudo sed -i 's/ResourceDisk.Format=y/ResourceDisk.Format=n/g' /etc/waagent.conf
      sudo sed -i 's/ResourceDisk.EnableSwap=y/ResourceDisk.EnableSwap=n/g' /etc/waagent.conf
      
    2. 如果选择 cloud-init,请选择如何提供交换配置。

      每次创建 VM 时,都可以通过 customdata 传递 cloud-init 配置。 使用此方法。

      或者,将 cloud-init 指令添加到映像,以便在每次创建 VM 时配置交换空间。 若要使用此方法,请创建一个 .cfg 文件:

      echo 'DefaultEnvironment="CLOUD_CFG=/etc/cloud/cloud.cfg.d/00-azure-swap.cfg"' | sudo tee -a /etc/systemd/system.conf
      cat << EOF | sudo tee /etc/cloud/cloud.cfg.d/00-azure-swap.cfg
      #cloud-config
      # Generated by Azure cloud image build
      disk_setup:
        ephemeral0:
          table_type: mbr
          layout: [66, [33, 82]]
          overwrite: True
      fs_setup:
        - device: ephemeral0.1
          filesystem: ext4
        - device: ephemeral0.2
          filesystem: swap
      mounts:
        - ["ephemeral0.1", "/mnt/resource"]
        - ["ephemeral0.2", "none", "swap", "sw,nofail,x-systemd.requires=cloud-init.service,x-systemd.device-timeout=2", "0", "0"]
      EOF
      
  8. 配置 cloud-init 来处理预配:

    1. 为 cloud-init 配置 waagent

      sudo sed -i 's/Provisioning.Agent=auto/Provisioning.Agent=cloud-init/g' /etc/waagent.conf
      sudo sed -i 's/ResourceDisk.Format=y/ResourceDisk.Format=n/g' /etc/waagent.conf
      sudo sed -i 's/ResourceDisk.EnableSwap=y/ResourceDisk.EnableSwap=n/g' /etc/waagent.conf
      

      如果要迁移某个特定虚拟机,并且不想创建通用化映像,请在 Provisioning.Agent=disabled 配置中设置

    2. 配置挂载:

      echo "Adding mounts and disk_setup to init stage"
      sudo sed -i '/ - mounts/d' /etc/cloud/cloud.cfg
      sudo sed -i '/ - disk_setup/d' /etc/cloud/cloud.cfg
      sudo sed -i '/cloud_init_modules/a\\ - mounts' /etc/cloud/cloud.cfg
      sudo sed -i '/cloud_init_modules/a\\ - disk_setup' /etc/cloud/cloud.cfg
      
      
    3. 配置 Azure 数据源:

      echo "Allow only Azure datasource, disable fetching network setting via IMDS"
      cat << EOF | sudo tee /etc/cloud/cloud.cfg.d/91-azure_datasource.cfg
      datasource_list: [ Azure ]
      datasource:
         Azure:
           apply_network_config: False
      EOF
      
    4. 如果配置了一个交换文件,请删除现有交换文件:

      if [[ -f /mnt/resource/swapfile ]]; then
      echo "Removing swapfile" #RHEL uses a swap file by default
      swapoff /mnt/resource/swapfile
      rm /mnt/resource/swapfile -f
      fi
      
    5. 配置 cloud-init 日志记录:

      echo "Add console log file"
      cat << EOF | sudo tee -a /etc/cloud/cloud.cfg.d/05_logging.cfg
      
      # This tells cloud-init to redirect its stdout and stderr to
      # 'tee -a /var/log/cloud-init-output.log' so the user can see output
      # there without needing to look on the console.
      output: {all: '| tee -a /var/log/cloud-init-output.log'}
      EOF
      
  9. 在取消预配虚拟机之前验证磁盘装载配置。 此验证适用于 SCSI 和远程 NVMe 映像。

    1. 查看 /etc/fstab。 请勿使用设备名称,例如 /dev/sd* ,或者 /dev/nvme*,因为设备名称可以在重新启动或磁盘控制器更改时发生更改。 请改用文件系统 UUID 或其他永久性标识符。

      列出块设备及其持久标识符,以便将其与 /etc/fstab 中的条目进行比较:

      sudo blkid
      
    2. 检查 /etc/fstab 中的语法错误、无效的挂载选项以及无法解析的引用:

      sudo findmnt --verify --verbose
      
    3. 仅当为远程 NVMe 磁盘控制器创建映像时,请确认已配置的 I/O 超时时间。 如果源 VM 当前使用 NVMe,请运行:

      cat /sys/module/nvme_core/parameters/io_timeout
      

      值必须是 240。 SCSI 源 VM 可能不会公开此运行时参数;在这种情况下,请验证 nvme_core.io_timeout=240 GRUB 配置中是否存在,并且重新生成的配置是否包含它。

  10. 运行以下命令以解除预配虚拟机。

    注意

    如果要迁移特定的虚拟机,但不希望创建通用化映像,请跳过取消预配步骤。 运行命令 waagent -force -deprovision+user 将使源计算机不可用。 此步骤仅用于创建通用映像。

    sudo rm -f /var/log/waagent.log
    sudo cloud-init clean
    sudo waagent -force -deprovision+user
    sudo rm -f ~/.bash_history
    sudo export HISTSIZE=0
    

    在 VirtualBox 上,运行 waagent -force -deprovision 后,可能会看到一条错误消息显示 [Errno 5] Input/output error。 此错误消息并不重要,可以忽略它。

  11. 关闭虚拟机并将 VHD 上传到 Azure。

后续步骤

使用 Azure CLI 从自定义磁盘创建 Linux VM