OpenShift Virtualization (OCP-V) - Create VM - Custom Image - PVC Clone

### OpenShift Virtualization

Red Hat® OpenShift® Virtualization は Red Hat OpenShift に含まれる機能であり、組織が新規および既存の仮想マシン (VM) ワークロードを実行およびデプロイするための先進的なプラットフォームを提供します。このソリューションにより、従来の仮想マシンを、信頼できる、一貫した包括的なハイブリッドクラウド・アプリケーション・プラットフォームに簡単に移行できます。

OpenShift Virtualization は、VM の移行を単純化するとともに、クラウドネイティブ・アプリケーション・プラットフォームのシンプルさと速度を利用してインフラストラクチャのモダナイゼーションの道筋を提供します。また、先進的な管理の原則を取り入れつつ既存の仮想化への投資を維持することを目指しており、Red Hat の包括的な仮想化ソリューションの基盤となります。

https://www.redhat.com/ja/technologies/cloud-computing/openshift/virtualization

### Creating VMs from custom images

OCP 4.17 Bare Metal 環境 OpenShift Virtualization (OCP-V) で Virtual Machine (VM) を作成してみます。ここでは以下の手順を参考に、独自に作成した `Custom Image` から作成してみます。

Creating VMs from custom images

https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html/virtualization/virtual-machines#creating-vms-from-custom-images

`Custom Image` では以下の4つの方法がありますが、今回は4番目の方法で行ってみます。

> - Importing the image as a container disk from a registry.
    Optional: You can enable auto updates for your container disks. See Managing automatic boot source updates for details.
> - Importing the image from a web page.
> - Uploading the image from a local machine.
> - Cloning a persistent volume claim (PVC) that contains the image. 

なお、それぞれの方法によってサポートされるフォーマットが指定されています。

CDI supported operations matrix

https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html/virtualization/storage#virt-cdi-supported-operations-matrix_virt-preparing-cdi-scratch-space

また、処理の過程で `Scratch Space (一時ストレージ領域)` を使用します。

Preparing CDI scratch space

https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html/virtualization/storage#virt-about-scratch-space_virt-preparing-cdi-scratch-space

#### Creating VMs by cloning PVCs

PVC Clone の作成方法は複数あり、どの方法で PVC Clone を行うかは CDI による自動選択になっているため、ユーザーが明示的に指定する必要はありません。

https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html/virtualization/virtual-machines#virt-creating-vms-by-cloning-pvcs

> About cloning
>  When cloning a data volume, the Containerized Data Importer (CDI) chooses one of the following Container Storage Interface (CSI) clone methods:
>
> - CSI volume cloning
> - Smart cloning 
>
>Both CSI volume cloning and smart cloning methods are efficient, but they have certain requirements for use. If the requirements are not met, the CDI uses host-assisted cloning. Host-assisted cloning is the slowest and least efficient method of cloning, but it has fewer requirements than either of the other two cloning methods. 

ここでは、以下の VM で使用している PVC から Clone を作成してみます。VM は停止しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128125/0698ff55-1bc1-48bf-a45c-b65b0c83b2c4.png)

VM が使用している PVC / PV は以下になります。
```shell
$ oc project -q
z-ocpv-vmtest-custom

$ oc get pvc
NAME                     STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS                                 VOLUMEATTRIBUTESCLASS   AGE
rhel9-custom01-upload    Bound    pvc-94f07b59-5200-4474-88be-05912a12e36a   30Gi       RWX            ocs-storagecluster-ceph-rbd-virtualization   <unset>                 2d23h

$ oc get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                                                          STORAGECLASS                                 VOLUMEATTRIBUTESCLASS   REASON   AGE
pvc-94f07b59-5200-4474-88be-05912a12e36a   30Gi       RWX            Delete           Bound    z-ocpv-vmtest-custom/rhel9-custom01-upload                                     ocs-storagecluster-ceph-rbd-virtualization   <unset>                          3d1h
```
この PVC が使用している StorageClass に対応する StorageProfile は以下のようになっています。
```shell
$ oc -o yaml get storageprofile ocs-storagecluster-ceph-rbd-virtualization
apiVersion: cdi.kubevirt.io/v1beta1
kind: StorageProfile
metadata:
  labels:
    app: containerized-data-importer
    app.kubernetes.io/component: storage
    app.kubernetes.io/managed-by: cdi-controller
    app.kubernetes.io/part-of: hyperconverged-cluster
    app.kubernetes.io/version: 4.17.4
    cdi.kubevirt.io: ""
  name: ocs-storagecluster-ceph-rbd-virtualization
  ownerReferences:
  - apiVersion: cdi.kubevirt.io/v1beta1
    blockOwnerDeletion: true
    controller: true
    kind: CDI
    name: cdi-kubevirt-hyperconverged
spec: {}
～省略～
```

