五、KubeVirt存储
KubeVirt提供了很多种存储方式,存储就决定了你使用虚拟机镜像到底什么内核、什么版本,三种比较常用的形式虚拟机镜像(磁盘)是启动虚拟机必不可少的部分,目前KubeVirt中提供多种方式的虚拟机磁盘。
◎cloudInitNoCloud/cloudInitConfigDrive:用于提供 cloud-init 初始化所需要的 user-data,使用 configmap 作为数据源,此时VMI 内部将出现第二块大约为356KB的第二块硬盘
◎dataVolume:虚拟机启动流程中自动将虚拟机磁盘导入 pvc 的功能,在不使用 DataVolume 的情况下,用户必须先准备带有磁盘映像的 PVC,然后再将其分配给 VM 或 VMI。dataVolume 拉取镜像的来源可以是HTTP、PVC。
◎PersistentVolumeClaim: PVC 做为后端存储,适用于数据持久化,即在虚拟机重启或关机后数据依然存在。PV 类型可以是 block 和 filesystem,为 filesystem 时,将使用 PVC 上的 disk.img,格式为 RAW 格式的文件作为硬盘。block 模式时,使用 block volume 直接作为原始块设备提供给虚拟机。缺点在于仅支持RAW格式镜像,若镜像较大CDI 导入镜像会比较慢(如果是QCW2 CDI 内部机制qemu.go 会将其进行格式转换为RAW并导入PVC中),因此降低快速创建 VMI 体验感
◎ephemeral、containerDisk: 数据是无法持久化,故在存储选型上,我们采用 CEPH 作为后端存储,通过调用Ceph CSI 插件创建 PVC 卷方式管理虚机磁盘设备。Ceph CSI 插件实现了容器存储编排与Ceph集群交互的接口,它可以为容器应用分配 存储集群中的存储空间,同时在选择 Ceph-CSI 版本需要考虑到当前 K8S 版本、及 CEPH 版本号
◎registryDisk
定义image来创建虚拟机的root disk。 virt-controller会在pod定义中创建registryVolume的container,container中的entry服务负责 将spec.volumes.registryDisk.image转化为qcow2格式,路径为pod根目录。
创建默认存储
我们知道创建PV,PVC有两种方法,一个是静态,一个是动态
静态就是手动编写PV,PVC资源清单,然后再将他们进行绑定
动态就是创建一个存储类:StorageClass,然后我们只需要编写PVC的资源清单即可,它会给我们自动创建PV,然后和PVC进行绑定
5.1部署NFS服务(动态)
# 在每个机器。 yum install -y nfs-utils # 在master 执行以下命令 echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports # 执行以下命令,启动 nfs 服务;创建共享目录 mkdir -p /nfs/data # 在master执行 systemctl enable rpcbind systemctl enable nfs-server systemctl start rpcbind systemctl start nfs-server # 使配置生效 exportfs -r #检查配置是否生效 exportfs |
5.2配置存储类
只需修改两部分:就是把NFS服务端的IP修改为自己的即可
## 创建了一个存储类 apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-storage annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: k8s-sigs.io/nfs-subdir-external-provisioner parameters: archiveOnDelete: "true" ## 删除pv的时候,pv的内容是否要备份 --- apiVersion: apps/v1 kind: Deployment metadata: name: nfs-client-provisioner labels: app: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: nfs-client-provisioner template: metadata: labels: app: nfs-client-provisioner spec: serviceAccountName: nfs-client-provisioner containers: - name: nfs-client-provisioner image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/nfs-subdir-external-provisioner:v4.0.2 # resources: # limits: # cpu: 10m # requests: # cpu: 10m volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: k8s-sigs.io/nfs-subdir-external-provisioner - name: NFS_SERVER value: 172.31.0.4 ## 指定自己nfs服务器地址 - name: NFS_PATH value: /nfs/data ## nfs服务器共享的目录 volumes: - name: nfs-client-root nfs: server: 172.31.0.4 path: /nfs/data --- apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nfs-client-provisioner-runner rules: - apiGroups: [""] resources: ["nodes"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["get", "list", "watch", "create", "delete"] - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "list", "watch", "update"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["create", "update", "patch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: run-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default roleRef: kind: ClusterRole name: nfs-client-provisioner-runner apiGroup: rbac.authorization.k8s.io --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default rules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get", "list", "watch", "create", "update", "patch"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default subjects: - kind: ServiceAccount name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io |
5.3应用,并查看StorageClass(sc)
上传镜像
KubeVirt 可以使用 PVC 作为后端磁盘,使用 filesystem 类型的 PVC 时,默认使用的时 /disk.img 这个镜像,用户可以将镜像上传到 PVC,在创建 VMI 时使用此 PVC。使用这种方式需要注意下面几点:
一个 PVC 只允许存在一个镜像,只允许一个 VMI 使用,要创建多个 VMI,需要上传多次
/disk.img 的格式必须是 RAW 格式
CDI 提供了使用使用 PVC 作为虚拟机磁盘的方案,在虚拟机启动前通过下面方式填充 PVC:
通过 URL 导入虚拟机镜像到 PVC,URL 可以是 http 链接,s3 链接
Clone 一个已经存在的 PVC
通过 container registry 导入虚拟机磁盘到 PVC,需要结合 ContainerDisk 使用
通过客户端上传本地镜像到 PVC
通过命令行 virtctl,结合 CDI 项目,可以上传本地镜像到 PVC 上,支持的镜像格式有:
.img
.qcow2
.iso
压缩为 .tar,.gz,.xz 格式的上述镜像
kubevirt 提供了registryDIsk的基础镜像: registry-disk-v1alpha, 根据Dockerfile形式去创建虚拟机镜像,以下是window镜像demo Dockerfile
FROM kubevirt/registry-disk-v1alpha COPY Windows---server-2012-datacenter-64bit-cn-syspreped---2018-01-15.qcow2 /disk/windows2012dc.img |
这个是最终构建成镜像名:windows2012dc:lastest,最终在CRD表现形式如下所示
kind: VirtualMachineInstance ... spec: domain: devices: disks: - disk: bus: virtio name: registrydisk volumeName: registryvolume ... - name: registryvolume registryDisk: image: windows2012dc:latest |
案例:(安装windows10虚拟机)
因为目标是安装windows10虚拟机,所以需要将上面下载好的windows镜像上传到PVC:
#参数讲解
--image-path='Win10_20H2_Chinese(Simplified)_x64.iso' #指定Win镜像的路径,我们是在/root目录下,因此就这个路径即可。 --pvc-name=iso-win10 #指定PVC的名字 --pvc-size=7G #指定PVC的大小,根据操作系统镜像大小来设定,一般略大一个G就行。 --uploadproxy-url=https://10.96.237.3 #cdi-uploadproxy 的 Service IP,可以通过命令 kubectl -n cdi get svc -l cdi.kubevirt.io=cdi-uploadproxy 来查看。 |
增加hostDisk支持
KubeVirt默认没有开启对hostDisk的支持,需要手动开启,这里我们直接editKubeVirt1的ConfigMap
apiVersion: v1 data: debug.useEmulation: "true" feature-gates: HostDisk #添加这一行即可 kind: ConfigMap metadata: creationTimestamp: "2022-04-17T01:19:05Z" name: kubevirt-config namespace: kubevirt resourceVersion: "46074" uid: 56ff5d51-1ce5-4c0e-bef2-7c2d24525306 |
5.4创建虚拟机
编写vm.yaml文件
apiVersion: kubevirt.io/v1alpha3 kind: VirtualMachine metadata: name: win10 spec: running: false template: metadata: labels: kubevirt.io/domain: win10 spec: domain: cpu: cores: 4 devices: disks: - bootOrder: 1 cdrom: bus: sata name: cdromiso - disk: bus: virtio name: harddrive - cdrom: bus: sata name: virtiocontainerdisk interfaces: - masquerade: {} model: e1000 name: default machine: type: q35 resources: requests: memory: 16G networks: - name: default pod: {} volumes: - name: cdromiso persistentVolumeClaim: claimName: iso-win10 - name: harddrive hostDisk: capacity: 50Gi path: /data/disk.img type: DiskOrCreate - containerDisk: image: kubevirt/virtio-container-disk name: virtiocontainerdisk |
这里用到了 3 个 Volume:
◎cdromiso : 提供操作系统安装镜像,即上文上传镜像后生成的 PVC iso-win10。
◎harddrive : 虚拟机使用的磁盘,即操作系统就会安装在该磁盘上。这里选择 hostDisk 直接挂载到宿主机以提升性能,如果使用分布式存储则体验非常不好。
◎containerDisk : 由于 Windows 默认无法识别 raw 格式的磁盘,所以需要安装 virtio 驱动。containerDisk 可以将打包好 virtio 驱动的容器镜像挂载到虚拟机中。
关于网络部分,spec.template.spec.networks 定义了一个网络叫 default,这里表示使用 Kubernetes 默认的 CNI。spec.template.spec.domain.devices.interfaces 选择定义的网络 default,并开启 masquerade,以使用网络地址转换 (NAT) 来通过 Linux 网桥将虚拟机连接至 Pod 网络后端。
- 使用模版文件创建虚拟机
kubectl apply -f win10.yaml virtctl start win10 |
#查看VM,VMI,Pod状态
示例:创建本地存储
创建一个本地存储目录
[root@k8s-master ~]# sudo mkdir -p /mnt/local-storage [root@k8s-master ~]# sudo chmod 777 /mnt/local-storage |
创建一个 PersistentVolume (PV) 配置文件 local-pv.yaml
apiVersion: v1 kind: PersistentVolume metadata: name: local-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-storage local: path: /mnt/local-storage nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - <NODE_NAME> |
应用配置
[root@k8s-master ~]# kubectl apply -f local-pv.yaml |
创建一个 StorageClass 配置文件 local-storage-class.yaml
[root@k8s-master ~]# vi local-storage-class.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer |
应用配置
[root@k8s-master ~]# kubectl apply -f local-storage-class.yaml |
创建PVC
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc spec: accessModes: - ReadWriteOnce storageClassName: local-storage resources: requests: storage: 5Gi |
创建文件后,应用配置
[root@k8s-master ~]# kubectl apply -f pvc.yaml |
查看PV和PVC是否绑定成功
如果绑定成功,STATUS 应该显示为 Bound
[root@k8s-master ~]# kubectl get pv my-pv [root@k8s-master ~]# kubectl get pvc my-vm-pvc |
创建虚拟机并绑定PVC
创建vmq.yaml文件定义一个 KubeVirt 虚拟机,并将刚创建的 PVC 挂载为虚拟机的磁盘
[root@k8s-master ~]# vi vmq-yaml apiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: testvm spec: running: false template: spec: domain: resources: requests: memory: 1024M devices: disks: - name: containerdisk disk: bus: virtio - name: datavolumedisk1 disk: bus: virtio volumes: - name: containerdisk containerDisk: image: kubevirt/cirros-container-disk-demo:latest - name: datavolumedisk1 persistentVolumeClaim: claimName: test-pvc |
应用yaml文件并启动虚拟机,验证虚拟机的状态
[root@k8s-master ~]# kubect apply -f vmq-yaml [root@k8s-master ~]# virtctl start testvmq |
进入虚拟机控制台,查看磁盘情况
[root@k8s-master ~]# virtctl console testvm |