1. Kubevirt介绍
Kubevirt 是 Red Hat 开源的以容器方式运行虚拟机的项目,基于 Kubernetes 运行,通过使用自定义资源(CRD)和其它 Kubernetes 功能来无缝扩展现有的集群,以提供一组可用于管理虚拟机的虚拟化的 API。
2. Kubevirt关键组件
2.1 virt-api
kubevirt是以CRD形式去管理vm pod, virt-api 就是所有虚拟化操作的入口,包括常规的 CRD 更新验证以及vm start、stop,为 Kubevirt 提供 API 服务能力,比如许多自定义的 API 请求,如开机、关机、重启等操作,通过 APIService 作为 Kubernetes Apiserver 的插件,业务可以通过 Kubernetes Apiserver 直接请求到 virt-api;
2.2 virt-controller
Virt-controller会根据 vmi CRD,生成对应的 virt-lancher pod,并维护 CRD 的状态,是Kubevirt 的控制器,功能类似于 Kubernetes 的 controller-manager,管理和监控 VMI 对象及其关联的 Pod,对其状态进行更新;
2.3 virt-handler
该模块以 Daemonset 形式部署,功能类似于 Kubelet,通过 Watch 本机 VMI 和实例资源,管理本宿主机上所有虚机实例;
主要执行动作如下:
(1)使 VMI 中定义的 Spec 与相应的 libvirt (本地 socket 通信)保持同步;
(2)汇报及控制更新虚拟机状态;
(3)调用相关插件初始化节点上网络和存储资源;
(4)热迁移相关操作;
2.4 virt-launcher
(1)Kubevirt 会为每一个 VMI 对象创建一个 Pod,该 Pod 的主进程为 virt-launcher,virt-launcher 的 Pod 提供了 cgroups 和 namespaces 的隔离,virt-launcher 为虚拟机实例的主进程。
(2)virt-handler 通过将 VMI 的 CRD 对象传递给 virt-launcher 来通知 virt-launcher 启动 VMI。然后,virt-launcher 在其容器中使用本地 libvirtd 实例来启动 VMI。virt-launcher 托管 VMI 进程,并在 VMI 退出后终止。
(3)如果 Kubernetes 运行时在 VMI 退出之前尝试关闭 virt-launcher 容器,virt-launcher 会将信号从Kubernetes 转发到 VMI 进程,并尝试推迟容器的终止,直到 VMI 成功关闭。
2.5其他
2.5.1 libvirtd
libvirtd的一个实例存在于每个VMI pod中。virt-launcher使用libvirtd来管理VMI进程的生命周期。
2.5.2 virtctl
virctl 是kubevirt自带类似kubectl命令,它是越过virt-lancher pod这层去直接管理vm,可以控制 vm 的start、stop、restart。
3.资源对象
3.1 VirtualMachine(VM)
该结果为集群内的VirtualMachineInstance提供管理功能,例如开机、关机、重启虚拟机,确保虚拟机实例的启动状态,与虚拟机实例是1:1的关系,类似与spec.replica为1的StatefulSet。(虚拟机设置)
3.2 VirtualMachineInstance(VMI)
VMI类似于kubernetes Pod,是管理虚拟机的最小资源。一个VirtualMachineInstance对象即表示一台正在运行的虚拟机实例,包含一个虚拟机所需要的各种配置。(vm的实例化)
3.3 VirtualMachineInstanceReplicaSet
此结构类似ReplicaSet,可以启动指定数量的VirtualMachineInstance,并且保证指定数量的VirtualMachineInstance运行。
3.4 VirtualMachineInstanceMigrations
此结构是虚拟机迁移需要的资源,一个资源对象表示为一次迁移任务,并反映出虚拟机迁移的状态。
4. VM构建流程
(1)执行 kubectl apply -f vm.yaml;
(2)k8s api-server 收到请求后会创建 vm 对象;
(3)virt-controller 监控到有新的 vm 对象,调用 k8s api-server,去创建对应的 vmi 对象;
(4)virt-controller 监控到有新的 vmi 对象,调用 k8s api-server去创建对应的 virt-launcher-xxx Pod 对象;
(5)Pod 的创建首先通过 kube-scheduler 选一台合适的 Node,再创建对应 Pod;(6)启动 virt-launcher 进程监听来自 virt-handler 的消息,对应更新 vmi 的 nodeName 字段
(7)节点上的 virt-handler 通过 Informer 监听到有新 vmi 创建到自己的节点后,发送 gRPC 消息把该 vmi 的各项配置发给 virt-launcher 进程,启动虚拟机;
(8)virt-launcher 接收到 vmi 的配置后转成虚拟机的 xml 文件,启动该虚拟机
(9)虚拟启动之后,更新 vmi 的虚拟机状态
5. 磁盘和卷
5.1 PersistentVolumeClaim
该结构使用 PVC 做为存储,适用于数据持久化,即在虚拟机重启或者重建后数据依旧存在。使用的 PV 类型可以是 block 和 filesystem,使用 filesystem 时,会使用 PVC 上的 /disk.img,格式为 RAW 格式的文件作为硬盘。block 模式时,使用 block volume 直接作为原始块设备提供给虚拟机。
5.2 ephemeral
基于后端存储在本地做一个写时复制(COW)镜像层,所有的写入都在本地存储的镜像中,VM 实例停止时写入层就被删除,后端存储上的镜像不变化。
5.3 containerDisk
基于 scratch 构建的一个 docker image,镜像中包含虚拟机启动所需要的虚拟机镜像,可以将该 docker image push 到 registry,使用时从 registry 拉取镜像,直接使用 containerDisk 作为 VMI 磁盘,数据是无法持久化的。
5.4 hostDisk
它使用节点上的磁盘镜像,类似于 hostpath,也可以在初始化时创建空的镜像。
5.5 dataVolume
提供了在虚拟机启动流程中自动将虚拟机磁盘导入 pvc 的功能,在不使用 DataVolume 的情况下,用户必须先准备带有磁盘映像的 pvc,然后再将其分配给 VM 或 VMI。
6. 安装Kubevirt前提条件
(1)k8s或K3s或Minikube(未能成功构建vm内核版本过低导致)
(2)QEMU与KVM(服务器能够支持虚拟化)
(3)Libvirt(virt-launcher调用)
7. Kubevirt通过源码更新
步骤一: 通过docker images进行镜像创建,生成pod,以virt-api为例,从github仓库上下载压缩包;
步骤二: 设置代理
go env -w GOPROXY=goproxy.cn,direct
编译代码
go build ../virt-api/virt-api.go
编译完成后,会在项目根目录下生成一个 virt-api 的可执行文件
步骤三: 制作镜像(正常通过Dockerfile制作docker镜像)
FROM ../virt-api:0.40.0-1
COPY virt-api /usr/bin/virt-api
把上面编译好的 virt-api 也放到与 Dockerfile 同级目录,执行如下命令
docker build -t ../virt-api:自定义版本号
执行完成后,docker images 就会看到刚刚制作的 0.40.0-2 的镜像。
步骤四: 然后使用如下命令,更新 virt-api pod 所使用的 image,对应的 Pod 就会自动重启。
$kubectl patch pod virt-api-775dd874f5-rjclv -n kubevirt -p '{"spec":{"containers":[{"name":"virt-api","image":"../virt-api:0.40.0-2"}]}}'
替换为对应版本的image。
8. Docker网络原理
也能够自定义类似Docker0的网络mynet(自定义)
veth-pair 就是一对的虚拟设备接口,成对出现的。一端连着协议栈,一端彼此相连,每启动一个docker容器,docker就会给docker容器分配一个ip,并会生成一对(2个)网卡,安装docker容器时就会安装一个(docker0)docker专用的网卡,启动后容器里面有一个网卡,宿主机也存在一个网卡用来专门绑定容器用,一对一对出现的。
9. 磁盘存储(通过CDI组件进行管理,提供动态创建PVC并将数据导入PVC的工作流)
虚拟机中spec.domain.devices.disks元素有一个强制的name,此外,磁盘的name必须引用其中的一卷spec.volumes.
9.1 磁盘访问类型
四种不同的类型来访问磁盘lun、disk、cdrom与floppy (被弃用),除了floppy,允许您指定bus属性。这bus属性确定磁盘将如何呈现给客户操作系统。floppy磁盘不支持bus属性:它们总是附加到fdc。
指定bus类型bus:virtio
9.1.1 lun
该磁盘方案会将卷作为LUN设备向虚拟机公开。这使得虚拟机可以执行任意iSCSI命令。
metadate:
name: testvmi-lun
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachineInstance
spec:
domain:
resources
requests:
memory: 64M
devices:
disks:
- name: mypvcdisk
# This makes it a lun device
lun: {}
volumes:
- name: mypvcdisk
persistentVolumeClaim:
claimNae: mypvc
9.1.2 Disk
该磁盘方案会将卷作为普通磁盘公开给虚拟机。
9.1.3 Cdrom
该磁盘方案将卷作为cdrom驱动器向虚拟机公开。默认情况下,它是只读的。
9.2 Volumes类型
9.2.1 cloudInitNoCloud
允许附加cloudInitNoCloud虚拟机的数据源。如果VM包含正确的cloud-init设置,它将把磁盘作为用户数据源。
metadata:
name: testvmi-cloudinitnocloud
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachineInstance
spec:
domain:
resources:
requests:
memory: 64M
devices:
disks:
- name: mybootdisk
lun: {}
- name: mynoclouddisk
disk: {}
volumes:
- name: mybootdisk
persistentVolumeClaim:
claimName: mypvc
- name: mynoclouddisk
cloudInitNoCloud:
secretRef:
name: testsecret
9.2.2 cloudInitConfigDrive
允许附加cloudInitConfigDrive虚拟机的数据源。如果VM包含正确的cloud-init设置,它将把磁盘作为用户数据源。
9.2.3 persistentVolumeClaim(主要)
当虚拟机终止后,虚拟机实例的磁盘需要保留时,这允许虚拟机的数据在重启之间保持持久。
9.2.4 ephemeral
临时卷是使用网络卷作为只读后备存储的本地COW(写入时复制)映像。有了临时卷,网络后备存储器就永远不会发生变化。相反,所有写入都存储在本地存储中的临时映像上。KubeVirt在虚拟机启动时动态生成与虚拟机相关的临时映像,并在虚拟机停止时丢弃这些临时映像。当VM达到最终状态(例如,成功、失败)时,COW映像被丢弃。目前,只有PersistentVolumeClaim可以用作临时卷的后备存储器。
metadata:
name: testvmi-ephemeral-pvc
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachineInstance
spec:
domain:
resources:
requests:
memory: 64M
devices:
disks:
- name: mybvcdisk
lun: {}
volumes:
- name: mybvcdisk
ephemeral:
persistentVolumeClaim:
claimName: mypvc
9.2.5 containerDisk
最初是registryDisk,该特性提供了在容器映像注册表中存储和分发虚拟机磁盘的能力。containerDisks可以在VirtualMachineInstance规范的磁盘部分分配给虚拟机。
containerDisks是临时存储设备,可以分配给任意数量的活动VirtualMachineInstances。这使得它们成为想要复制大量不需要持久数据的虚拟机工作负载的用户的理想工具。containerDisks通常与VirtualMachineInstanceReplicaSets一起使用。
但是containerDisks对于任何需要跨虚拟机重启的持久化根磁盘的工作负载,不是一个好的解决方案。
(1) containerDisk工作流示例
containerdisks能够并且应该基于scratch
将本地VirtualMachineInstance磁盘注入容器映像。
cat << END> Dockerfile
FROM scratch
ADD --chown=107:107 fedora25.qcow2 /disk/
END
docker build -t vmidisks/fedora25:latest .
将远程VirtualMachineInstance磁盘插入容器映像。
cat << END> Dockerfile
FROM scratch
ADD --chown=107:107 cloud.centos.org/centos/7/images/Centos-7-x86_64-GenericCloud.qcow2 /disk/
END
docker push vmidisks/fedora25:latest .
将容器磁盘作为临时磁盘连接到虚拟机。
metadata:
name: testvmi-containerdisk
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachineInstance
spec:
domain:
resources:
requests:
memory: 64M
devices:
disks:
- name: containerdisk
lun: {}
volumes:
- name: containerdisk
containerdisk:
image: vmidisks/fedora25:latest
注意containerDisk是基于文件的,因此不能作为lun虚拟机的设备。
自定义磁盘映像路径:ContainerDisk还允许在需要时将磁盘映像存储在任何文件夹中。
构建容器磁盘映像:
cat << END> Dockerfile
FROM scratch
ADD fedora25.qcow2 /custom-disk-path/fedora25.qcow2
END
docker build -t vmidisks/fedora25:latest .
docker push vmidisks/fedora25:latest .
使用指向自定义位置的容器磁盘创建VMI:
metadata:
name: testvmi-containerdisk
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachineInstance
spec:
domain:
resources:
requests:
memory: 64M
devices:
disks:
- name: containerdisk
disk: {}
volumes:
- name: containerdisk
containerdisk:
image: vmidisks/fedora25:latest
path: /custom-disk-path/fedora25.qcow2
9.2.6 emptyDisk
工作方式类似于在Kubenetes中的emptyDir。分配额外的一部分的qcow2磁盘,并且将与虚拟机的寿命一样长。因此,它将在客户端虚拟机重新启动后存活,但不能在虚拟机重新创建后存活。磁盘capacity需要指定大小。
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachineInstance
metadata:
name: testvmi-nocloud
spec:
terminationGracePeriodSeconds: 5
domain:
resources:
requests:
memory: 64M
devices:
disks:
- name: containerdisk
disk:
bus: virtio
volumes:
- name: containerdisk
containerdisk:
image: kubvirt/cirros-registry-disk-dome:latest
- name: emptydisk
emptyDisk:
capacity: 2Gi
短暂的虚拟机通常带有只读根映像和有限的tmpfs空间。在许多情况下,这不足以安装应用程序依赖项并为应用程序数据提供足够的磁盘空间。虽然这些数据并不重要,因此可能会丢失,但在应用程序的生命周期中,仍然需要这些数据来正常运行,空磁盘经常被使用并安装在/var/lib或者/var/run.
9.2.7 hostDisk
hostDisk卷类型提供了创建或使用位于节点某处的磁盘映像的能力。它的工作方式类似于hostPath在Kubernetes中,提供了两种使用类型:DiskOrCreate如果给定位置不存在磁盘映像,则创建一个;Disk磁盘映像必须存在于给定的位置。
注意:您需要启用主机磁盘。
创建位于/data/disk.img的1Gi磁盘映像,并将其附加到虚拟机
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachineInstance
metadata:
labels:
special: vmi-host-disk
name: vmi-host-disk
spec:
terminationGracePeriodSeconds: 0
domain:
resources:
requests:
memory: 64M
devices:
disks:
- name: host-disk
disk:
bus: virtio
mechine:
type: ""
volumes:
- hostDisk:
capacity: 1Gi
path: /data/disk.img
name: host-disk
status: {}
10. VM网络和接口(通过安装calico与flannel进行管理)
Kubernetes网络(Kubernetes CNI负责配置),libvirt网络,虚拟机网络
将虚拟机连接到网络包括两个部分。首先,网络在spec.networks。然后,通过在中指定网络支持的接口,将它们添加到虚拟机中spec.domain.devices.interfaces.
10.1 网络类型
类型 |
描述 |
Pod |
默认Kubernetes网络 |
multus |
使用Multus提供二级网络 |
10.1.1 pod
网络代表每个pod中的集群网络解决方案配置的接口是通过默认的eth0。
kind: vm
spec:
domain:
devices:
interfaces:
- name: default
masquerade: {}
networks:
- name: default
pod: {} # Stock pod network
10.1.2 Multus
Multus CNI是Kubernetes的一个容器网络接口(CNI)插件,可以将多个网络接口附加到pod上。通常,在Kubernetes中,每个pod只有一个网络接口(除了一个环回接口)——使用Multus,您可以创建一个具有多个接口的多宿主pod。这是通过Multus充当“元插件”来实现的,元插件是一个CNI插件,可以调用多个其他CNI插件。
注意:multus默认网络和pod网络类型是互斥的。virt-launcher pod启动VMI时不使用pod网络的配置。选用默认的multus委托必须至少返回一个IP地址。
10.2 接口
网络接口在中配置spec.domain.devices.interfaces。他们将虚拟接口的属性描述为在客户实例中“可见”。同一个网络后端可以以多种不同的方式连接到虚拟机,每种方式都有自己的连接保证和特征。
类型 |
描述 |
bridge |
使用linux桥连接 |
slirp |
使用QEMU用户网络模式连接 |
sriov |
通过以下方式穿过SR-IOV PCI设备vfio |
masquerade |
使用Iptables规则连接nat流量 |
每个接口还可能有额外的配置字段,用于修改客户实例中“可见”的属性。
名字 |
格式 |
默认 |
描述 |
model |
如:e1000, e1000e, ne2k_pci, pcnet, rtl8139, virtio |
virtio |
网卡类型 |
macAddress |
ff:ff:ff:ff:ff:ff或者FF-FF-FF-FF-FF-FF |
|
在客户系统内部看到的MAC地址, 如:de:ad:00:00:be:af |
ports |
|
空 |
要转发到虚拟机的端口列表。 |
pciAddressss |
0000:81:00.1 |
|
设置网络接口PCI地址, 如:0000:81:00.1 |
kind: vm
spec:
domain:
devices:
interfaces:
- name: default
model: e1000 # expose e1000 NIC to the guest
masquerade: {} # connect through a masquerade
ports:
- name: http
port: 80
networks:
- name: default
pod: {}
注意:使用slirp接口时,只有已配置的端口会被转发到虚拟机.
名字 |
格式 |
需要 |
描述 |
name |
|
不 |
名字 |
port |
1 - 65535 |
是 |
要公开的端口 |
protocol |
TCP,UDP |
不 |
连接协议 |
如果spec.domain.devices.interfaces则虚拟机使用的默认pod网络接口进行连接bridge类型。如果您想要一个没有任何网络连接的虚拟机实例,您可以使用autoattachPodInterface字段如下:
kind: vm
spec:
domain:
devices:
autoattachPodInterface: false
10.2.1 bridge
在bridge模式下,虚拟机通过linux“桥”连接到网络后端。pod网络IPv4地址通过DHCPv4委派给虚拟机。虚拟机应配置为使用DHCP获取IPv4地址。
注意:如果虚拟机接口规范中未配置特定的MAC地址,则来自相关pod接口的MAC地址将被委派给虚拟机。
kind: vm
spec:
domain:
devices:
interfaces:
- name: red
bridge: {} #connect through a bridge
networks:
- name: red
multus:
networkName: red
这时,bridge模式不支持附加配置字段。
注意:由于IPv4地址委派,在bridge模式pod没有配置IP地址,这可能会给依赖它的第三方解决方案带来问题。例如,Istio可能无法在此模式下工作。
注意:管理员可以禁止使用bridge通过指定配置标志的pod网络接口类型。要实现这一点,管理员应该将以下选项设置为false:
apiVersion: kubevirt.io/v1alpha3
kind: Kubevirt
metadata:
name: kubevirt
namespace: kubevirt
spec:
configuration:
networks:
permitBridgeInterfaceOnPodNetwork: false
10.2.2 slirp
在slirp模式下,虚拟机使用QEMU用户网络模式连接到网络后端。在这种模式下,QEMU为虚拟机分配内部IP地址,并将它们隐藏在NAT之后。
kind: vm
spec:
domain:
devices:
interfaces:
- name: red
slirp: {} #connect using SLIRP mode
networks:
- name: red
pod: {}
这时,slirp模式不支持附加配置字段。
注意:在slirp模式下,唯一支持的协议是TCP和UDP。ICMP是不支持。
10.2.3 masquerade
在masquerade模式下,KubeVirt将内部IP地址分配给虚拟机,并将其隐藏在NAT之后。所有离开虚拟机的流量都使用pod IP地址进行“NAT”。应该将客户操作系统配置为使用DHCP来获取IPv4地址。
为了允许特定端口的流量进入虚拟机,模板ports接口的一部分应配置如下。如果ports部分丢失,所有端口都转发到虚拟机。
kind: vm
spec:
domain:
devices:
interfaces:
- name: red
masquerade: {} #connect using masquerade mode
ports:
- port: 80 # allow incoming traffic on port 80 to get into the virtual machine
networks:
- name: red
pod: {}
注意:伪装只允许连接到pod网络。