Bridging the Hypervisor Gap: A Deep Dive into Hyper-V Drivers for Linux-to-Azure Migrations

If we need to migrate a Linux VM from an on-premises environment to run in Azure, what should we pay attention to?
First, you need to understand that a VM placed on Azure is essentially located in a Hyper-V environment. Being in a Hyper-V environment inevitably requires installing the necessary Hyper-V drivers in the Linux Kernel to ensure propper operation.
Key Linux Kernel Modules on Azure
The following hv_*
drivers are core kernel modules that make up Microsoft Hyper-v Linux Integration Services (LIS). These modules are crucial for efficiently running Linux VM on the Hyper-V and Microsoft Azure.
- hv_vmbus: As the foundational communication layer, a high-speed data exchange channle between the guset VM and the Hyper-V host has been established, serving as the cornerstone for all other integration servcies.
- hv_storvsc: Specifically designed for storage, the semi-virtualized driver provides high-performance access to virtual hard disks and is key to ensuring system boot-up and smooth I/O operations.
- hv_netvsc: Optimized network drivers supporting advanced features such as Accelerated Networking, significantly reducing network latency and improving throughput.
- hv_utils: A multifunctional utility driver that integrates time synchronization, hearbeat signaling, integrated shutdown, and data exchange with the host, among other management functions.
Basically, Linux 3.3 and later versions all include these modules; it's just a matter of whether they are enabled or not.
Find out if your Linux supports Azure
You need to know whether the Linux kernel you are currently using includes hv_
. By default, unless you are directly installing the OS on Microsoft Hyper-V, the hv_*
drivers are NOT installed when you set up a Linux OS in a local environment such as bare metal, VMware ESXi. Therefore, in most cases, these drivers are not installed, but we can still check it by command line.
Using Oracle Linux as an example
- Obtain the version of the Linux Kernel you are currently using.
[root@vm-oracle8-twn ~]$ uname -r
5.15.0-307.178.5.el8uek.x86_64
307
- Using lsinitrd to perform verification
lsinitrd is a command line tool used to inspect the contents of the Linux initial RAM disk (initrd) or initial RAM filesystem (initramfs)
[root@vm-oracle8-twn ~]# lsinitrd --kver $(uname -r) |grep -i hv_
# or specify a specific version number
# [root@vm-oracle8-twn ~]# lsinitrd /boot/initramfs-5.15.0-307.178.5.el8uek.x86_64.img | grep -i hv_
-rw-r--r-- 1 root root 66476 Nov 7 2024 usr/lib/modules/5.15.0-307.178.5.el8uek.x86_64/kernel/drivers/hv/hv_vmbus.ko.xz
-rw-r--r-- 1 root root 92368 Nov 7 2024 usr/lib/modules/5.15.0-307.178.5.el8uek.x86_64/kernel/drivers/net/hyperv/hv_netvsc.ko.xz
-rw-r--r-- 1 root root 24008 Nov 7 2024 usr/lib/modules/5.15.0-307.178.5.el8uek.x86_64/kernel/drivers/scsi/hv_storvsc.ko.xz
Includes hv-related modules
[root@vm-oracle8-vmware ~]# lsinitrd --kver $(uname -r) |grep -i hv_
# or
# [root@vm-oracle8-vmware ~]# lsinitrd /boot/initramfs-$(uname -r).img | grep -i hv_
Not includes hv-related modules
Kernel Version: Differences Between Current and Next Startup
However, please note that if you have attempted a system update (dnf upgrade / apt upgrade) but haven't restarted your OS for a long time, it is possible that the kernel version used during startup is different from your current version.
uname -r
Display the currently running core version
This command will return a string similar to 5.15.0-xxx
, representing the kernel currently loaded in memory and running. However, this version may not necessarily be the on the system loads during the next reboot, especially after a system update or manual changes to boot setting. The behavior during the next boot is determined by the configuration of the GRUB bootloader. Most of the time, you only need to find the parameter at index=0
# Display the path of the default boot kernel
[root@vm-oracle8-twn ~]# grubby --default-kernel
/boot/vmlinuz-5.15.0-307.178.5.el8uek.x86_64
# Display information about all installed kernels and mark the default kernel
[root@vm-oracle8-twn ~]# grubby --info=ALL
index=0 <-----!!!!!!default boot kernel!!!!!!
kernel="/boot/vmlinuz-5.15.0-307.178.5.el8uek.x86_64"
args="ro crashkernel=auto console=tty0 console=ttyS0,115200n8 rd.lvm.vg=rootvg rd.lvm.lv=rootvg/rootlv $tuned_params"
root="/dev/mapper/rootvg-rootlv"
initrd="/boot/initramfs-5.15.0-307.178.5.el8uek.x86_64.img $tuned_initrd"
title="Oracle Linux Server (5.15.0-307.178.5.el8uek.x86_64 with Unbreakable Enterprise Kernel) 8.10"
id="d8d61b054d31499289b7bdb68ff83657-5.15.0-307.178.5.el8uek.x86_64"
index=1
kernel="/boot/vmlinuz-4.18.0-553.47.1.el8_10.x86_64"
args="ro crashkernel=auto console=tty0 console=ttyS0,115200n8 rd.lvm.vg=rootvg rd.lvm.lv=rootvg/rootlv $tuned_params"
root="/dev/mapper/rootvg-rootlv"
initrd="/boot/initramfs-4.18.0-553.47.1.el8_10.x86_64.img $tuned_initrd"
title="Oracle Linux Server (4.18.0-553.47.1.el8_10.x86_64) 8.10"
id="d8d61b054d31499289b7bdb68ff83657-4.18.0-553.47.1.el8_10.x86_64"
index=2
kernel="/boot/vmlinuz-0-rescue-d8d61b054d31499289b7bdb68ff83657"
args="ro crashkernel=auto console=tty0 console=ttyS0,115200n8 rd.lvm.vg=rootvg rd.lvm.lv=rootvg/rootlv $tuned_params"
root="/dev/mapper/rootvg-rootlv"
initrd="/boot/initramfs-0-rescue-d8d61b054d31499289b7bdb68ff83657.img $tuned_initrd"
title="Oracle Linux Server (0-rescue-d8d61b054d31499289b7bdb68ff83657 with Unbreakable Enterprise Kernel) 8.10"
id="d8d61b054d31499289b7bdb68ff83657-0-rescue"
Ensure that kernel moduls can be installed every time the Linux OS is updated

