Applies to: ✔️ Linux VMs ✔️ Flexible scale sets ✔️ Uniform scale sets
In this article, you learn how to prepare a Red Hat Enterprise Linux (RHEL) virtual machine (VM) for use in Azure. The versions of RHEL that are covered in this article are 7.x, 8.X., 9.x. The hypervisors for preparation that are covered in this article are Hyper-V, kernel-based VM (KVM), VMware, and Kickstart.
For more information about eligibility requirements for participating in Red Hat's Cloud Access program, see the Red Hat Cloud Access website and Running RHEL on Azure. For ways to automate building RHEL images, see Azure Image Builder.
Prerequisites
Note
This document assumes you already deployed RHEL7 and have it fully licensed. Uploaded images that are at or beyond EOL are supported on a reasonable business-effort basis. For more information, see the Red Hat Product Life Cycles.
RHEL installation notes
Important
The remote NVMe preparation steps in this article are optional. Complete them only if you're creating an image that you intend to deploy with a remote NVMe disk controller. If the target VM uses SCSI, skip every step labeled for remote NVMe and retain the standard settings. Remote NVMe requires a Gen2 image and a supported RHEL release. Review Supported OS images for remote NVMe, and verify that the exact target VM size advertises NVMe in its DiskControllerTypes capability.
- Azure doesn't support the VHDX format. Azure supports only fixed VHD. You can use Hyper-V Manager to convert the disk to VHD format, or you can use the
convert-vhd cmdlet. If you use VirtualBox, select Fixed size as opposed to the default dynamically allocated option when you create the disk.
- Azure supports Gen1 (BIOS boot) and Gen2 (UEFI boot) VMs.
- The maximum allowed size for the VHD is 1,023 GB.
- The vfat kernel module must be enabled in the kernel.
- Logical Volume Manager (LVM) is supported and can be used on the OS disk or data disks in Azure VMs. In general, we recommend that you use standard partitions on the OS disk rather than LVM. This practice avoids LVM name conflicts with cloned VMs, particularly if you ever need to attach an operating system disk to another identical VM for troubleshooting. For more information, see the LVM and RAID documentation.
- Kernel support for mounting Universal Disk Format (UDF) file systems is required. At first boot on Azure, the attached UDF-formatted media passes the provisioning configuration to the Linux VM. The Azure Linux agent must be able to mount the UDF file system to read its configuration and provision the VM. Without this step, provisioning fails.
- Don't configure a swap partition on the operating system disk. For more information, read the following steps.
- All VHDs on Azure must have a virtual size aligned to 1 MB. When you convert from a raw disk to VHD, you must ensure that the raw disk size is a multiple of 1 MB before conversion. For more information, read the following steps. See also Linux installation notes.
Important
Swap guidance that uses the temporary resource disk applies only to VM sizes that include local temporary storage. The remote disk controller type, SCSI or NVMe, doesn't determine whether a resource disk is available.
Note
Cloud-init >= 21.2 removes the UDF requirement. However, without the UDF module enabled, the provided CD-ROM fails to mount, preventing custom data from being applied. A workaround is to apply custom data by using user data. Unlike custom data, user data isn't encrypted. For more information, see User data formats.
In Hyper-V Manager, select the VM.
Select Connect to open a console window for the VM.
Create or edit the /etc/sysconfig/network file, and add the following text:
NETWORKING=yes
HOSTNAME=localhost.localdomain
Create or edit the /etc/sysconfig/network-scripts/ifcfg-eth0 file, and add the following text:
DEVICE=eth0
ONBOOT=yes
BOOTPROTO=dhcp
TYPE=Ethernet
USERCTL=no
PEERDNS=yes
IPV6INIT=no
PERSISTENT_DHCLIENT=yes
NM_CONTROLLED=yes
Note
When you use Accelerated Networking, the provisioned synthetic interface must be configured to be unmanaged by using a udev rule. This action prevents NetworkManager from assigning the same IP to it as the primary interface.
To apply it:
sudo tee <<EOF /etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules > /dev/null
# Accelerated Networking on Azure exposes a new SRIOV interface to the VM.
# This interface is transparently bonded to the synthetic interface,
# so NetworkManager should just ignore any SRIOV interfaces.
SUBSYSTEM=="net", DRIVERS=="hv_pci", ACTION!="remove", ENV{NM_UNMANAGED}="1"
EOF
Ensure that the network service starts at boot time:
sudo systemctl enable network
Register your Red Hat subscription to enable the installation of packages from the RHEL repository:
sudo subscription-manager register --auto-attach --username=XXX --password=XXX
Modify the kernel boot line in your grub configuration to include more kernel parameters for Azure. To do this modification, open /etc/default/grub in a text editor and edit the GRUB_CMDLINE_LINUX parameter. For example:
GRUB_TIMEOUT=10
GRUB_CMDLINE_LINUX="console=tty1 console=ttyS0,115200n8 earlyprintk=ttyS0 net.ifnames=0"
GRUB_TERMINAL="serial console"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"
These modifications turn off the new naming convention for NICs, ensure all console messages are sent to the first serial port, and enable interaction with the console, which can assist Aure support with debugging issues.
rhgb quiet crashkernel=auto
Graphical and quiet boots aren't useful in a cloud environment where you want all the logs to be sent to the serial port. You can leave the crashkernel option configured if you want. This parameter reduces the amount of available memory in the VM by 128 MB or more, which might be a problem for smaller VM sizes.
After you're finished editing /etc/default/grub, run the following command to rebuild the grub configuration:
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
Note
If you're uploading a UEFI-enabled VM, the command to update grub is grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg.
Only if you're creating an image for a remote NVMe disk controller, configure and verify NVMe boot support. Otherwise, skip this step.
Make the NVMe drivers persistent in future initramfs images by creating a dracut configuration file, and then rebuild the current initramfs:
sudo tee /etc/dracut.conf.d/azure-nvme.conf > /dev/null <<EOF
add_drivers+=" nvme nvme_core "
EOF
sudo dracut --force --verbose
Add the Azure NVMe I/O timeout to the kernel command line so the setting persists across reboots:
sudo grubby --update-kernel=ALL --args="nvme_core.io_timeout=240"
Confirm that both NVMe modules exist in the kernel's module tree so they can be packaged into initramfs. This check reads the on-disk module files, so it succeeds even on a SCSI preparation VM and doesn't imply that NVMe is loaded or in use:
modinfo nvme
modinfo nvme_core
Each command must return module details. If either module isn't found, install or enable it by following the Red Hat documentation before you continue.
Confirm that the rebuilt initramfs package includes both NVMe drivers. This check confirms the image is ready:
sudo lsinitrd /boot/initramfs-$(uname -r).img | grep -E 'nvme(_core)?\.ko'
The output must list both the nvme and nvme_core drivers. After the image boots on an NVMe VM, verify the runtime timeout by running cat /sys/module/nvme_core/parameters/io_timeout; the expected value is 240.
For both SCSI and NVMe images, use file-system UUIDs or another persistent identifier in /etc/fstab. Don't use /dev/sd* or /dev/nvme* device names, because device names can change across reboots or when the disk controller changes.
List the block devices and their persistent identifiers so that you can compare them with the entries in /etc/fstab:
sudo blkid
Check /etc/fstab for syntax errors, invalid mount options, and references that can't be resolved:
sudo findmnt --verify --verbose
Ensure that the SSH server is installed and configured to start at boot time, which is usually the default. Modify /etc/ssh/sshd_config to include the following line:
ClientAliveInterval 180
The WALinuxAgent package, WALinuxAgent-<version>, is available from the Extras repository.
sudo subscription-manager repos --enable=rhel-7-server-extras-rpms
Install the Azure Linux agent, cloud-init, and other necessary utilities:
sudo yum install -y WALinuxAgent cloud-init cloud-utils-growpart gdisk hyperv-daemons
sudo systemctl enable waagent.service
sudo systemctl enable cloud-init.service
Configure cloud-init to handle the provisioning:
Configure waagent for cloud-init:
sudo sed -i 's/Provisioning.Agent=auto/Provisioning.Agent=auto/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
Note
If you're migrating a specific VM and don't want to create a generalized image, set Provisioning.Agent=disabled on the /etc/waagent.conf configuration.
Configure mounts:
sudo 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
Configure the Azure data source:
sudo echo "Allow only Azure datasource, disable fetching network setting via IMDS"
sudo tee /etc/cloud/cloud.cfg.d/91-azure_datasource.cfg <<EOF
datasource_list: [ Azure ]
datasource:
Azure:
apply_network_config: False
EOF
If configured, remove the existing swap file:
if [[ -f /mnt/resource/swapfile ]]; then
echo "Removing swapfile" #RHEL uses a swapfile by default
swapoff /mnt/resource/swapfile
rm /mnt/resource/swapfile -f
fi
Configure cloud-init logging:
sudo echo "Add console log file"
sudo tee <<EOF /etc/cloud/cloud.cfg.d/05_logging.cfg > /dev/null
# 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
Swap configuration:
Don't create swap space on the operating system disk.
Previously, the Azure Linux agent was used to automatically configure swap space on the local resource disk attached to the VM after the VM is provisioned on Azure, cloud-init handles now this action. You must not use the Linux agent to format the resource disk to create the swap file. Modify the following parameters in /etc/waagent.conf appropriately:
ResourceDisk.Format=n
ResourceDisk.EnableSwap=n
If you want to mount, format, and create the swap, you can either:
Pass this code in as a cloud-init configuration every time you create a VM through custom data. We recommend this method.
Use a cloud-init directive baked into the image that does this step every time the VM is created.
sudo echo 'DefaultEnvironment="CLOUD_CFG=/etc/cloud/cloud.cfg.d/00-azure-swap.cfg"' >> /etc/systemd/system.conf
sudo tee /etc/cloud/cloud.cfg.d/00-azure-swap.cfg <<EOF
#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
Configure cloud-init telemetry to assist with troubleshooting for provisioning issues:
sudo tee -a /etc/cloud/cloud.cfg.d/10-azure-kvp.cfg <<EOF
# This config enables cloud-init to report provisioning telemetry to aid with troubleshooting
Reporting:
logging:
type: log
telemetry:
type: hyperv
EOF
If you want to unregister the subscription, run the following command:
sudo subscription-manager unregister
Deprovision the VM and prepare it for provisioning on Azure:
Caution
If you're migrating a specific VM and don't want to create a generalized image, skip the deprovision step. Running the command waagent -force -deprovision+user renders the source machine unusable. This step is intended only to create a generalized image.
sudo rm -f /var/log/waagent.log
sudo cloud-init clean --logs --seed
sudo waagent -force -deprovision+user
sudo rm -f ~/.bash_history
sudo export HISTSIZE=0
Select Action > Shut Down in Hyper-V Manager. Your Linux VHD is now ready to be uploaded to Azure.
In Hyper-V Manager, select the VM.
Select Connect to open a console window for the VM.
Ensure that the Network Manager service starts at boot time:
sudo systemctl enable NetworkManager.service
Prevent NetworkManager from configuring the adapter added for accelerated networking.
sudo tee <<EOF /etc/NetworkManager/conf.d/99-azure-unmanaged-devices.conf > /dev/null
[keyfile]
unmanaged-devices=driver:mlx4_core,driver:mlx5_core
EOF
Configure the network interface to automatically start at boot and use the Dynamic Host Configuration Protocol:
sudo nmcli con mod eth0 connection.autoconnect yes ipv4.method auto
Note
When you use Accelerated Networking, the provisioned synthetic interface must be configured to be unmanaged by using a udev rule. This action prevents NetworkManager from assigning the same IP to it as the primary interface.
To apply it:
sudo tee <<EOF /etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules > /dev/null
# Accelerated Networking on Azure exposes a new SRIOV interface to the VM.
# This interface is transparently bonded to the synthetic interface,
# so NetworkManager should just ignore any SRIOV interfaces.
SUBSYSTEM=="net", DRIVERS=="hv_pci", ACTION!="remove", ENV{NM_UNMANAGED}="1"
EOF
Register your Red Hat subscription to enable the installation of packages from the RHEL repository:
sudo subscription-manager register --auto-attach --username=XXX --password=XXX
Modify the kernel boot line in your grub configuration to include more kernel parameters for Azure and enable the serial console.
Remove current GRUB parameters:
sudo grub2-editenv - unset kernelopts
Edit /etc/default/grub in a text editor, and add the following parameters:
GRUB_TIMEOUT=10
GRUB_CMDLINE_LINUX="console=tty1 console=ttyS0,115200n8 earlyprintk=ttyS0,115200 earlyprintk=ttyS0 net.ifnames=0"
GRUB_TERMINAL="serial console"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"
These modifications turn off the new naming convention for NICs, ensure all console messages are sent to the first serial port, and enable interaction with the console, which can assist Aure support with debugging issues.
Note
If ENABLE_BLSCFG=false is present in /etc/default/grub instead of ENABLE_BLSCFG=true, tools such as grubedit or gubby, which rely on the Boot Loader Specification (BLS) for managing boot entries and configurations, might not function correctly in RHEL 8 and 9. If ENABLE_BLSCFG isn't present, the default behavior is false.
We recommend that you also remove the following parameters:
rhgb quiet crashkernel=auto
Graphical and quiet boots aren't useful in a cloud environment where you want all the logs to be sent to the serial port. You can leave the crashkernel option configured if you want. This parameter reduces the amount of available memory in the VM by 128 MB or more, which might be a problem for smaller VM sizes.
After you're finished editing /etc/default/grub, run the following command to rebuild the grub configuration:
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
sudo grubby --update-kernel=ALL
Only if you're creating an image for a remote NVMe disk controller, configure and verify NVMe boot support. Otherwise, skip this step.
Make the NVMe drivers persistent in future initramfs images by creating a dracut configuration file, and then rebuild the current initramfs:
sudo tee /etc/dracut.conf.d/azure-nvme.conf > /dev/null <<EOF
add_drivers+=" nvme nvme_core "
EOF
sudo dracut --force --verbose
Add the Azure NVMe I/O timeout to the kernel command line so the setting persists across reboots:
sudo grubby --update-kernel=ALL --args="nvme_core.io_timeout=240"
Confirm that both NVMe modules exist in the kernel's module tree so they can be packaged into initramfs. This check reads the on-disk module files, so it succeeds even on a SCSI preparation VM and doesn't imply that NVMe is loaded or in use:
modinfo nvme
modinfo nvme_core
Each command must return module details. If either module isn't found, install or enable it by following the Red Hat documentation before you continue.
Confirm that the rebuilt initramfs package includes both NVMe drivers. This check confirms the image is ready:
sudo lsinitrd /boot/initramfs-$(uname -r).img | grep -E 'nvme(_core)?\.ko'
The output must list both the nvme and nvme_core drivers. After the image boots on an NVMe VM, verify the runtime timeout by running cat /sys/module/nvme_core/parameters/io_timeout; the expected value is 240.
For both SCSI and NVMe images, use file-system UUIDs or another persistent identifier in /etc/fstab. Don't use /dev/sd* or /dev/nvme* device names, because device names can change across reboots or when the disk controller changes.
List the block devices and their persistent identifiers so that you can compare them with the entries in /etc/fstab:
sudo blkid
Check /etc/fstab for syntax errors, invalid mount options, and references that can't be resolved:
sudo findmnt --verify --verbose
Ensure that the SSH server is installed and configured to start at boot time, which is usually the default. Modify /etc/ssh/sshd_config to include the following line:
ClientAliveInterval 180
Install the Azure Linux agent, cloud-init, and other necessary utilities:
sudo yum install -y WALinuxAgent cloud-init cloud-utils-growpart gdisk hyperv-daemons
sudo systemctl enable waagent.service
sudo systemctl enable cloud-init.service
Configure cloud-init to handle the provisioning:
Configure waagent for cloud-init:
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
Note
If you're migrating a specific VM and don't want to create a generalized image, set Provisioning.Agent=disabled on the /etc/waagent.conf configuration.
Configure mounts:
Note
Not required for cloud-init v23.x and newer.
sudo 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
Configure the Azure data source:
sudo echo "Allow only Azure datasource, disable fetching network setting via IMDS"
sudo tee /etc/cloud/cloud.cfg.d/91-azure_datasource.cfg <<EOF
datasource_list: [ Azure ]
datasource:
Azure:
apply_network_config: False
EOF
If configured, remove the existing swap file:
if [[ -f /mnt/resource/swapfile ]]; then
echo "Removing swapfile" #RHEL uses a swapfile by defaul
swapoff /mnt/resource/swapfile
rm /mnt/resource/swapfile -f
fi
Configure cloud-init logging:
sudo echo "Add console log file"
sudo tee <<EOF /etc/cloud/cloud.cfg.d/05_logging.cfg > /dev/null
# 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
Swap configuration:
Don't create swap space on the operating system disk.
Previously, the Azure Linux agent was used to automatically configure swap space on the local resource disk attached to the VM after the VM is provisioned on Azure, cloud-init handles now this action. You must not use the Linux agent to format the resource disk to create the swap file. Modify the following parameters in /etc/waagent.conf appropriately:
ResourceDisk.Format=n
ResourceDisk.EnableSwap=n
Pass this code in as a cloud-init configuration every time you create a VM through custom data. We recommend this method.
Use a cloud-init directive baked into the image that does this step every time the VM is created.
sudo echo 'DefaultEnvironment="CLOUD_CFG=/etc/cloud/cloud.cfg.d/00-azure-swap.cfg"' >> /etc/systemd/system.conf
sudo tee /etc/cloud/cloud.cfg.d/00-azure-swap.cfg <<EOF
#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.device-timeout=2,x-systemd.requires=cloud-init.service", "0", "0"]
EOF
Configure cloud-init telemetry to assist with troubleshooting for provisioning issues:
sudo tee -a /etc/cloud/cloud.cfg.d/10-azure-kvp.cfg <<EOF
# This config enables cloud-init to report provisioning telemetry to aid with troubleshooting
Reporting:
logging:
type: log
telemetry:
type: hyperv
EOF
If you want to unregister the subscription, run the following command:
sudo subscription-manager unregister
Run the following commands to deprovision the VM and prepare it for provisioning on Azure:
sudo cloud-init clean --logs --seed
sudo waagent -force -deprovision+user
sudo rm -f ~/.bash_history
sudo sudo rm -f /var/log/waagent.log
sudo export HISTSIZE=0
Caution
If you're migrating a specific VM and don't want to create a generalized image, skip the deprovision step. Running the command waagent -force -deprovision+user renders the source machine unusable. This step is intended only to create a generalized image.
Select Action > Shut Down in Hyper-V Manager. Your Linux VHD is now ready to be uploaded to Azure.
This section shows you how to use KVM to prepare RHEL 7 to upload to Azure.
Download the RHEL 7 KVM image of from the Red Hat website.
Set a root password.
Generate an encrypted password, and copy the output of the command:
sudo openssl passwd -1 changeme
Set a root password with guestfish:
sudo guestfish --rw -a <image-name>
> <fs> run
> <fs> list-filesystems
> <fs> mount /dev/sda1 /
> <fs> vi /etc/shadow
> <fs> exit
Change the second field for the root user from !! to the encrypted password.
Create a VM in KVM from the qcow2 image. Set the disk type to qcow2, and set the virtual network interface device model to virtio. Then, start the VM and sign in as root.
Create or edit the /etc/sysconfig/network file, and add the following text:
NETWORKING=yes
HOSTNAME=localhost.localdomain
Create or edit the /etc/sysconfig/network-scripts/ifcfg-eth0 file, and add the following text:
DEVICE=eth0
ONBOOT=yes
BOOTPROTO=dhcp
TYPE=Ethernet
USERCTL=no
PEERDNS=yes
IPV6INIT=no
PERSISTENT_DHCLIENT=yes
NM_CONTROLLED=yes
Note
When you use Accelerated Networking, the provisioned synthetic interface must be configured to be unmanaged by using a udev rule. This action prevents NetworkManager from assigning the same IP to it as the primary interface.
To apply it:
sudo tee <<EOF /etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules > /dev/null
# Accelerated Networking on Azure exposes a new SRIOV interface to the VM.
# This interface is transparently bonded to the synthetic interface,
# so NetworkManager should just ignore any SRIOV interfaces.
SUBSYSTEM=="net", DRIVERS=="hv_pci", ACTION!="remove", ENV{NM_UNMANAGED}="1"
EOF
Ensure that the network service starts at boot time:
sudo systemctl enable network
Register your Red Hat subscription to enable installation of packages from the RHEL 7 repository:
sudo subscription-manager register --auto-attach --username=XXX --password=XXX
Modify the kernel boot line in your grub configuration to include more kernel parameters for Azure. To do this configuration, open /etc/default/grub in a text editor and edit the GRUB_CMDLINE_LINUX parameter. For example:
GRUB_CMDLINE_LINUX="console=ttyS0 earlyprintk=ttyS0 net.ifnames=0"
This command also ensures that all console messages are sent to the first serial port, which can assist Azure support with debugging issues. The command also turns off the new naming conventions for NICs. We also recommend that you remove the following parameters:
rhgb quiet crashkernel=auto
Graphical and quiet boots aren't useful in a cloud environment where you want all the logs to be sent to the serial port. You can leave the crashkernel option configured if you want. This parameter reduces the amount of available memory in the VM by 128 MB or more, which might be a problem for smaller VM sizes.
After you're finished editing /etc/default/grub, run the following command to rebuild the grub configuration:
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
Note
If you're uploading a UEFI-enabled VM, the command to update grub is grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg.
Add Hyper-V modules into initramfs.
Edit /etc/dracut.conf and add content:
add_drivers+=" hv_vmbus hv_netvsc hv_storvsc "
Rebuild initramfs:
sudo dracut -f -v
Uninstall cloud-init:
sudo yum remove cloud-init
Only if you're creating an image for a remote NVMe disk controller, configure and verify NVMe boot support. Otherwise, skip this step.
Make the NVMe drivers persistent in future initramfs images by creating a dracut configuration file, and then rebuild the current initramfs:
sudo tee /etc/dracut.conf.d/azure-nvme.conf > /dev/null <<EOF
add_drivers+=" nvme nvme_core "
EOF
sudo dracut --force --verbose
Add the Azure NVMe I/O timeout to the kernel command line so the setting persists across reboots:
sudo grubby --update-kernel=ALL --args="nvme_core.io_timeout=240"
Confirm that both NVMe modules exist in the kernel's module tree so they can be packaged into initramfs. This check reads the on-disk module files, so it succeeds even on a SCSI preparation VM and doesn't imply that NVMe is loaded or in use:
modinfo nvme
modinfo nvme_core
Each command must return module details. If either module isn't found, install or enable it by following the Red Hat documentation before you continue.
Confirm that the rebuilt initramfs package includes both NVMe drivers. This check confirms the image is ready:
sudo lsinitrd /boot/initramfs-$(uname -r).img | grep -E 'nvme(_core)?\.ko'
The output must list both the nvme and nvme_core drivers. After the image boots on an NVMe VM, verify the runtime timeout by running cat /sys/module/nvme_core/parameters/io_timeout; the expected value is 240.
For both SCSI and NVMe images, use file-system UUIDs or another persistent identifier in /etc/fstab. Don't use /dev/sd* or /dev/nvme* device names, because device names can change across reboots or when the disk controller changes.
List the block devices and their persistent identifiers so that you can compare them with the entries in /etc/fstab:
sudo blkid
Check /etc/fstab for syntax errors, invalid mount options, and references that can't be resolved:
sudo findmnt --verify --verbose
Ensure that the SSH server is installed and configured to start at boot time:
sudo systemctl enable sshd
Modify /etc/ssh/sshd_config to include the following lines:
PasswordAuthentication yes
ClientAliveInterval 180
The WALinuxAgent package, WALinuxAgent-<version>, is located in the Red Hat extras repository. Enable the extras repository:
sudo subscription-manager repos --enable=rhel-7-server-extras-rpms
Install the Azure Linux agent:
sudo yum install WALinuxAgent
Enable the waagent service:
sudo systemctl enable waagent.service
Install cloud-init.
Follow the steps in "Prepare a RHEL 7 VM from Hyper-V Manager," step 14, "Configure cloud-init to handle the provisioning."
Swap configuration:
- Don't create swap space on the operating system disk.
- Follow the steps in "Prepare a RHEL 7 VM from Hyper-V Manager," step 15, "Swap configuration."
Unregister the subscription (if necessary):
sudo subscription-manager unregister
Deprovision by following the steps in "Prepare a RHEL 7 VM from Hyper-V Manager," step 18, "Deprovision the VM and prepare it for provisioning on Azure."
Shut down the VM in KVM.
Convert the qcow2 image to the VHD format.
Note
There's a known bug in QEMU versions between 2.2.1 and 2.6 that results in an improperly formatted VHD. We recommend that you use either qemu-img 2.2.0 or lower, or update to 2.6 or higher. For more information, see this website.
First convert the image to raw format:
sudo qemu-img convert -f qcow2 -O raw rhel-7.4.qcow2 rhel-7.4.raw
Make sure that the size of the raw image is aligned with 1 MB. Otherwise, round up the size to align with 1 MB:
MB=$((1024*1024))
size=$(qemu-img info -f raw --output json "rhel-7.4.raw" | \
jq '."virtual-size"')
rounded_size=$((($size/$MB + 1)*$MB))
sudo qemu-img resize rhel-7.4.raw $rounded_size
Convert the raw disk to a fixed-size VHD:
sudo qemu-img convert -f raw -o subformat=fixed -O vpc rhel-7.4.raw rhel-7.4.vhd
Or, with qemu version 1.6+, include the force_size option:
sudo qemu-img convert -f raw -o subformat=fixed,force_size -O vpc rhel-7.4.raw rhel-7.4.vhd
Ensure that the Network Manager service starts at boot time:
sudo systemctl enable NetworkManager.service
Configure the network interface to automatically start at boot and use the Dynamic Host Configuration Protocol:
sudo nmcli con mod eth0 connection.autoconnect yes ipv4.method auto
Note
When you use Accelerated Networking, the provisioned synthetic interface must be configured to be unmanaged by using a udev rule. This action prevents NetworkManager from assigning the same IP to it as the primary interface.
To apply it:
sudo tee <<EOF /etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules >/dev/null
# Accelerated Networking on Azure exposes a new SRIOV interface to the VM.
# This interface is transparently bonded to the synthetic interface,
# so NetworkManager should just ignore any SRIOV interfaces.
SUBSYSTEM=="net", DRIVERS=="hv_pci", ACTION!="remove", ENV{NM_UNMANAGED}="1"
EOF
Prevent NetworkManager from configuring the adapter added for accelerated networking.
sudo tee <<EOF /etc/NetworkManager/conf.d/99-azure-unmanaged-devices.conf > /dev/null
[keyfile]
unmanaged-devices=driver:mlx4_core,driver:mlx5_core
EOF
Register your Red Hat subscription to enable the installation of packages from the RHEL repository:
sudo subscription-manager register --auto-attach --username=XXX --password=XXX
Modify the kernel boot line in your grub configuration to include more kernel parameters for Azure and enable the serial console.
Remove current GRUB parameters:
sudo grub2-editenv - unset kernelopts
Edit /etc/default/grub in a text editor, and add the following parameters:
GRUB_TIMEOUT=10
GRUB_CMDLINE_LINUX="console=tty1 console=ttyS0,115200n8 earlyprintk=ttyS0,115200 earlyprintk=ttyS0 net.ifnames=0"
GRUB_TERMINAL="serial console"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"
ENABLE_BLSCFG=true
These modifications turn off the new naming convention for NICs, ensure all console messages are sent to the first serial port, and enable interaction with the console, which can assist Aure support with debugging issues.
Note
If ENABLE_BLSCFG=false is present in /etc/default/grub instead of ENABLE_BLSCFG=true, tools such as grubedit or gubby, which rely on the Boot Loader Specification (BLS) for managing boot entries and configurations, might not function correctly in RHEL 8,9 and 10. If ENABLE_BLSCFG isn't present, the default behavior is false.
We recommend that you also remove the following parameters:
rhgb quiet crashkernel=auto
Graphical and quiet boots aren't useful in a cloud environment where you want all the logs to be sent to the serial port. You can leave the crashkernel option configured if you want. This parameter reduces the amount of available memory in the VM by 128 MB or more, which might be a problem for smaller VM sizes.
After you're finished editing /etc/default/grub, run the following command to rebuild the grub configuration:
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
sudo grubby --update-kernel=ALL
Add Hyper-V modules into initramfs.
Create /etc/dracut.conf.d/azure.conf and add content:
add_drivers+=" hv_vmbus hv_netvsc hv_storvsc "
Rebuild initramfs:
sudo dracut -f -v
Only if you're creating an image for a remote NVMe disk controller, configure and verify NVMe boot support. Otherwise, skip this step.
Make the NVMe drivers persistent in future initramfs images by creating a dracut configuration file, and then rebuild the current initramfs:
sudo tee /etc/dracut.conf.d/azure-nvme.conf > /dev/null <<EOF
add_drivers+=" nvme nvme_core "
EOF
sudo dracut --force --verbose
Add the Azure NVMe I/O timeout to the kernel command line so the setting persists across reboots:
sudo grubby --update-kernel=ALL --args="nvme_core.io_timeout=240"
Confirm that both NVMe modules exist in the kernel's module tree so they can be packaged into initramfs. This check reads the on-disk module files, so it succeeds even on a SCSI preparation VM and doesn't imply that NVMe is loaded or in use:
modinfo nvme
modinfo nvme_core
Each command must return module details. If either module isn't found, install or enable it by following the Red Hat documentation before you continue.
Confirm that the rebuilt initramfs package includes both NVMe drivers. This check confirms the image is ready:
sudo lsinitrd /boot/initramfs-$(uname -r).img | grep -E 'nvme(_core)?\.ko'
The output must list both the nvme and nvme_core drivers. After the image boots on an NVMe VM, verify the runtime timeout by running cat /sys/module/nvme_core/parameters/io_timeout; the expected value is 240.
For both SCSI and NVMe images, use file-system UUIDs or another persistent identifier in /etc/fstab. Don't use /dev/sd* or /dev/nvme* device names, because device names can change across reboots or when the disk controller changes.
List the block devices and their persistent identifiers so that you can compare them with the entries in /etc/fstab:
sudo blkid
Check /etc/fstab for syntax errors, invalid mount options, and references that can't be resolved:
sudo findmnt --verify --verbose
Ensure that the SSH server is installed and configured to start at boot time, which is usually the default. Modify /etc/ssh/sshd_config to include the following line:
ClientAliveInterval 180
Install the Azure Linux agent, cloud-init, and other necessary utilities:
sudo yum install -y WALinuxAgent cloud-init cloud-utils-growpart gdisk hyperv-daemons
sudo systemctl enable waagent.service
sudo systemctl enable cloud-init.service
Configure cloud-init to handle the provisioning:
Configure waagent for cloud-init:
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
Note
If you're migrating a specific VM and don't want to create a generalized image, set Provisioning.Agent=disabled on the /etc/waagent.conf configuration.
Configure mounts:
sudo 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
Note
The items for each section in the /etc/cloud/cloud.cfg file must use the same level
of indentation
Example of an incorrect setting:
cloud_init_modules:
- disk_setup
- mounts
- migrators
Example of a correct setting:
cloud_init_modules:
- disk_setup
- mounts
- migrators
Configure the Azure data source:
sudo echo "Allow only Azure datasource, disable fetching network setting via IMDS"
sudo tee /etc/cloud/cloud.cfg.d/91-azure_datasource.cfg <<EOF
datasource_list: [ Azure ]
datasource:
Azure:
apply_network_config: False
EOF
If configured, remove the existing swap file:
if [[ -f /mnt/resource/swapfile ]]; then
echo "Removing swapfile" #RHEL uses a swapfile by default
swapoff /mnt/resource/swapfile
rm /mnt/resource/swapfile -f
fi
Configure cloud-init logging:
sudo echo "Add console log file"
sudo tee <<EOF /etc/cloud/cloud.cfg.d/05_logging.cfg >/dev/null
# 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
Swap configuration:
Don't create swap space on the operating system disk.
Previously, the Azure Linux agent was used to automatically configure swap space on the local resource disk attached to the VM after the VM is provisioned on Azure, cloud-init handles now this action. You must not use the Linux agent to format the resource disk to create the swap file. Modify the following parameters in /etc/waagent.conf appropriately:
ResourceDisk.Format=n
ResourceDisk.EnableSwap=n
Pass this code in as a cloud-init configuration every time you create a VM through custom data. We recommend this method.
Use a cloud-init directive baked into the image that does this step every time the VM is created.
sudo echo 'DefaultEnvironment="CLOUD_CFG=/etc/cloud/cloud.cfg.d/00-azure-swap.cfg"' >> /etc/systemd/system.conf
sudo tee /etc/cloud/cloud.cfg.d/00-azure-swap.cfg <<EOF
#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.device-timeout=2,x-systemd.requires=cloud-init.service", "0", "0"]
EOF
Configure cloud-init telemetry to assist with troubleshooting for provisioning issues:
sudo tee -a /etc/cloud/cloud.cfg.d/10-azure-kvp.cfg <<EOF
# This config enables cloud-init to report provisioning telemetry to aid with troubleshooting
Reporting:
logging:
type: log
telemetry:
type: hyperv
EOF
If you want to unregister the subscription, run the following command:
sudo subscription-manager unregister
Run the following commands to deprovision the VM and prepare it for provisioning on Azure:
sudo cloud-init clean --logs --seed
sudo waagent -force -deprovision+user
sudo rm -f ~/.bash_history
sudo sudo rm -f /var/log/waagent.log
sudo export HISTSIZE=0
Caution
If you're migrating a specific VM and don't want to create a generalized image, skip the deprovision step. Running the command waagent -force -deprovision+user renders the source machine unusable. This step is intended only to create a generalized image.
Shut down the VM in KVM. Your Linux VHD is now ready to be uploaded to Azure.
Convert the qcow2 image to the VHD format.
Note
There's a known bug in QEMU versions between 2.2.1 and 2.6 that results in an improperly formatted VHD. We recommend that you use either qemu-img 2.2.0 or lower, or update to 2.6 or higher. For more information, see this website.
First convert the image to raw format:
sudo qemu-img convert -f qcow2 -O raw rhel-[version].qcow2 rhel-[version].raw
Make sure that the size of the raw image is aligned with 1 MB. Otherwise, round up the size to align with 1 MB:
MB=$((1024*1024))
size=$(qemu-img info -f raw --output json "rhel-[version].raw" | \
jq '."virtual-size"')
rounded_size=$((($size/$MB + 1)*$MB))
sudo qemu-img resize rhel-[version].raw $rounded_size
Convert the raw disk to a fixed-size VHD:
sudo qemu-img convert -f raw -o subformat=fixed -O vpc rhel-[verion].raw rhel-[version].vhd
Or, with qemu version 1.6+, include the force_size option:
sudo qemu-img convert -f raw -o subformat=fixed,force_size -O vpc rhel-[version].raw rhel-[version].vhd
Create or edit the /etc/sysconfig/network file, and add the following text:
NETWORKING=yes
HOSTNAME=localhost.localdomain
Create or edit the /etc/sysconfig/network-scripts/ifcfg-eth0 file, and add the following text:
DEVICE=eth0
ONBOOT=yes
BOOTPROTO=dhcp
TYPE=Ethernet
USERCTL=no
PEERDNS=yes
IPV6INIT=no
PERSISTENT_DHCLIENT=yes
NM_CONTROLLED=yes
Note
When you use Accelerated Networking, the provisioned synthetic interface must be configured to be unmanaged by using a udev rule. This action prevents NetworkManager from assigning the same IP to it as the primary interface.
To apply it:
sudo tee <<EOF /etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules > /dev/null
# Accelerated Networking on Azure exposes a new SRIOV interface to the VM.
# This interface is transparently bonded to the synthetic interface,
# so NetworkManager should just ignore any SRIOV interfaces.
SUBSYSTEM=="net", DRIVERS=="hv_pci", ACTION!="remove", ENV{NM_UNMANAGED}="1"
EOF
Ensure that the network service starts at boot time:
sudo systemctl enable network
Register your Red Hat subscription to enable the installation of packages from the RHEL repository:
sudo subscription-manager register --auto-attach --username=XXX --password=XXX
Modify the kernel boot line in your grub configuration to include more kernel parameters for Azure. To do this modification, open /etc/default/grub in a text editor and edit the GRUB_CMDLINE_LINUX parameter. For example:
GRUB_CMDLINE_LINUX="console=ttyS0 earlyprintk=ttyS0 net.ifnames=0"
These modifications turn off the new naming convention for NICs, ensure all console messages are sent to the first serial port and enable interaction with the console, which can assist Aure support with debugging issues, we recommend that you remove the following parameters:
rhgb quiet crashkernel=auto
Graphical and quiet boots aren't useful in a cloud environment where you want all the logs to be sent to the serial port. You can leave the crashkernel option configured if you want. This parameter reduces the amount of available memory in the VM by 128 MB or more, which might be a problem for smaller VM sizes.
After you're finished editing /etc/default/grub, run the following command to rebuild the grub configuration:
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
Add Hyper-V modules to initramfs:
Edit /etc/dracut.conf, add content:
add_drivers+=" hv_vmbus hv_netvsc hv_storvsc "
Rebuild initramfs:
sudo dracut -f -v
Only if you're creating an image for a remote NVMe disk controller, configure and verify NVMe boot support. Otherwise, skip this step.
Make the NVMe drivers persistent in future initramfs images by creating a dracut configuration file, and then rebuild the current initramfs:
sudo tee /etc/dracut.conf.d/azure-nvme.conf > /dev/null <<EOF
add_drivers+=" nvme nvme_core "
EOF
sudo dracut --force --verbose
Add the Azure NVMe I/O timeout to the kernel command line so the setting persists across reboots:
sudo grubby --update-kernel=ALL --args="nvme_core.io_timeout=240"
Confirm that both NVMe modules exist in the kernel's module tree so they can be packaged into initramfs. This check reads the on-disk module files, so it succeeds even on a SCSI preparation VM and doesn't imply that NVMe is loaded or in use:
modinfo nvme
modinfo nvme_core
Each command must return module details. If either module isn't found, install or enable it by following the Red Hat documentation before you continue.
Confirm that the rebuilt initramfs package includes both NVMe drivers. This check confirms the image is ready:
sudo lsinitrd /boot/initramfs-$(uname -r).img | grep -E 'nvme(_core)?\.ko'
The output must list both the nvme and nvme_core drivers. After the image boots on an NVMe VM, verify the runtime timeout by running cat /sys/module/nvme_core/parameters/io_timeout; the expected value is 240.
For both SCSI and NVMe images, use file-system UUIDs or another persistent identifier in /etc/fstab. Don't use /dev/sd* or /dev/nvme* device names, because device names can change across reboots or when the disk controller changes.
List the block devices and their persistent identifiers so that you can compare them with the entries in /etc/fstab:
sudo blkid
Check /etc/fstab for syntax errors, invalid mount options, and references that can't be resolved:
sudo findmnt --verify --verbose
Ensure that the SSH server is installed and configured to start at boot time. This setting is usually the default. Modify /etc/ssh/sshd_config to include the following line:
ClientAliveInterval 180
The WALinuxAgent package, WALinuxAgent-<version>, is not located in the Red Hat extras repository. Enable the extras repository:
sudo subscription-manager repos --enable=rhel-7-server-extras-rpms
Install the Azure Linux agent:
sudo yum install WALinuxAgent
sudo systemctl enable waagent.service
Install cloud-init:
Follow the steps in "Prepare a RHEL 7 VM from Hyper-V Manager," step 14, "Configure cloud-init to handle the provisioning."
Swap configuration:
- Don't create swap space on the operating system disk.
- Follow the steps in "Prepare a RHEL 7 VM from Hyper-V Manager," step 15, "Swap configuration."
If you want to unregister the subscription, run the following command:
sudo subscription-manager unregister
Deprovision by following the steps in "Prepare a RHEL 7 VM from Hyper-V Manager," step 18, "Deprovision the VM and prepare it for provisioning on Azure."
Shut down the VM and convert the VMDK file to the VHD format.
Note
There's a known bug in QEMU versions between 2.2.1 and 2.6 that results in an improperly formatted VHD. We recommend that you use either qemu-img 2.2.0 or lower, or update to 2.6 or higher. For more information, see this website.
First convert the image to raw format:
sudo qemu-img convert -f vmdk -O raw rhel-[version].vmdk rhel-[version].raw
Make sure that the size of the raw image is aligned with 1 MB. Otherwise, round up the size to align with 1 MB:
MB=$((1024*1024))
size=$(qemu-img info -f raw --output json "rhel-[version].raw" | \
gawk 'match($0, /"virtual-size": ([0-9]+),/, val) {print val[1]}')
rounded_size=$((($size/$MB + 1)*$MB))
sudo qemu-img resize rhel-[verion].raw $rounded_size
Convert the raw disk to a fixed-size VHD:
sudo qemu-img convert -f raw -o subformat=fixed -O vpc rhel-[version].raw rhel-[version].vhd
Or, with qemu version 2.6+, include the force_size option:
sudo qemu-img convert -f raw -o subformat=fixed,force_size -O vpc rhel-[version].raw rhel-[version].vhd
Ensure that the Network Manager service starts at boot time:
sudo systemctl enable NetworkManager.service
Configure the network interface to automatically start at boot and use the Dynamic Host Configuration Protocol:
sudo nmcli con mod eth0 connection.autoconnect yes ipv4.method auto
Note
When you use Accelerated Networking, the provisioned synthetic interface must be configured to be unmanaged by using a udev rule. This action prevents NetworkManager from assigning the same IP to it as the primary interface.
To apply it:
sudo tee <<EOF /etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules >/dev/null
# Accelerated Networking on Azure exposes a new SRIOV interface to the VM.
# This interface is transparently bonded to the synthetic interface,
# so NetworkManager should just ignore any SRIOV interfaces.
SUBSYSTEM=="net", DRIVERS=="hv_pci", ACTION!="remove", ENV{NM_UNMANAGED}="1"
EOF
Prevent NetworkManager from configuring the adapter added for accelerated networking.
sudo tee <<EOF /etc/NetworkManager/conf.d/99-azure-unmanaged-devices.conf > /dev/null
[keyfile]
unmanaged-devices=driver:mlx4_core,driver:mlx5_core
EOF
Register your Red Hat subscription to enable the installation of packages from the RHEL repository:
sudo subscription-manager register --auto-attach --username=XXX --password=XXX
Modify the kernel boot line in your grub configuration to include more kernel parameters for Azure and enable the serial console.
Remove current GRUB parameters:
sudo grub2-editenv unset kernelopts
Edit /etc/default/grub in a text editor, and add the following parameters:
GRUB_TIMEOUT=10
GRUB_CMDLINE_LINUX="console=tty1 console=ttyS0,115200n8 earlyprintk=ttyS0,115200 earlyprintk=ttyS0 net.ifnames=0"
GRUB_TERMINAL_OUTPUT="serial console"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"
These modifications turn off the new naming convention for NICs, ensure all console messages are sent to the first serial port and enable interaction with the console, which can assist Aure support with debugging issues.
We recommend that you also remove the following parameters:
rhgb quiet crashkernel=auto
Graphical and quiet boots aren't useful in a cloud environment where you want all the logs to be sent to the serial port. You can leave the crashkernel option configured if you want. This parameter reduces the amount of available memory in the VM by 128 MB or more, which might be a problem for smaller VM sizes.
After you're finished editing /etc/default/grub, run the following command to rebuild the grub configuration:
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
Add Hyper-V modules into initramfs.
Create /etc/dracut.conf.d/azure.conf and add content:
add_drivers+=" hv_vmbus hv_netvsc hv_storvsc "
Rebuild initramfs:
sudo dracut -f -v
Only if you're creating an image for a remote NVMe disk controller, configure and verify NVMe boot support. Otherwise, skip this step.
Make the NVMe drivers persistent in future initramfs images by creating a dracut configuration file, and then rebuild the current initramfs:
sudo tee /etc/dracut.conf.d/azure-nvme.conf > /dev/null <<EOF
add_drivers+=" nvme nvme_core "
EOF
sudo dracut --force --verbose
Add the Azure NVMe I/O timeout to the kernel command line so the setting persists across reboots:
sudo grubby --update-kernel=ALL --args="nvme_core.io_timeout=240"
Confirm that both NVMe modules exist in the kernel's module tree so they can be packaged into initramfs. This check reads the on-disk module files, so it succeeds even on a SCSI preparation VM and doesn't imply that NVMe is loaded or in use:
modinfo nvme
modinfo nvme_core
Each command must return module details. If either module isn't found, install or enable it by following the Red Hat documentation before you continue.
Confirm that both NVMe drivers are packaged into the rebuilt initramfs. This is the authoritative readiness check for the image:
sudo lsinitrd /boot/initramfs-$(uname -r).img | grep -E 'nvme(_core)?\.ko'
The output must list both the nvme and nvme_core drivers. You can verify the runtime timeout after the image boots on an NVMe VM by running cat /sys/module/nvme_core/parameters/io_timeout; the expected value is 240.
For both SCSI and NVMe images, use file-system UUIDs or another persistent identifier in /etc/fstab. Don't use /dev/sd* or /dev/nvme* device names, because device names can change across reboots or when the disk controller changes.
List the block devices and their persistent identifiers so that you can compare them with the entries in /etc/fstab:
sudo blkid
Check /etc/fstab for syntax errors, invalid mount options, and references that can't be resolved:
sudo findmnt --verify --verbose
Ensure that the SSH server is installed and configured to start at boot time, which is usually the default. Modify /etc/ssh/sshd_config to include the following line:
ClientAliveInterval 180
Install the Azure Linux agent, cloud-init, and other necessary utilities:
sudo dnf install -y WALinuxAgent cloud-init cloud-utils-growpart gdisk hyperv-daemons
sudo systemctl enable waagent.service
sudo systemctl enable cloud-init.service
Configure cloud-init to handle the provisioning:
Configure waagent for cloud-init:
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
Note
If you're migrating a specific VM and don't want to create a generalized image, set Provisioning.Agent=disabled on the /etc/waagent.conf configuration.
Configure mounts:
Note
Not required for cloud-init v23.x and newer.
sudo 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
Note
The items for each section in the /etc/cloud/cloud.cfg file must use the same level
of indentation
Example of an incorrect setting:
cloud_init_modules:
- disk_setup
- mounts
- migrators
Example of a correct setting:
cloud_init_modules:
- disk_setup
- mounts
- migrators
Configure the Azure data source:
sudo echo "Allow only Azure datasource, disable fetching network setting via IMDS"
sudo tee /etc/cloud/cloud.cfg.d/91-azure_datasource.cfg <<EOF
datasource_list: [ Azure ]
datasource:
Azure:
apply_network_config: False
EOF
If configured, remove the existing swap file:
if [[ -f /mnt/resource/swapfile ]]; then
echo "Removing swapfile" #RHEL uses a swapfile by defaul
swapoff /mnt/resource/swapfile
rm /mnt/resource/swapfile -f
fi
Configure cloud-init logging:
sudo echo "Add console log file"
sudo tee <<EOF /etc/cloud/cloud.cfg.d/05_logging.cfg > /dev/null
# 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
Swap configuration:
Don't create swap space on the operating system disk.
Previously, the Azure Linux agent was used to automatically configure swap space on the local resource disk attached to the VM after the VM is provisioned on Azure, cloud-init handles now this action. You must not use the Linux agent to format the resource disk to create the swap file. Modify the following parameters in /etc/waagent.conf appropriately:
ResourceDisk.Format=n
ResourceDisk.EnableSwap=n
Pass this code in as a cloud-init configuration every time you create a VM through custom data. We recommend this method.
Use a cloud-init directive baked into the image that does this step every time the VM is created.
sudo echo 'DefaultEnvironment="CLOUD_CFG=/etc/cloud/cloud.cfg.d/00-azure-swap.cfg"' >> /etc/systemd/system.conf
sudo tee /etc/cloud/cloud.cfg.d/00-azure-swap.cfg <<EOF
#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.device-timeout=2,x-systemd.requires=cloud-init.service", "0", "0"]
EOF
Configure cloud-init telemetry to assist with troubleshooting for provisioning issues:
sudo tee -a /etc/cloud/cloud.cfg.d/10-azure-kvp.cfg <<EOF
# This config enables cloud-init to report provisioning telemetry to aid with troubleshooting
Reporting:
logging:
type: log
telemetry:
type: hyperv
EOF
If you want to unregister the subscription, run the following command:
sudo subscription-manager unregister
Run the following commands to deprovision the VM and prepare it for provisioning on Azure:
sudo cloud-init clean --logs --seed
sudo waagent -force -deprovision+user
sudo rm -f ~/.bash_history
sudo sudo rm -f /var/log/waagent.log
sudo export HISTSIZE=0
Caution
If you're migrating a specific VM and don't want to create a generalized image, skip the deprovision step. Running the command waagent -force -deprovision+user renders the source machine unusable. This step is intended only to create a generalized image.
Make sure that the size of the raw image is aligned with 1 MB. Otherwise, round up the size to align with 1 MB:
First convert the image to raw format:
sudo qemu-img convert -f qcow2 -O raw rhel-[version].qcow2 rhel-[version].raw
Make sure that the size of the raw image is aligned with 1 MB. Otherwise, round up the size to align with 1 MB:
MB=$((1024*1024))
size=$(qemu-img info -f raw --output json "rhel-[version].raw" | \
gawk 'match($0, /"virtual-size": ([0-9]+),/, val) {print val[1]}')
rounded_size=$((($size/$MB + 1)*$MB))
sudo qemu-img resize rhel-[version].raw $rounded_size
or using jquery:
MB=$((1024*1024))
size=$(qemu-img info -f raw --output json "rhel-[version].raw" | jq '."virtual-size"')
rounded_size=$((($size/$MB + 1)*$MB))
sudo qemu-img resize -f raw rhel-[version].raw $rounded_size
Convert the raw disk to a fixed-size VHD:
sudo qemu-img convert -f raw -o subformat=fixed -O vpc rhel-[verion].raw rhel-[version].vhd
Or, with qemu version 1.6+, include the force_size option:
sudo qemu-img convert -f raw -o subformat=fixed,force_size -O vpc rhel-[version].raw rhel-[version].vhd
This section shows you how to prepare RHEL 7 from an ISO by using a kickstart file.
Create a kickstart file that includes the following content and save the file. For information about kickstart installation, see the Kickstart Installation Guide.
# Kickstart for provisioning a Azure VM
# System authorization information
auth --enableshadow --passalgo=sha512
# Use graphical install
text
# Don't run the Setup Agent on first boot
firstboot --disable
# Keyboard layouts
keyboard --vckeymap=us --xlayouts='us'
# System language
lang en_US.UTF-8
# Network information
network --bootproto=dhcp
# Root password
rootpw --plaintext "to_be_disabled"
# System services
services --enabled="sshd,waagent,NetworkManager"
# System timezone
timezone Etc/UTC --isUtc --ntpservers 0.rhel.pool.ntp.org,1.rhel.pool.ntp.org,2.rhel.pool.ntp.org,3.rhel.pool.ntp.org
# Partition clearing information
clearpart --all --initlabel
# Clear the MBR
zerombr
# Disk partitioning information
part /boot --fstype="xfs" --size=500
part / --fstyp="xfs" --size=1 --grow --asprimary
# System bootloader configuration
bootloader --location=mbr
# Firewall configuration
firewall --disabled
# Enable SELinux
selinux --enforcing
# Don't configure X
skipx
# Power down the machine after install
poweroff
%packages
@base
@console-internet
chrony
sudo
parted
-dracut-config-rescue
%end
%post --log=/var/log/anaconda/post-install.log
#!/bin/bash
# Register Red Hat Subscription
subscription-manager register --username=XXX --password=XXX --auto-attach --force
# Install latest repo update
yum update -y
# Enable extras repo
subscription-manager repos --enable=rhel-7-server-extras-rpms
# Install WALinuxAgent
yum install -y WALinuxAgent
# Unregister Red Hat subscription
subscription-manager unregister
# Enable waaagent at boot-up
systemctl enable waagent
# Install cloud-init
yum install -y cloud-init cloud-utils-growpart gdisk hyperv-daemons
# Configure waagent for cloud-init
sed -i 's/Provisioning.Agent=auto/Provisioning.Agent=cloud-init/g' /etc/waagent.conf
sed -i 's/ResourceDisk.Format=y/ResourceDisk.Format=n/g' /etc/waagent.conf
sed -i 's/ResourceDisk.EnableSwap=y/ResourceDisk.EnableSwap=n/g' /etc/waagent.conf
echo "Adding mounts and disk_setup to init stage"
sed -i '/ - mounts/d' /etc/cloud/cloud.cfg
sed -i '/ - disk_setup/d' /etc/cloud/cloud.cfg
sed -i '/cloud_init_modules/a\\ - mounts' /etc/cloud/cloud.cfg
sed -i '/cloud_init_modules/a\\ - disk_setup' /etc/cloud/cloud.cfg
# Disable the root account
usermod root -p '!!'
# Configure swap using cloud-init
echo 'DefaultEnvironment="CLOUD_CFG=/etc/cloud/cloud.cfg.d/00-azure-swap.cfg"' >> /etc/systemd/system.conf
sudo tee /etc/cloud/cloud.cfg.d/00-azure-swap.cfg <<EOF
#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"]
- ["ephemeral0.2", "none", "swap", "sw,nofail,x-systemd.device-timeout=2,x-systemd.requires=cloud-init.service", "0", "0"]
EOF
# Set the cmdline
sed -i 's/^\(GRUB_CMDLINE_LINUX\)=".*"$/\1="console=tty1 console=ttyS0 earlyprintk=ttyS0"/g' /etc/default/grub
# Enable SSH keepalive
sed -i 's/^#\(ClientAliveInterval\).*$/\1 180/g' /etc/ssh/sshd_config
# Build the grub cfg
grub2-mkconfig -o /boot/grub2/grub.cfg
# Configure network
tee <<EOF /etc/sysconfig/network-scripts/ifcfg-eth0 > /dev/null
DEVICE=eth0
ONBOOT=yes
BOOTPROTO=dhcp
TYPE=Ethernet
USERCTL=no
PEERDNS=yes
IPV6INIT=no
PERSISTENT_DHCLIENT=yes
NM_CONTROLLED=yes
EOF
sudo tee <<EOF /etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules >/dev/null
# Accelerated Networking on Azure exposes a new SRIOV interface to the VM.
# This interface is transparently bonded to the synthetic interface,
# so NetworkManager should just ignore any SRIOV interfaces.
SUBSYSTEM=="net", DRIVERS=="hv_pci", ACTION!="remove", ENV{NM_UNMANAGED}="1"
EOF
# Deprovision and prepare for Azure if you are creating a generalized image
sudo cloud-init clean --logs --seed
sudo rm -rf /var/lib/cloud/
sudo rm -rf /var/lib/waagent/
sudo rm -f /var/log/waagent.log
sudo waagent -force -deprovision+user
rm -f ~/.bash_history
export HISTSIZE=0
%end
Only if you're creating an image for a remote NVMe disk controller, modify the kickstart file to add the Azure NVMe I/O timeout and make the NVMe drivers available in the initial RAM disk. In the %post section, after the grub2-mkconfig -o /boot/grub2/grub.cfg command, add the following lines:
# Add the Azure NVMe I/O timeout to the kernel command line, then rebuild GRUB.
sed -i '/^GRUB_CMDLINE_LINUX=/ s/"$/ nvme_core.io_timeout=240"/' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
# Add the NVMe drivers to the initial RAM disk for remote NVMe boot.
mkdir -p /etc/dracut.conf.d
printf '%s\n' 'add_drivers+=" nvme nvme_core "' > /etc/dracut.conf.d/azure-nvme.conf
dracut --force --verbose
Place the kickstart file where the installation system can access it.
In Hyper-V Manager, create a new VM. On the Connect Virtual Hard Disk page, select Attach a virtual hard disk later, and complete the New Virtual Machine wizard.
Open the VM settings:
Attach a new VHD to the VM. Make sure to select VHD Format and Fixed Size.
Attach the installation ISO to the DVD drive.
Set the BIOS to boot from CD.
Start the VM. When the installation guide appears, select the Tab key to configure the boot options.
Enter inst.ks=<the location of the kickstart file> at the end of the boot options, and select the Enter key.
Wait for the installation to finish, then VM shuts down automatically. Your Linux VHD is now ready to be uploaded to Azure.
This section shows you how to prepare RHEL (8 OR 9) from an ISO by using a kickstart file.
Create a kickstart file that includes the following content and save the file. For information about kickstart installation, see the Kickstart Installation Guide.
# Kickstart for provisioning a Azure VM
# System authorization information
auth --enableshadow --passalgo=sha512
# Use graphical install
text
# Don't run the Setup Agent on first boot
firstboot --disable
# Keyboard layouts
keyboard --vckeymap=us --xlayouts='us'
# System language
lang en_US.UTF-8
# Network information
network --bootproto=dhcp
# Root password
rootpw --plaintext "to_be_disabled"
# System services
services --enabled="sshd,waagent,NetworkManager"
# System timezone
timezone Etc/UTC --isUtc --ntpservers 0.rhel.pool.ntp.org,1.rhel.pool.ntp.org,2.rhel.pool.ntp.org,3.rhel.pool.ntp.org
# Partition clearing information
clearpart --all --initlabel
# Clear the MBR
zerombr
# Disk partitioning information
part /boot --fstype="xfs" --size=500
part / --fstyp="xfs" --size=1 --grow --asprimary
# System bootloader configuration
bootloader --location=mbr
# Firewall configuration
firewall --disabled
# Enable SELinux
selinux --enforcing
# Don't configure X
skipx
# Power down the machine after install
poweroff
%packages
@base
@console-internet
chrony
sudo
parted
-dracut-config-rescue
%end
%post --log=/var/log/anaconda/post-install.log
#!/bin/bash
# Register Red Hat Subscription
subscription-manager register --username=XXX --password=XXX --auto-attach --force
# Install latest repo update
yum update -y
# Enable extras repo
subscription-manager repos --enable=rhel-[version]-server-extras-rpms
# Install WALinuxAgent
yum install -y WALinuxAgent
# Unregister Red Hat subscription
subscription-manager unregister
# Enable waaagent at boot-up
systemctl enable waagent
# Install cloud-init
yum install -y cloud-init cloud-utils-growpart gdisk hyperv-daemons
# Configure waagent for cloud-init
sed -i 's/Provisioning.Agent=auto/Provisioning.Agent=cloud-init/g' /etc/waagent.conf
sed -i 's/ResourceDisk.Format=y/ResourceDisk.Format=n/g' /etc/waagent.conf
sed -i 's/ResourceDisk.EnableSwap=y/ResourceDisk.EnableSwap=n/g' /etc/waagent.conf
echo "Adding mounts and disk_setup to init stage"
sed -i '/ - mounts/d' /etc/cloud/cloud.cfg
sed -i '/ - disk_setup/d' /etc/cloud/cloud.cfg
sed -i '/cloud_init_modules/a\\ - mounts' /etc/cloud/cloud.cfg
sed -i '/cloud_init_modules/a\\ - disk_setup' /etc/cloud/cloud.cfg
# Disable the root account
usermod root -p '!!'
# Configure swap using cloud-init
sudo echo 'DefaultEnvironment="CLOUD_CFG=/etc/cloud/cloud.cfg.d/00-azure-swap.cfg"' >> /etc/systemd/system.conf
sudo tee /etc/cloud/cloud.cfg.d/00-azure-swap.cfg <<EOF
#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"]
- ["ephemeral0.2", "none", "swap", "sw,nofail,x-systemd.device-timeout=2,x-systemd.requires=cloud-init.service", "0", "0"]
EOF
# Configure `cloud-init` telemetry to assist with troubleshooting for provisioning issues:
sudo tee /etc/cloud/cloud.cfg.d/10-azure-kvp.cfg <<EOF
# This config enables cloud-init to report provisioning telemetry to aid with troubleshooting
Reporting:
logging:
type: log
telemetry:
type: hyperv
EOF
# Set the cmdline
sed -i 's/^\(GRUB_CMDLINE_LINUX\)=".*"$/\1="console=tty1 console=ttyS0 earlyprintk=ttyS0"/g' /etc/default/grub
# Enable SSH keepalive
sed -i 's/^#\(ClientAliveInterval\).*$/\1 180/g' /etc/ssh/sshd_config
# Build the grub cfg
grub2-mkconfig -o /boot/grub2/grub.cfg
# Configure network
tee <<EOF /etc/sysconfig/network-scripts/ifcfg-eth0 > /dev/null
DEVICE=eth0
ONBOOT=yes
BOOTPROTO=dhcp
TYPE=Ethernet
USERCTL=no
PEERDNS=yes
IPV6INIT=no
PERSISTENT_DHCLIENT=yes
NM_CONTROLLED=yes
EOF
sudo tee <<EOF /etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules >/dev/null
# Accelerated Networking on Azure exposes a new SRIOV interface to the VM.
# This interface is transparently bonded to the synthetic interface,
# so NetworkManager should just ignore any SRIOV interfaces.
SUBSYSTEM=="net", DRIVERS=="hv_pci", ACTION!="remove", ENV{NM_UNMANAGED}="1"
EOF
sudo tee <<EOF /etc/NetworkManager/conf.d/99-azure-unmanaged-devices.conf > /dev/null
[keyfile]
unmanaged-devices=driver:mlx4_core,driver:mlx5_core
EOF
# Deprovision and prepare for Azure if you are creating a generalized image
sudo cloud-init clean --logs --seed
sudo rm -rf /var/lib/cloud/
sudo rm -rf /var/lib/waagent/
sudo rm -f /var/log/waagent.log
sudo waagent -force -deprovision+user
rm -f ~/.bash_history
export HISTSIZE=0
%end
Only if you're creating an image for a remote NVMe disk controller, modify the kickstart file to add the Azure NVMe I/O timeout and make the NVMe drivers available in the initial RAM disk. In the %post section, after the grub2-mkconfig -o /boot/grub2/grub.cfg command, add the following lines:
# Add the Azure NVMe I/O timeout to the kernel command line, then rebuild GRUB.
sed -i '/^GRUB_CMDLINE_LINUX=/ s/"$/ nvme_core.io_timeout=240"/' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
# Add the NVMe drivers to the initial RAM disk for remote NVMe boot.
mkdir -p /etc/dracut.conf.d
printf '%s\n' 'add_drivers+=" nvme nvme_core "' > /etc/dracut.conf.d/azure-nvme.conf
dracut --force --verbose
Place the kickstart file where the installation system can access it.
In Hyper-V Manager, create a new VM. On the Connect Virtual Hard Disk page, select Attach a virtual hard disk later, and complete the New Virtual Machine wizard.
Open the VM settings:
Attach a new VHD to the VM. Make sure to select VHD Format and Fixed Size.
Attach the installation ISO to the DVD drive.
Set the BIOS to boot from CD.
Start the VM. When the installation guide appears, select the Tab key to configure the boot options.
Enter inst.ks=<the location of the kickstart file> at the end of the boot options, and select the Enter key.
Wait for the installation to finish, then VM shuts down automatically. Your Linux VHD is now ready to be uploaded to Azure.
Known issues
The following issue is known.
The Hyper-V driver couldn't be included in the initial RAM disk when using a non-Hyper-V hypervisor
In some cases, Linux installers might not include the drivers for Hyper-V in the initial RAM disk (initrd or initramfs) unless Linux detects that it's running in a Hyper-V environment.
When you're using a different virtualization system (for example, VirtualBox or Xen) to prepare your Linux image, you might need to rebuild initrd to ensure that at least the hv_vmbus and hv_storvsc kernel modules are available on the initial RAM disk. This issue is known, at least on systems that are based on the upstream Red Hat distribution.
To resolve this issue, add Hyper-V modules to initramfs and rebuild it:
Edit /etc/dracut.conf, and add the following content:
add_drivers+=" hv_vmbus hv_netvsc hv_storvsc "
Rebuild initramfs:
sudo dracut -f -v
For more information, see Rebuilding initramfs.
Related content