18 边缘虚拟化 #
本章介绍如何使用边缘虚拟化在边缘节点上运行虚拟机。必须指出的是,边缘虚拟化并非全面的解决方案,其功能有限;它会尝试解决需要基本虚拟机功能的轻量级虚拟化的要求。SUSE 通过 Harvester 提供更全面的虚拟化(和超融合基础架构)解决方案。
SUSE 边缘虚拟化支持两种虚拟机运行方法:
在主机级别通过 libvirt+qemu-kvm 手动部署虚拟机
部署 KubeVirt 操作器来实现基于 Kubernetes 的虚拟机管理
这两种方法都有效,但下面仅介绍第二种方法。如果您要使用 SLE Micro 现成提供的标准虚拟化机制,可在此处找到详细的指南,尽管该指南主要是针对 SUSE Linux Enterprise Server 编写的,但概念几乎相同。
本指南首先介绍如何将其他虚拟化组件部署到已预先部署的系统,然后使用一个章节介绍如何通过 Edge Image Builder 将此配置嵌入到初始部署中。如果您不想了解基础知识并想要手动完成设置,请直接跳到该章节。
18.1 KubeVirt 概述 #
KubeVirt 让您可以通过 Kubernetes 管理虚拟机及其他容器化工作负载。它通过在容器中运行 Linux 虚拟化堆栈的用户空间部分来实现此目的。这样可以最大程度地降低对主机系统的要求,从而简化设置和管理。
有关 KubeVirt 体系结构的细节,请参见上游文档。
18.2 先决条件 #
如果您要学习本指南,事先需要做好以下准备:
18.3 手动安装边缘虚拟化 #
本指南不会指导您完成 Kubernetes 的部署过程,而是假设您已安装适用于 SUSE Edge 的 K3s 或 RKE2 版本,并已相应地配置了
kubeconfig,以便能够以超级用户身份执行标准的 kubectl
命令。假设您的节点构成单节点群集,不过,此过程与多节点部署预期不会有太大的差异。
具体而言,将通过以下三个独立的 Helm chart 来部署 SUSE 边缘虚拟化:
KubeVirt:核心虚拟化组件,即 Kubernetes CRD、操作器,以及使 Kubernetes 能够部署和管理虚拟机的其他组件。
KubeVirt 仪表板扩展:可选的 Rancher UI 扩展,用于实现基本的虚拟机管理,例如启动/停止虚拟机以及访问控制台。
Containerized Data Importer (CDI):一个附加组件,可为 KubeVirt 实现永久存储集成,使虚拟机能够使用现有 Kubernetes 存储后端来存储数据,同时使用户能够导入或克隆虚拟机的数据卷。
其中的每个 Helm chart 将根据您当前使用的 SUSE Edge 版本进行版本控制。对于生产/支持的用途,请采用 SUSE 注册表中提供的项目。
首先,请确保可以正常进行 kubectl
访问:
$ kubectl get nodes
此命令应会显示如下所示的输出:
NAME STATUS ROLES AGE VERSION node1.edge.rdo.wales Ready control-plane,etcd,master 4h20m v1.28.9+rke2r1 node2.edge.rdo.wales Ready control-plane,etcd,master 4h15m v1.28.9+rke2r1 node3.edge.rdo.wales Ready control-plane,etcd,master 4h15m v1.28.9+rke2r1
现在您可以继续安装 KubeVirt 和 Containerized Data Importer (CDI) Helm chart:
$ helm install kubevirt oci://registry.suse.com/edge/kubevirt-chart --namespace kubevirt-system --create-namespace $ helm install cdi oci://registry.suse.com/edge/cdi-chart --namespace cdi-system --create-namespace
几分钟后,所有 KubeVirt 和 CDI 组件应会部署完成。您可以通过检查 kubevirt-system
和
cdi-system
名称空间中部署的所有资源进行验证。
校验 KubeVirt 资源:
$ kubectl get all -n kubevirt-system
此命令应会显示如下所示的输出:
NAME READY STATUS RESTARTS AGE pod/virt-operator-5fbcf48d58-p7xpm 1/1 Running 0 2m24s pod/virt-operator-5fbcf48d58-wnf6s 1/1 Running 0 2m24s pod/virt-handler-t594x 1/1 Running 0 93s pod/virt-controller-5f84c69884-cwjvd 1/1 Running 1 (64s ago) 93s pod/virt-controller-5f84c69884-xxw6q 1/1 Running 1 (64s ago) 93s pod/virt-api-7dfc54cf95-v8kcl 1/1 Running 1 (59s ago) 118s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubevirt-prometheus-metrics ClusterIP None <none> 443/TCP 2m1s service/virt-api ClusterIP 10.43.56.140 <none> 443/TCP 2m1s service/kubevirt-operator-webhook ClusterIP 10.43.201.121 <none> 443/TCP 2m1s service/virt-exportproxy ClusterIP 10.43.83.23 <none> 443/TCP 2m1s NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE daemonset.apps/virt-handler 1 1 1 1 1 kubernetes.io/os=linux 93s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/virt-operator 2/2 2 2 2m24s deployment.apps/virt-controller 2/2 2 2 93s deployment.apps/virt-api 1/1 1 1 118s NAME DESIRED CURRENT READY AGE replicaset.apps/virt-operator-5fbcf48d58 2 2 2 2m24s replicaset.apps/virt-controller-5f84c69884 2 2 2 93s replicaset.apps/virt-api-7dfc54cf95 1 1 1 118s NAME AGE PHASE kubevirt.kubevirt.io/kubevirt 2m24s Deployed
校验 CDI 资源:
$ kubectl get all -n cdi-system
此命令应会显示如下所示的输出:
NAME READY STATUS RESTARTS AGE pod/cdi-operator-55c74f4b86-692xb 1/1 Running 0 2m24s pod/cdi-apiserver-db465b888-62lvr 1/1 Running 0 2m21s pod/cdi-deployment-56c7d74995-mgkfn 1/1 Running 0 2m21s pod/cdi-uploadproxy-7d7b94b968-6kxc2 1/1 Running 0 2m22s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/cdi-uploadproxy ClusterIP 10.43.117.7 <none> 443/TCP 2m22s service/cdi-api ClusterIP 10.43.20.101 <none> 443/TCP 2m22s service/cdi-prometheus-metrics ClusterIP 10.43.39.153 <none> 8080/TCP 2m21s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/cdi-operator 1/1 1 1 2m24s deployment.apps/cdi-apiserver 1/1 1 1 2m22s deployment.apps/cdi-deployment 1/1 1 1 2m21s deployment.apps/cdi-uploadproxy 1/1 1 1 2m22s NAME DESIRED CURRENT READY AGE replicaset.apps/cdi-operator-55c74f4b86 1 1 1 2m24s replicaset.apps/cdi-apiserver-db465b888 1 1 1 2m21s replicaset.apps/cdi-deployment-56c7d74995 1 1 1 2m21s replicaset.apps/cdi-uploadproxy-7d7b94b968 1 1 1 2m22s
要校验是否已部署 VirtualMachine
自定义资源定义 (CRD),请使用以下命令:
$ kubectl explain virtualmachine
此命令应会列显 VirtualMachine
对象的定义,如下所示:
GROUP: kubevirt.io KIND: VirtualMachine VERSION: v1 DESCRIPTION: VirtualMachine handles the VirtualMachines that are not running or are in a stopped state The VirtualMachine contains the template to create the VirtualMachineInstance. It also mirrors the running state of the created VirtualMachineInstance in its status. (snip)
18.4 部署虚拟机 #
部署 KubeVirt 和 CDI 后,我们需要基于 openSUSE Tumbleweed 定义一个简单的虚拟机。此虚拟机采用最简单的配置,与任何其他 Pod 一样使用标准的“Pod 网络”进行网络配置。与任何没有 PVC 的容器一样,它也采用非永久存储,因此可确保存储是临时性的。
$ kubectl apply -f - <<EOF apiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: tumbleweed namespace: default spec: runStrategy: Always template: spec: domain: devices: {} machine: type: q35 memory: guest: 2Gi resources: {} volumes: - containerDisk: image: registry.opensuse.org/home/roxenham/tumbleweed-container-disk/containerfile/cloud-image:latest name: tumbleweed-containerdisk-0 - cloudInitNoCloud: userDataBase64: I2Nsb3VkLWNvbmZpZwpkaXNhYmxlX3Jvb3Q6IGZhbHNlCnNzaF9wd2F1dGg6IFRydWUKdXNlcnM6CiAgLSBkZWZhdWx0CiAgLSBuYW1lOiBzdXNlCiAgICBncm91cHM6IHN1ZG8KICAgIHNoZWxsOiAvYmluL2Jhc2gKICAgIHN1ZG86ICBBTEw9KEFMTCkgTk9QQVNTV0Q6QUxMCiAgICBsb2NrX3Bhc3N3ZDogRmFsc2UKICAgIHBsYWluX3RleHRfcGFzc3dkOiAnc3VzZScK name: cloudinitdisk EOF
此命令列显的输出应会指出已创建 VirtualMachine
:
virtualmachine.kubevirt.io/tumbleweed created
此 VirtualMachine
定义极其简洁,几乎未指定配置信息。它只是概括性地指明,此计算机的类型为“q35”,具有 2 GB 内存,使用基于临时
containerDisk
的磁盘映像(即,存储在远程映像储存库中某个容器映像内的磁盘映像)。此定义还指定了一个 base64 编码的 cloudInit
磁盘,该磁盘仅用于在引导时创建用户和执行口令(可使用 base64-d
对其进行解码)。
注意此虚拟机映像仅用于测试。该映像不受官方支持,仅用作文档示例。
此虚拟机需要几分钟时间才能完成引导,因为它需要下载 openSUSE Tumbleweed 磁盘映像,但一旦完成此过程,您可以通过检查虚拟机信息来查看有关该虚拟机的更多细节:
$ kubectl get vmi
此命令应会列显启动虚拟机的节点以及虚拟机的 IP 地址。请记住,由于它使用 Pod 网络,因此报告的 IP 地址与任何其他 Pod 一样并且可路由:
NAME AGE PHASE IP NODENAME READY tumbleweed 4m24s Running 10.42.2.98 node3.edge.rdo.wales True
在使用 CNI(例如 Cilium)将流量直接路由到 Pod 的情况下,在 Kubernetes 群集节点本身上运行这些命令时,您应该可以通过
ssh
直接连接到该虚拟机本身。请将下面的 IP 地址替换为分配给您的虚拟机的 IP 地址:
$ ssh suse@10.42.2.98 (password is "suse")
进入此虚拟机后,可对其进行任意操作,但请记住,它的资源有限,磁盘空间只有 1 GB。完成后,请按 Ctrl-D
或输入 exit
断开连接 SSH 会话。
虚拟机进程仍包装在标准 Kubernetes Pod 中。VirtualMachine
CRD
代表所需的虚拟机,但与任何其他应用程序一样,实际启动虚拟机的过程是通过 virt-launcher
Pod(标准 Kubernetes Pod)进行的。对于启动的每个虚拟机,都有一个对应的
virt-launcher
Pod:
$ kubectl get pods
此命令应会针对我们定义的 Tumbleweed 虚拟机显示一个 virt-launcher
Pod:
NAME READY STATUS RESTARTS AGE virt-launcher-tumbleweed-8gcn4 3/3 Running 0 10m
如果深入查看这个 virt-launcher
Pod,您会看到它正在执行
libvirt
和 qemu-kvm
进程。我们可以进入该 Pod
本身并查看其内部工作。请注意您需要根据自己的 Pod 名称修改以下命令:
$ kubectl exec -it virt-launcher-tumbleweed-8gcn4 -- bash
进入 Pod 后,尝试运行 virsh
命令并查看进程。您会看到
qemu-system-x86_64
二进制文件正在运行,以及用于监控虚拟机的某些进程。您还会看到磁盘映像的位置以及网络(作为 tap 设备)的插接方式:
qemu@tumbleweed:/> ps ax PID TTY STAT TIME COMMAND 1 ? Ssl 0:00 /usr/bin/virt-launcher-monitor --qemu-timeout 269s --name tumbleweed --uid b9655c11-38f7-4fa8-8f5d-bfe987dab42c --namespace default --kubevirt-share-dir /var/run/kubevirt --ephemeral-disk-dir /var/run/kubevirt-ephemeral-disks --container-disk-dir /var/run/kube 12 ? Sl 0:01 /usr/bin/virt-launcher --qemu-timeout 269s --name tumbleweed --uid b9655c11-38f7-4fa8-8f5d-bfe987dab42c --namespace default --kubevirt-share-dir /var/run/kubevirt --ephemeral-disk-dir /var/run/kubevirt-ephemeral-disks --container-disk-dir /var/run/kubevirt/con 24 ? Sl 0:00 /usr/sbin/virtlogd -f /etc/libvirt/virtlogd.conf 25 ? Sl 0:01 /usr/sbin/virtqemud -f /var/run/libvirt/virtqemud.conf 83 ? Sl 0:31 /usr/bin/qemu-system-x86_64 -name guest=default_tumbleweed,debug-threads=on -S -object {"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/run/kubevirt-private/libvirt/qemu/lib/domain-1-default_tumbleweed/master-key.aes"} -machine pc-q35-7.1,usb 286 pts/0 Ss 0:00 bash 320 pts/0 R+ 0:00 ps ax qemu@tumbleweed:/> virsh list --all Id Name State ------------------------------------ 1 default_tumbleweed running qemu@tumbleweed:/> virsh domblklist 1 Target Source --------------------------------------------------------------------------------------------- sda /var/run/kubevirt-ephemeral-disks/disk-data/tumbleweed-containerdisk-0/disk.qcow2 sdb /var/run/kubevirt-ephemeral-disks/cloud-init-data/default/tumbleweed/noCloud.iso qemu@tumbleweed:/> virsh domiflist 1 Interface Type Source Model MAC ------------------------------------------------------------------------------ tap0 ethernet - virtio-non-transitional e6:e9:1a:05:c0:92 qemu@tumbleweed:/> exit exit
最后,我们需要删除此虚拟机以清理资源:
$ kubectl delete vm/tumbleweed virtualmachine.kubevirt.io "tumbleweed" deleted
18.5 使用 virtctl #
除了标准的 Kubernetes CLI 工具 kubectl
之外,KubeVirt 还附带了 CLI
实用程序用于与群集连接,这种连接方式可以弥合虚拟化领域与 Kubernetes
适用领域之间的差距。例如,virtctl
工具提供管理虚拟机生命周期(启动、停止、重启动等)的功能,可用于访问虚拟控制台、上载虚拟机映像,以及与 Kubernetes
构造(例如服务)连接,而无需直接使用 API 或 CRD。
我们来下载最新的稳定版 virtctl
工具:
$ export VERSION=v1.1.0 $ wget https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/virtctl-${VERSION}-linux-amd64
如果您使用的是其他体系结构或非 Linux 计算机,可在此处找到其他版本。需要先将其转换为可执行文件才能继续,将其移动到
$PATH
中的某个位置可能会有帮助:
$ mv virtctl-${VERSION}-linux-amd64 /usr/local/bin/virtctl $ chmod a+x /usr/local/bin/virtctl
然后,可以使用 virtctl
命令行工具创建虚拟机。我们来复制前面创建的虚拟机,请注意我们会通过管道将输出直接传入 kubectl apply
:
$ virtctl create vm --name virtctl-example --memory=1Gi \ --volume-containerdisk=src:registry.opensuse.org/home/roxenham/tumbleweed-container-disk/containerfile/cloud-image:latest \ --cloud-init-user-data "I2Nsb3VkLWNvbmZpZwpkaXNhYmxlX3Jvb3Q6IGZhbHNlCnNzaF9wd2F1dGg6IFRydWUKdXNlcnM6CiAgLSBkZWZhdWx0CiAgLSBuYW1lOiBzdXNlCiAgICBncm91cHM6IHN1ZG8KICAgIHNoZWxsOiAvYmluL2Jhc2gKICAgIHN1ZG86ICBBTEw9KEFMTCkgTk9QQVNTV0Q6QUxMCiAgICBsb2NrX3Bhc3N3ZDogRmFsc2UKICAgIHBsYWluX3RleHRfcGFzc3dkOiAnc3VzZScK" | kubectl apply -f -
此命令应会显示虚拟机正在运行(由于容器映像将被缓存,因此这一次虚拟机的启动速度更快一些):
$ kubectl get vmi NAME AGE PHASE IP NODENAME READY virtctl-example 52s Running 10.42.2.29 node3.edge.rdo.wales True
现在我们可以使用 virtctl
直接连接到该虚拟机:
$ virtctl ssh suse@virtctl-example (password is "suse" - Ctrl-D to exit)
virtctl
还可以使用其他许多命令。例如,如果网络出现故障,您可以使用 virtctl
console
访问串行控制台;可以使用 virtctl guestosinfo
获取详细的操作系统信息,前提是已在 Guest 上安装并运行 qemu-guest-agent
。
最后,我们需要暂停再恢复该虚拟机:
$ virtctl pause vm virtctl-example VMI virtctl-example was scheduled to pause
您会发现,VirtualMachine
对象显示为 Paused,而 VirtualMachineInstance
对象则显示为 Running,但同时显示了 READY=False:
$ kubectl get vm NAME AGE STATUS READY virtctl-example 8m14s Paused False $ kubectl get vmi NAME AGE PHASE IP NODENAME READY virtctl-example 8m15s Running 10.42.2.29 node3.edge.rdo.wales False
您还会发现不再可以连接到该虚拟机:
$ virtctl ssh suse@virtctl-example can't access VMI virtctl-example: Operation cannot be fulfilled on virtualmachineinstance.kubevirt.io "virtctl-example": VMI is paused
我们需要恢复该虚拟机并重试:
$ virtctl unpause vm virtctl-example VMI virtctl-example was scheduled to unpause
现在我们应该可以重新建立连接:
$ virtctl ssh suse@virtctl-example suse@vmi/virtctl-example.default's password: suse@virtctl-example:~> exit logout
最后,我们需要去除该虚拟机:
$ kubectl delete vm/virtctl-example virtualmachine.kubevirt.io "virtctl-example" deleted
18.6 简单入口网络 #
本节介绍如何将虚拟机公开为标准 Kubernetes 服务,并通过 Kubernetes 入口服务(例如 RKE2 中的 NGINX 或 K3s 中的 Traefik)来提供这些虚拟机。本文档假设已正确配置这些组件,并且有一个适当的 DNS 指针指向 Kubernetes 服务器节点或入口虚拟 IP(例如通过通配符来指向),以正确解析入口。
注意在 SUSE Edge 3.0+ 中,如果您在多服务器节点配置中使用 K3s,则可能需要为入口配置基于 MetalLB 的 VIP;对于 RKE2 则不需要这样做。
在示例环境中,部署了另一个 openSUSE Tumbleweed 虚拟机,cloud-init 用于在引导时将 NGINX 安装为简单 Web
服务器,此外,已配置为返回一条简单讯息,以校验在发出调用时该虚拟机是否按预期工作。要了解如何执行此操作,只需对以下输出中的 cloud-init
部分运行 base64 -d
即可。
现在我们来创建此虚拟机:
$ kubectl apply -f - <<EOF apiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: ingress-example namespace: default spec: runStrategy: Always template: metadata: labels: app: nginx spec: domain: devices: {} machine: type: q35 memory: guest: 2Gi resources: {} volumes: - containerDisk: image: registry.opensuse.org/home/roxenham/tumbleweed-container-disk/containerfile/cloud-image:latest name: tumbleweed-containerdisk-0 - cloudInitNoCloud: userDataBase64: I2Nsb3VkLWNvbmZpZwpkaXNhYmxlX3Jvb3Q6IGZhbHNlCnNzaF9wd2F1dGg6IFRydWUKdXNlcnM6CiAgLSBkZWZhdWx0CiAgLSBuYW1lOiBzdXNlCiAgICBncm91cHM6IHN1ZG8KICAgIHNoZWxsOiAvYmluL2Jhc2gKICAgIHN1ZG86ICBBTEw9KEFMTCkgTk9QQVNTV0Q6QUxMCiAgICBsb2NrX3Bhc3N3ZDogRmFsc2UKICAgIHBsYWluX3RleHRfcGFzc3dkOiAnc3VzZScKcnVuY21kOgogIC0genlwcGVyIGluIC15IG5naW54CiAgLSBzeXN0ZW1jdGwgZW5hYmxlIC0tbm93IG5naW54CiAgLSBlY2hvICJJdCB3b3JrcyEiID4gL3Nydi93d3cvaHRkb2NzL2luZGV4Lmh0bQo= name: cloudinitdisk EOF
此虚拟机成功启动后,我们可以使用 virtctl
命令公开
VirtualMachineInstance
,其外部端口为
8080
,目标端口为 80
(NGINX 默认侦听此端口)。此处我们之所以使用
virtctl
命令,是因为它能够识别虚拟机对象与 Pod 之间的映射。这为我们创建了新服务:
$ virtctl expose vmi ingress-example --port=8080 --target-port=80 --name=ingress-example Service ingress-example successfully exposed for vmi ingress-example
然后会自动创建一个适当的服务:
$ kubectl get svc/ingress-example NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-example ClusterIP 10.43.217.19 <none> 8080/TCP 9s
接下来,如果您使用 kubectl create ingress
,则可以创建一个指向此服务的 ingress
对象。此处请根据您的 DNS 配置修改 URL(在 ingress
对象中称为“host”),并确保将其指向端口 8080
:
$ kubectl create ingress ingress-example --rule=ingress-example.suse.local/=ingress-example:8080
正确配置 DNS 后,可以立即对 URL 运行 curl 命令:
$ curl ingress-example.suse.local It works!
我们来通过去除此虚拟机及其服务和入口资源进行清理:
$ kubectl delete vm/ingress-example svc/ingress-example ingress/ingress-example virtualmachine.kubevirt.io "ingress-example" deleted service "ingress-example" deleted ingress.networking.k8s.io "ingress-example" deleted
18.7 使用 Rancher UI 扩展 #
SUSE 边缘虚拟化为 Rancher Manager 提供了 UI 扩展,让您可以使用 Rancher 仪表板 UI 进行基本的虚拟机管理。
18.7.1 安装 #
请参见 Rancher 仪表板扩展的相关章节获取安装指导。
18.7.2 使用 KubeVirt Rancher 仪表板扩展 #
该扩展在群集资源管理器中引入了新的 KubeVirt 部分。此部分已添加到装有 KubeVirt 的任何受管群集。
使用该扩展可以直接与以下两个 KubeVirt 资源交互:
虚拟机实例
— 代表单个正在运行的虚拟机实例的资源。虚拟机
— 用于管理虚拟机生命周期的资源。
18.7.2.1 创建虚拟机 #
单击左侧导航栏中已启用 KubeVirt 的受管群集,导航到 Cluster Explorer(群集资源管理器)。
导航到 KubeVirt > Virtual Machines(虚拟机)页面,然后单击屏幕右上角的
Create from YAML
(从 YAML 创建)。填写或粘贴虚拟机定义,然后按
Create
(创建)。使用“部署虚拟机”一节中创建的虚拟机定义作为灵感来源。
18.7.2.2 启动和停止虚拟机 #
使用操作菜单(可通过每个虚拟机右侧的 ⋮ 下拉列表访问)来启动和停止虚拟机,或者选择虚拟机并使用列表顶部的组操作对其执行操作。
只能对定义了 spec.running
属性的虚拟机运行启动和停止操作。如果使用了
spec.runStrategy
,则无法直接启动和停止此类计算机。有关详细信息,请参见 KubeVirt
文档。
18.7.2.3 访问虚拟机控制台 #
“Virtual machines”(虚拟机)列表提供了控制台
下拉列表,用于通过 VNC 或串行控制台连接到虚拟机。此操作仅适用于正在运行的虚拟机。
在某些情况下,需要等待一段时间才能在全新启动的虚拟机上访问控制台。
18.8 使用 Edge Image Builder 进行安装 #
SUSE Edge 使用第 9 章 “Edge Image Builder”来自定义基础 SLE Micro 操作系统映像。请按照第 21.9 节 “KubeVirt 和 CDI 安装”中所述,在 EIB 置备的 Kubernetes 群集上进行 KubeVirt 和 CDI 隔离式安装。