VirtualServer

VirtualServer 在集群中创建一个虚拟机,与容器环境相比,虚拟机:

  • 提供与物理计算机类似的运行环境,在有些场景更加符合用户需求;
  • 提供更强的隔离,可以限制潜在的安全影响范围。

创建 VirtualServer

下面是一个基本的 VirtualServer 示例:

apiVersion: tensorstack.dev/v1beta1
kind: VirtualServer
metadata:
  name: vs-example
spec:
  resources:
    cpu:
      cores: "1"
    memory: 8Gi
  storage:
    root:
      pvc:
        size: 4Gi
        volumeMode: Filesystem
        accessModes: ["ReadWriteOnce"]
        storageClassName: cephfs-hdd
      source:
        http:
          url: https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-amd64.img
    additionalDisks:
      - name: disk-name
        persistentVolumeClaim:
          claimName: pvc-as-disk
    filesystems:
      - name: my-filesys
        persistentVolumeClaim:
          claimName: pvc-as-fs
  network:
    tcp: [80, 5901]
    udp: []
    macAddress: ""
    dnsConfig: {}
    dnsPolicy: ClusterFirst
  cloudInit: |-
    #cloud-config
    bootcmd:
    - test "$(lsblk /dev/vdb)" && mkfs.ext4 /dev/vdb
    - mkdir -p /mnt/vdb
    - mkdir -p /mnt/file-sys
    - mount -t virtiofs my-filesys /mnt/file-sys
    mounts:
    - [ "/dev/vdb", "/mnt/vdb", "ext4", "defaults,nofail", "0", "2" ]

在该例中:

  1. 虚拟机申请使用 1 个 CPU 和 8Gi 内存(由 spec.resources 字段指定)。
  2. 虚拟机绑定一个 4Gi 的 PVC 作为根磁盘,并在 PVC 中下载 https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-amd64.img 系统镜像(由 spec.storage.root 字段指定)。
  3. 虚拟机声明 PVC pvc-as-disk 为磁盘 /dev/vdb(由 spec.storage.additionalDisks 字段指定,根磁盘为 /dev/vda,其他磁盘按顺序命名,但预设名称的方式不完全可靠,建议使用 serial 字段确认磁盘,参考 disk),并将该磁盘格式化为 ext4 文件系统,挂载到 /mnt/vdb 路径(格式化和绑定操作由 spec.cloudInit 字段指定)。
  4. 虚拟机声明 PVC pvc-as-fs 为文件系统 my-filesys(由 spec.storage.filesystems 字段指定),并将该文件系统挂载到 /mnt/file-sys 路径(绑定操作由 spec.cloudInit 字段指定)。
  5. 虚拟机会暴露 805901 两个 TCP 服务端口(由 spec.network 字段指定)。

虚拟机配置详解

资源申请

VirtualServer 支持设置 spec.resources 字段来申请 CPU、GPU 和内存三种资源。

spec:
  resources:
    gpu:
      type: nvidia.com/GA100_A100_PCIE_40GB
      count: 2
    cpu:
      model: Conroe
      cores: 1
    memory: 10Gi

在该例中,虚拟机申请使用 2nvidia.com/GA100_A100_PCIE_40GB GPU 卡、1Conroe 型号的 CPU 以及 10Gi 的内存。

根磁盘

VirtualServer 根据 spec.storage.root.pvc 字段中的配置创建 PVC 作为根磁盘,并从 spec.storage.root.source 字段所指定的数据源下载操作系统。

spec:
  storage:
    root:
      ephemeral: false
      pvc:
        size: 4Gi
        volumeMode: Filesystem
        accessModes: ["ReadWriteOnce"]
        storageClassName: cephfs-hdd
      source:
        http:
          url: https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-amd64.img