##### Creating a VM from a PVC by using the web console

OCP Console から Virtualization -> Catalog を表示します。画面が `Template catalog` Tab になっている事を確認します。

左上の Project に VM を作成する Project として `z-ocpv-vmtest-custom` を指定した上で、`Red Hat Enterprise Linux 9 VM` Template を選択します。`Disk source` から `PVC (clone PVC)` を選択し、`PVC project` と `PVC name` を指定します。


その他の必要な箇所の設定変更を行います。今回は `VirtualMachine name` のみ `rhel9-custom01-clonepvc-from-upload` に変更しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128125/adfeb66b-2f83-42c5-8a5e-c2d0348ecfb8.png)

`Quick create VirtualMachine` ボタンを押して VM を作成します。以下のような画面が表示され、`Status Running` になれば完了です。
※ PVC Clone 元の VM が起動した状態で実行すると、実行中に `Provisioning` Status で処理が停止します。その状態で PVC Clone 元の VM を停止すると、処理が継続して正常終了します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128125/c8fc95b2-e193-4207-9e9c-2d550b79bad6.png)

なお、今回の手順では、以下のような `DataVolume` が作成されます。
```shell
$ oc get datavolume
NAME                                  PHASE       PROGRESS   RESTARTS   AGE
rhel9-custom01-clonepvc-from-upload   Succeeded   100.0%                7m14s

$ oc -o yaml get datavolume rhel9-custom01-clonepvc-from-upload
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  annotations:
    cdi.kubevirt.io/allowClaimAdoption: "true"
    cdi.kubevirt.io/cloneType: csi-clone
    cdi.kubevirt.io/storage.clone.token: *************************************
    cdi.kubevirt.io/storage.usePopulator: "true"
  labels:
    kubevirt.io/created-by: 7b7e6857-db6b-4f15-93c5-fcf53377efc3
  name: rhel9-custom01-clonepvc-from-upload
  namespace: z-ocpv-vmtest-custom
  ownerReferences:
  - apiVersion: kubevirt.io/v1
    blockOwnerDeletion: true
    controller: true
    kind: VirtualMachine
    name: rhel9-custom01-clonepvc-from-upload
spec:
  source:
    pvc:
      name: rhel9-custom01-upload
      namespace: z-ocpv-vmtest-custom
  storage:
    resources:
      requests:
        storage: "32212254720"
～省略～
```

v1beta1.DataVolumeSourcePVC

https://kubevirt.io/api-reference/main/definitions.html#_v1beta1_datavolumesourcepvc