According to the docs, you need to regenerate the initramfs.
- Add the necessary drivers in /etc/dracut.conf for RHEL/SLES or /etc/initramfs-tools/modules for Ubuntu/Debian
# PUT YOUR CONFIG IN separate files
# in /etc/dracut.conf.d named "<name>.conf"
# SEE man dracut.conf(5) for options
add_drivers+=" hv_vmbus hv_netvsc hv_storvsc hv_utils "
/etc/dracut.conf
- Post install script execution after kernel update
Located in /etc/kernel/postinst.d/, it will automatically trigger after kernel installation/update
[root@vm-oracle8-twn ~]# mkdir -p /etc/kernel/postinst.d
[root@vm-oracle8-twn ~]# touch /etc/kernel/postinst.d/99-hyperv-drivers
[root@vm-oracle8-twn ~]# chmod +x /etc/kernel/postinst.d/99-hyperv-drivers
Forced driver regenrated triggered after kernel update
- Force regenrated initramfs
#!/bin/bash
LOG_FILE="/var/log/hyperv-drivers.log"
echo "[$(date)] Start regenerated initramfs (Kernel: $1)" >> $LOG_FILE
echo "$1" >> $LOG_FILE
dracut --force /boot/initramfs-"$1".img "$1" 2>&1 >> $LOG_FILE
ls -la /boot/initramfs-"$1".img >> $LOG_FILE
echo "[$(date)] Completed,Exit Code: $?" >> $LOG_FILE
/etc/kernel/postinst.d/99-hyperv-drivers
- Core Version ($1): The 1st parameter passed to the script is the version string of the newly installed kernel (formatted the same as the output of uname -r). This is the most important piece of information in the script, as all operations targeting a specific kernel version should be based on this parameter.
- Kernel Image Path ($2): The 2nd parameter is the absolute path to the new kernel image file typically /boot/vmlinuz-*
- Perform system update
[root@vm-oracle8-twn ~]# dnf upgrade
...omit...
Installed:
kernel-4.18.0-553.58.1.el8_10.x86_64
kernel-core-4.18.0-553.58.1.el8_10.x86_64
kernel-modules-4.18.0-553.58.1.el8_10.x86_64
kernel-uek-5.15.0-309.180.4.el8uek.x86_64
kernel-uek-core-5.15.0-309.180.4.el8uek.x86_64
kernel-uek-modules-5.15.0-309.180.4.el8uek.x86_64
# or force regenerate by script
# [root@vm-oracle8-twn ~]# /etc/kernel/postinst.d/99-hyperv-drivers 5.15.0-309.180.4.el8uek.x86_64
- Check log from
hyperv-drivers.log
[root@vm-oracle8-twn ~]# grubby --default-kernel
/boot/vmlinuz-5.15.0-309.180.4.el8uek.x86_64
[root@vm-oracle8-twn postinst.d]# cat /var/log/hyperv-drivers.log
[Mon Jun 30 23:29:58 EDT 2025] Start regenerated initramfs (Kernel: 5.15.0-309.180.4.el8uek.x86_64
5.15.0-309.180.4.el8uek.x86_64
-rw-------. 1 root root 96796853 Jun 30 23:31 /boot/initramfs-5.15.0-309.180.4.el8uek.x86_64.img
[Mon Jun 30 23:31:14 EDT 2025] Completed,Exit Code: 0
309