在上述示例中:

  • VirtualServer 创建一个 4Gi PVC,绑定为根磁盘。
  • https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-amd64.img 链接下载系统镜像到根磁盘中。
    • VirtualServer 支持从多种数据源下载数据,比如 HTTP 链接、s3 数据库、镜像仓库、其他 PVC 等,更多信息请参考 DataVolumeSource
  • 该根磁盘以非临时磁盘的方式绑定给虚拟机(通过 spec.storage.root.ephemeral 字段指定)。所谓临时磁盘,即在虚拟机中的所有改动不会反应到 PVC 中,而是以一个临时镜像的方式存储;如果虚拟机重启,则该临时镜像消失。

其他数据卷

除了根磁盘以外,VirtualServer 支持以 disk 和 filesystem 方式绑定任意数量的数据卷。

绑定方式

filesystem

VirtualServer 通过 spec.storage.filesystems 字段声明文件系统:

spec:
  storage:
    filesystems:
      - name: my-filesys
        persistentVolumeClaim:
          claimName: pvc-as-fs

在上述示例中,VirtualServer 将 PVC pvc-as-fs 声明为文件系统 my-filesys

上面字段值只声明了文件系统,用户还需要将文件系统挂载到一个路径才可以使用。用户可以用 spec.cloudInit 字段来挂载该文件系统:

spec:
  cloudInit: |-
    #cloud-config
    bootcmd:
    - mkdir -p /mnt/file-sys
    - mount -t virtiofs my-filesys /mnt/file-sys

此外,用户也可以在虚拟机启动后,进入虚拟机手动执行上述挂载命令,此处不再演示。

disk

VirtualServer 通过 spec.storage.additionalDisks 字段声明磁盘:

spec:
  storage:
    additionalDisks:
      - name: disk-name
        persistentVolumeClaim:
          claimName: pvc-as-disk
        serial: CVLY623300HK240D

在上述示例中:

  • VirtualServer 将 PVC pvc-as-disk 声明为一个磁盘;
  • 序列号为 CVLY623300HK240D,如果不设置该字段,则磁盘序列号会随机生成,在重启虚拟机后,序列号会发生改变。序列号有助于确认磁盘,如通过命令 lsblk --nodeps -no name,serial | grep CVLY623300HK240D | cut -f1 -d' ' 确认上述示例中磁盘的名称。

如果该磁盘是第一次使用,用户还需要将磁盘格式化并挂载到一个路径,可以用 spec.cloudInit 字段完成该操作:

spec:
  cloudInit: |-
    #cloud-config
    bootcmd:
    - test "$(lsblk /dev/vdb)" && mkfs.ext4 /dev/vdb
    - mkdir -p /mnt/file-sys
    mounts:
    - [ "/dev/vdb", "/mnt/vdb", "ext4", "defaults,nofail", "0", "2" ]

此外,用户也可以在虚拟机启动后,进入虚拟机手动执行格式化和挂载操作,此处不再演示。

数据卷类型

PersistentVolumeClaim

使用一个 PVC 作为磁盘或文件系统:

spec:
  storage:
    filesystems:
      - name: filesys-name
        persistentVolumeClaim:
          claimName: pvc-name
---
spec:
  storage:
    additionalDisks:
      - name: disk-name
        persistentVolumeClaim:
          claimName: pvc-name
临时 PVC

临时 PVC 指的是虚拟机将 PVC 作为只读存储,虚拟机在启动后会在本地维护一个临时镜像,记录用户对 PVC 的写入都会记录在该临时镜像中,而不会实际写入到 PVC 中。如果虚拟机停止或重启,则临时镜像丢失。

spec:
  storage:
    filesystems:
      - name: filesys-name
        ephemeral:
          persistentVolumeClaim:
            claimName: pvc-name
---
spec:
  storage:
    additionalDisks:
      - name: disk-name
        ephemeral:
          persistentVolumeClaim:
            claimName: pvc-name
ConfigMap

ConfigMap 也可以作为磁盘或文件系统绑定到虚拟机上:

spec:
  storage:
    filesystems:
      - name: config-fs
        configMap:
          name: app-config
  cloudInit: |-
    #cloud-config
    bootcmd:
    - "sudo mkdir /mnt/app-config"
    - "sudo mount -t virtiofs config-fs /mnt/app-config"