`PVC / PV` は、以下のとおりです。
```shell
$ oc get pvc
NAME                                                      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS                                 VOLUMEATTRIBUTESCLASS   AGE
rhel9-custom01-clonepvc-from-upload                       Bound    pvc-3a633b25-b08c-4105-ab1a-f95d126f7bb0   30Gi       RWX            ocs-storagecluster-ceph-rbd-virtualization   <unset>                 49m

$ oc get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                                                          STORAGECLASS                                 VOLUMEATTRIBUTESCLASS   REASON   AGE
pvc-3a633b25-b08c-4105-ab1a-f95d126f7bb0   30Gi       RWX            Delete           Bound    z-ocpv-vmtest-custom/rhel9-custom01-clonepvc-from-upload                       ocs-storagecluster-ceph-rbd-virtualization   <unset>                          50m
```
`VirtualMachine` は、以下のとおりです。
```shell
$ oc get vm
NAME                                                      AGE     STATUS    READY
rhel9-custom01-clonepvc-from-upload                       35m     Running   True

$ oc -o yaml get vm rhel9-custom01-clonepvc-from-upload
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  annotations:
    kubevirt.io/latest-observed-api-version: v1
    kubevirt.io/storage-observed-api-version: v1
    vm.kubevirt.io/validations: |
      [
        {
          "name": "minimal-required-memory",
          "path": "jsonpath::.spec.domain.memory.guest",
          "rule": "integer",
          "message": "This VM requires more memory.",
          "min": 1610612736
        }
      ]
  finalizers:
  - kubevirt.io/virtualMachineControllerFinalize
  generation: 1
  labels:
    app: rhel9-custom01-clonepvc-from-upload
    kubevirt.io/dynamic-credentials-support: "true"
    vm.kubevirt.io/template: rhel9-server-small
    vm.kubevirt.io/template.namespace: openshift
    vm.kubevirt.io/template.revision: "1"
    vm.kubevirt.io/template.version: v0.31.1
  name: rhel9-custom01-clonepvc-from-upload
  namespace: z-ocpv-vmtest-custom
spec:
  dataVolumeTemplates:
  - apiVersion: cdi.kubevirt.io/v1beta1
    kind: DataVolume
    metadata:
      creationTimestamp: null
      name: rhel9-custom01-clonepvc-from-upload
    spec:
      source:
        pvc:
          name: rhel9-custom01-upload
          namespace: z-ocpv-vmtest-custom
      storage:
        resources:
          requests:
            storage: "32212254720"
  running: true
～省略～
```


##### Creating a VM from a PVC by using the command line
command line (CLI) からの PVC clone には、以下の方法があります。

> - Cloning a PVC to a new data volume.
    This method creates a data volume whose lifecycle is independent of the original VM. Deleting the original VM does not affect the new data volume or its associated PVC.
> - Cloning a PVC by creating a VirtualMachine manifest with a dataVolumeTemplates stanza.
    This method creates a data volume whose lifecycle is dependent on the original VM. Deleting the original VM deletes the cloned data volume and its associated PVC. 
    
###### Cloning a PVC to a new data volume
以下のような Manifest から DataVolume を作成します。
```shell
$ cat new-datavolume-from-upload.yaml
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: "new-datavolume-from-upload"
  namespace: "z-ocpv-vmtest-custom"
spec:
  source:
    pvc:
      namespace: "z-ocpv-vmtest-custom"
      name: "rhel9-custom01-upload"
  storage: {}

$ oc create -f new-datavolume-from-upload.yaml
datavolume.cdi.kubevirt.io/new-datavolume-from-upload created
```
DataVolume と PVC / PV が作成されます。
```shell
$ oc get datavolume
NAME                                                      PHASE       PROGRESS   RESTARTS   AGE
new-datavolume-from-upload                                Succeeded   100.0%                27m

$ oc get pvc
NAME                                                      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS                                 VOLUMEATTRIBUTESCLASS   AGE
new-datavolume-from-upload                                Bound    pvc-a4761e1b-48cb-424e-95b7-8e58e505603f   30Gi       RWX            ocs-storagecluster-ceph-rbd-virtualization   <unset>                 27m

$ oc get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                                                          STORAGECLASS                                 VOLUMEATTRIBUTESCLASS   REASON   AGE
pvc-a4761e1b-48cb-424e-95b7-8e58e505603f   30Gi       RWX            Delete           Bound    z-ocpv-vmtest-custom/new-datavolume-from-upload                                ocs-storagecluster-ceph-rbd-virtualization   <unset>                          28m
```
OCP Console から、この PVC を指定して VM を作成します。`VirtualMachine name` を `rhel9-custom01-clonepvc-from-upload-with-cli-datavolume` に変更しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128125/99171780-5209-42e7-ba02-f5fd0dca38ee.png)

以下のような画面が表示され、`Status Running` になれば完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128125/1375036e-e76f-4163-bfc3-0730a55f7d45.png)

###### Cloning a PVC by creating a VirtualMachine manifest with a dataVolumeTemplates stanza
マニュアルの記載を参考に、以下のような VirtualMachine Manifest から VM を作成します。`.spec.dataVolumeTemplates.spec.storage` の指定が、Clone 元 PVC と整合性が取れている事に注意します。