---
spec:
  storage:
    additionalDisks:
      - name: disk-name
        configMap:
          name: app-config
        serial: CVLY623300HK240D
  cloudInit: |-
    #cloud-config
    bootcmd:
    - "sudo mkdir /mnt/app-config"
    - "sudo mount /dev/$(lsblk --nodeps -no name,serial | grep CVLY623300HK240D | cut -f1 -d' ') /mnt/app-config"

与 PVC 不同,以 ConfigMap 作为 disk 绑定给虚拟机时不需要格式化,控制器会自动创建磁盘并将 ConfigMap data 中的内容以文件的形式存入磁盘。

用户可以给磁盘设置 serial 字段,以便查找磁盘。

ServiceAccount
spec:
  storage:
    filesystems:
      - name: sa-fs
        serviceAccount:
          serviceAccountName: app-secret
  cloudInit: |-
    #cloud-config
    bootcmd:
    - "sudo mkdir /mnt/app-sa"
    - "sudo mount -t virtiofs sa-fs /mnt/app-sa"
---
spec:
  storage:
    additionalDisks:
      - name: disk-name
        serviceAccount:
          serviceAccountName: serviceaccountdisk
        serial: SERVICEACCOUNT12
  cloudInit: |-
    #cloud-config
    bootcmd:
    - "sudo mkdir /mnt/app-sa"
    - "sudo mount /dev/$(lsblk --nodeps -no name,serial | grep SERVICEACCOUNT12 | cut -f1 -d' ') /mnt/app-sa"

以 ServiceAccount 作为 disk 绑定给虚拟机时不需要格式化,控制器会自动创建磁盘,并在磁盘中创建 ca.crtnamespacetoken 三个文件,这些文件中记录着 ServiceAccount 的权限信息。

以 ServiceAccount 作为 filesystem 绑定给虚拟机,控制器也会在挂在路径中创建 ca.crtnamespacetoken 三个文件,这些文件中记录着 ServiceAccount 的权限信息。

网络策略

用户可通过 spec.network 字段设置虚拟机的 DNS 策略,暴露虚拟机服务:

spec:
  network:
    tcp: [80, 5901]
    udp: []
    macAddress: ""
    dnsConfig: {}
    dnsPolicy: ClusterFirst

字段说明:

  • tcpudp:表示虚拟机所要暴露的服务端口。
  • macAddress:网络接口的 MAC 地址,以便在虚拟机重启前后 MAC 地址保持一致。如果不指定该字段,虚拟机每次重启都会被分配随机 MAC 地址。
  • dnsPolicy:DNS 策略,默认为 ClusterFirst,可选值参考 DNS Policy
  • dnsConfig:虚拟机 DNS 配置,参考 DNS Config

在设置 spec.network 字段后,控制器会自动创建一个与虚拟机同名的 Service。用户可以通过该 Service 访问虚拟机的上述 TCP/UDP 接口,比如通过以下命令将服务暴露到本地 8080 端口:

kubectl port-forward service/managed-virtualserver-9dda9 8080:80

CloudInit

CloudInit 是 Canonical 公司开发的系统初始化工具,已被许多云服务提供商和 Linux 发行版广泛接受。

用户可通过 spec.cloudInit 字段设置 CloudInit 配置来初始化虚拟机环境,包括用户密码、启动命令等:

spec:
  cloudInit: |-
    #cloud-config
    user: ubuntu
    password: ubuntu
    chpasswd: { expire: False }
    package_update: true
    package_upgrade: true
    packages: 
    - qemu-guest-agent
    runcmd:
    - [ systemctl, start, qemu-guest-agent ]
    bootcmd:
    - "sudo mkdir /mnt/app-configmap"
    - "sudo mount -t virtiofs configmap-fs /mnt/app-configmap"
    - "sudo mkdir /mnt/app-sa"
    - "sudo mount /dev/$(lsblk --nodeps -no name,serial | grep SERVICEACCOUNT12 | cut -f1 -d' ') /mnt/app-sa"