https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html/virtualization/virtual-machines#virt-creating-vm-cloning-pvc-data-volume-template_virt-creating-vms-by-cloning-pvcs

```shell
$ cat rhel9-custom01-clonepvc-from-upload-with-cli-vm.yaml
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  labels:
    kubevirt.io/vm: vm-dv-clone
  name: "rhel9-custom01-clonepvc-from-upload-with-cli-vm"
  namespace: "z-ocpv-vmtest-custom"
spec:
  running: false
  template:
    metadata:
      labels:
        kubevirt.io/vm: vm-dv-clone
    spec:
      domain:
        devices:
          disks:
          - disk:
              bus: virtio
            name: root-disk
        resources:
          requests:
            memory: 2Gi
      volumes:
      - dataVolume:
          name: favorite-clone
        name: root-disk
  dataVolumeTemplates:
  - metadata:
      name: favorite-clone
    spec:
      storage:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: "32212254720"
      source:
        pvc:
          namespace: "z-ocpv-vmtest-custom"
          name: "rhel9-custom01-upload"
          
$ oc create -f rhel9-custom01-clonepvc-from-upload-with-cli-vm.yaml
virtualmachine.kubevirt.io/rhel9-custom01-clonepvc-from-upload-with-cli-vm created
```

`.spec.running` に `false (VirtualMachineInstance を作成しない)` と指定したため、`Stopped` status で VM が作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128125/5b37d84d-1df7-4943-8ffe-3481b12d5853.png)

VM を起動します。以下のような画面が表示され、`Status Running` になれば完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128125/d88d2e64-9bd0-4b98-a997-6a66c6e16e7d.png)

今回の手順では、以下のようなリソースが作成されます。
```shell
$ oc get all,pvc
NAME                                                                  READY   STATUS    RESTARTS   AGE
pod/virt-launcher-rhel9-custom01-clonepvc-from-upload-with-clin4n6s   1/1     Running   0          3m6s

NAME               TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
service/headless   ClusterIP   None         <none>        5434/TCP   3d1h

NAME                                                                                 PHASE       PROGRESS   RESTARTS   AGE
datavolume.cdi.kubevirt.io/favorite-clone                                            Succeeded   100.0%                7m25s

NAME                                                                                         AGE    PHASE     IP             NODENAME   READY
virtualmachineinstance.kubevirt.io/rhel9-custom01-clonepvc-from-upload-with-cli-vm           3m6s   Running   10.129.2.50    worker01   True

NAME                                                                                 AGE     STATUS    READY
virtualmachine.kubevirt.io/rhel9-custom01-clonepvc-from-upload-with-cli-vm           7m25s   Running   True

NAME                                                                            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS                                 VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/favorite-clone                                            Bound    pvc-ba56bd65-838d-43e6-911c-f53333792154   30Gi       RWO            ocs-storagecluster-ceph-rbd-virtualization   <unset>                 7m25s

$ oc get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                                                          STORAGECLASS                                 VOLUMEATTRIBUTESCLASS   REASON   AGE
pvc-ba56bd65-838d-43e6-911c-f53333792154   30Gi       RWO            Delete           Bound    z-ocpv-vmtest-custom/favorite-clone                                            ocs-storagecluster-ceph-rbd-virtualization   <unset>                          9m17s

$ oc get -o yaml datavolume favorite-clone
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  annotations:
    cdi.kubevirt.io/allowClaimAdoption: "true"
    cdi.kubevirt.io/cloneType: csi-clone
    cdi.kubevirt.io/storage.clone.token: **************
    cdi.kubevirt.io/storage.usePopulator: "true"
  labels:
    kubevirt.io/created-by: 96382df8-6456-4f58-aad3-ca3e1d482c4c
  name: favorite-clone
  namespace: z-ocpv-vmtest-custom
  ownerReferences:
  - apiVersion: kubevirt.io/v1
    blockOwnerDeletion: true
    controller: true
    kind: VirtualMachine
    name: rhel9-custom01-clonepvc-from-upload-with-cli-vm
spec:
  source:
    pvc:
      name: rhel9-custom01-upload
      namespace: z-ocpv-vmtest-custom
  storage:
    accessModes:
    - ReadWriteOnce
    resources:
      requests:
        storage: "32212254720"
～省略～
```