在上述示例中,虚拟机会执行以下初始化操作:

  • 创建用户 ubuntu:ubuntu(用户名:密码);
  • 更新系统软件包;
  • 安装并启动 qemu-guest-agent 服务;
  • 绑定文件系统 configmap-fs 和磁盘(序列号为 SERVICEACCOUNT12)。

更多 CloudInit 配置请参考 Cloud Config

虚拟机运行策略

apiVersion: tensorstack.dev/v1beta1
kind: VirtualServer
metadata:
  name: example-vs
spec:
  runStrategy: Always
  ...
status:
  runStrategy: Halted
  ...

在上述示例中:

  • spec.runStrategy 表示 VirtualServer 创建时,使用的运行策略
  • status.runStrategy 表示当前 VirtualServer 的运行策略。

用户可以通过虚拟机操作切换虚拟机运行策略:

Run Strategystartstoprestart
Always-HaltedAlways
RerunOnFailureRerunOnFailureHaltedRerunOnFailure
ManualManualManualManual
HaltedAlways--
  • 第一行后三项为虚拟机操作,分别为启动、停止和重启。
  • 最左侧一列为操作前的运行策略,右侧为操作后的运行策略。
  • - 表示在当前运行策略下,不可执行该操作。

虚拟机操作

用户可以通过 K8s 拓展 API 来操控虚拟机,常见的虚拟机操作方式包括:

  • kubectl virt start $vm_name:启动虚拟机;
  • kubectl virt stop $vm_name:停止虚拟机;
  • kubectl virt restart $vm_name:重启虚拟机;
  • kubectl virt console $vm_name:连接虚拟机终端。

更多虚拟机操作请通过 kubectl virt -h 命令查看。

状态

虚拟机名称

为避免资源名称过长,导致创建子资源时失败,VirtualServer 在创建虚拟机时,将名称和 UID 进行哈希编码构成虚拟机名称,并将虚拟机名称记录在 status.vm.name 字段中。

status:
  vm:
    name: managed-virtualserver-9dda9

VirtualServer 的状态

status.conditions 字段用于描述当前 VirtualServer 的状态,包括以下 4 种类型:

  • DataImported:VirtualServer 已经创建根磁盘 PVC,并下载好系统镜像。
  • Ready:虚拟机已经成功启动,这里指的是系统成功启动,但是不考虑 CloudInit 过程。
  • Failure:虚拟机意外关闭,如节点崩溃、OOMKILLED 等。
  • Paused:虚拟机暂停,无法使用 CPU 和内存。

在下面的示例中,VirtualServer 成功创建了根磁盘并下载好系统镜像,所以类型为 DataImportedcondition 被设为 True;VirtualServer 此时被停止(不是暂停),工作负载被删除,所以类型为 Readycondition 被设为 False

status:
  conditions:
    - lastTransitionTime: "2024-03-21T08:26:59Z"
      message: Root disk has been imported successfully.
      reason: Succeeded
      status: "True"
      type: DataImported
    - lastTransitionTime: "2024-03-21T08:35:23Z"
      message: VMI does not exist
      reason: VMINotExists
      status: "False"
      type: Ready

VirtualServer 的可读状态

status.printableStatus 字段是一个易被用户理解的状态字符串,该字段可能为以下取值:

  • Stopped:虚拟机当前处于停止状态,且不会主动启动。
  • Provisioning:集群正在为虚拟机准备资源,包括向磁盘中下载数据。
  • Starting:正在启动虚拟机。
  • Running:虚拟机处于运行阶段。
  • Paused:虚拟机被暂停。
  • Stopping:虚拟机正在停止,包括删除工作负载等步骤。
  • Terminating:虚拟机正在被删除。
  • CrashLoopBackOff:虚拟机目前已崩溃,正等待重启。
  • Migrating:虚拟机正在迁移到另一个主机(目前不支持)。
  • Unknown:虚拟机状态未知,通常发生在虚拟机所在主机失去连接。
  • FailedUnschedulable:虚拟机无法被分配,可能的原因包括集群资源不足等。

参考