OpenShift Virtualization のディスクに関する独り言(CDI関連)

※この記事は OpenShift Advent Calendar 2024 の 10日目の記事です。

qiita.com

こんにちは、ストレージと OpenShift を生業にしているうつぼ(宇都宮)です。ご無沙汰しております。
ここ最近というかこの一年はすっかり OpenShift Virtualization(OCP-V)の小僧として活動させていただいております。
お陰様で OCP-V は非常に多方面から注目を浴びております。ありがとうございます。

OCP-V 小僧の活動は多忙ではありますが、良いところが多くあります。
それは OCP-V のお話をしていると、ほぼ確実にストレージの話題が出るところです。やはりドインフラな内容なので、皆様ストレージは気になるようです。
そんな中で、うつぼは普段なかなか抜かせてもらえないストレージトークの刃を日々合法的に振り回してカタルシスを感じております。ありがとうございます。

最近はそんな感じですが、今日の記事も普通に OCP-V を使う分にはあまり知られていない(そしてさほど知らなくても大丈夫な)ディスク周りのことです。

Containerized Data Importer(CDI)

OCP-V で仮想マシン(VM)を作る際には VirtualMachine というカスタムリソースを作ります。これは GUI でも CLI でも同じです。
VM にはいわゆる rootdisk が絶対必要です。永続性が求められるため PVC として用意するのが必須です。
ゲスト OS のイメージを PVC に放り込んで rootdisk を作っていくわけですが、いくつかの方法があります。

  • 既存の PVC や VolumeSnapshot からクローン
  • Web サーバやコンテナレジストリからディスクイメージをインポート
  • ローカルのディスクイメージをアップロード

これらを実行してくれるのが、Containerized Data Importer(CDI) です。

OpenShift Virtualization Operator でインストールすると自動的に作られるので意識することはほぼないですが、CDI はカスタムリソースとして作られます。

apiVersion: cdi.kubevirt.io/v1beta1
kind: CDI
metadata:
  annotations:
    cdi.kubevirt.io/configAuthority: ''
  name: cdi-kubevirt-hyperconverged
...(snip)
  finalizers:
    - operator.cdi.kubevirt.io
  labels:
    app: kubevirt-hyperconverged
    app.kubernetes.io/component: storage
    app.kubernetes.io/managed-by: hco-operator
    app.kubernetes.io/part-of: hyperconverged-cluster
    app.kubernetes.io/version: 4.17.1
spec:
  certConfig:
    ca:
      duration: 48h0m0s
      renewBefore: 24h0m0s
    server:
      duration: 24h0m0s
      renewBefore: 12h0m0s
  config:
    featureGates:
      - HonorWaitForFirstConsumer
      - DataVolumeClaimAdoption
    tlsSecurityProfile:
      intermediate: {}
      type: Intermediate
  customizeComponents: {}
  infra: {}
  uninstallStrategy: BlockUninstallIfWorkloadsExist
  workload: {}

Data Volume

GUI で VM を作成すると、自動的に VM と同じ名前の PVC が作成されます。この PVC を調べてみると、オーナーが Data Volume と指定されています。
OCP-V の VM は生の PVC ではなく、PVC を実体とする Data Volume というカスタムリソースで扱います。
デフォルトで用意されている VM テンプレートから VM を作成すると、次のような Data Volume が自動的に作られます。

apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: fedora-sapphire-possum-92
  namespace: demo
  ownerReferences:
    - apiVersion: kubevirt.io/v1
      blockOwnerDeletion: true
      controller: true
      kind: VirtualMachine
      name: fedora-sapphire-possum-92
...(snip)
spec:
  source:
    snapshot:
      name: fedora-fee3d6a527c8
      namespace: openshift-virtualization-os-images
  storage:
    resources:
      requests:
        storage: 30Gi
status:
  claimName: fedora-sapphire-possum-92

spec.source には、"snapshot" というセクションがありますが、これは VM テンプレートの rootdisk が VolumeSnapshot で用意されているためです。
この snapshot をソースとして、spec.storage で指定する性質(ここでは 30Gi というだけ)の PVC にクローンします。
その結果、status.claimName の PVC ができ上がります。

なお、rootdisk を別の方法、例えばコンテナレジストリに保管されているディスクイメージから作る場合は、次のような spec になります。

spec:
  source:
    registry:
      url: 'docker://quay.io/containerdisks/fedora:latest'
  storage:
    resources:
      requests:
        storage: 30Gi

Data Source

Data Volume は単体で作成してもよいですが、VirtualMachine のマニフェストで指定することもできます。

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: fedora-sapphire-possum-92
  namespace: demo
  finalizers:
    - kubevirt.io/virtualMachineControllerFinalize
  labels:
    app: fedora-possum-92
...(snip)
spec:
  dataVolumeTemplates:
    - apiVersion: cdi.kubevirt.io/v1beta1
      kind: DataVolume
      metadata:
        creationTimestamp: null
        name: fedora-sapphire-possum-92
      spec:
        sourceRef:
          kind: DataSource
          name: fedora
          namespace: openshift-virtualization-os-images
        storage:
          resources:
            requests:
              storage: 30Gi
  running: true
  template:
...(snip)
      volumes:
        - dataVolume:
            name: fedora-sapphire-possum-92
          name: rootdisk
...(snip)

上のように spec.dataVolumeTemplates の中に入れ子で Data Volume の定義を記載します。
この定義の中、spec.spec.dataVolumeTemplates.spec.sourceRef を見ると、先程の Data Volume とは違って Data Source というリソースが指定されています。
Data Source はその名の通り VM のディスクイメージを管理するカスタムリソースです。 デフォルトで用意されている VM テンプレートの中には、"Source available" というマークが付いているものがありますが、これはこのテンプレートには事前に Data Source が登録されて紐づけられているよ、ということです。

4つのテンプレートに "Source available" という青地白抜きのマーク
4つの Data Source(DS) が青地白抜きのマークに相当

ゴールデンイメージのように繰り返し使う標準化された VM イメージを作りたい場合は、そのディスクイメージを Data Source として登録してユーザに公開する方法がよいでしょう。

CDI と Data Volume と Data Source の関係

最後に CDI と Data Volume と Data Source の関係について紹介します。

CDI, Data Volume, Data Source の関係

前述のように Data Volume を作成すると Data Source からクローンなりインポートなりして仮想ディスクの PVC が作成されますが、このクローンなりインポートなりをしてくれるのが CDI です。

実際に Data Volume を作成すると、作成した namespace では、"importer-prime-XXX" という Pod が起動します。
Pod には "importer" というコンテナが起動し、Data Volume 作成時に指定した source からデータをインポートしてくれます。
以下はコンテナレジストリからディスクイメージをインポートして Data Volume を作る時の Importer Pod のログです。

[lab-user@bastion ~]$ oc get pod -n aaa
NAME                                                  READY   STATUS    RESTARTS   AGE
importer-prime-b16e5647-45c9-478a-a853-4b488c84651e   1/1     Running   0          12s
[lab-user@bastion ~]$
[lab-user@bastion ~]$ oc logs importer-prime-b16e5647-45c9-478a-a853-4b488c84651e
I1210 07:19:04.888954       1 importer.go:107] Starting importer
I1210 07:19:04.889904       1 importer.go:182] begin import process
I1210 07:19:04.890015       1 registry-datasource.go:190] Copying proxy certs
I1210 07:19:04.890045       1 registry-datasource.go:61] Error creating allCertDir open /proxycerts/: no such file or directory
I1210 07:19:04.890109       1 data-processor.go:348] Calculating available size
I1210 07:19:04.891113       1 data-processor.go:356] Checking out block volume size.
I1210 07:19:04.891144       1 data-processor.go:368] Request image size not empty.
I1210 07:19:04.891153       1 data-processor.go:373] Target size 32212254720.
I1210 07:19:04.891169       1 data-processor.go:247] New phase: TransferScratch
I1210 07:19:04.891278       1 registry-datasource.go:100] Copying registry image to scratch space.
I1210 07:19:04.891289       1 transport.go:193] Downloading image from 'docker://quay.io/containerdisks/fedora:latest', copying file from 'disk' to '/scratch'
I1210 07:19:06.901432       1 transport.go:217] Processing layer {Digest:sha256:c8f39d7d11433a0aa9f682ca065b3d65a6e09bb891bcf4e06faca4b8e88911b0 Size:489690908 URLs:[] Annotations:map[] MediaType:application/vnd.docker.image.rootfs.diff.tar.gzip CompressionOperation:0 CompressionAlgorithm:<nil> CryptoOperation:0}
I1210 07:19:07.750464       1 transport.go:152] File 'disk/disk.img' found in the layer
I1210 07:19:07.750846       1 util.go:96] Writing data...
I1210 07:19:11.288706       1 registry-datasource.go:178] VM disk image filename is disk.img
I1210 07:19:11.288738       1 data-processor.go:247] New phase: Convert
I1210 07:19:11.288747       1 data-processor.go:253] Validating image
E1210 07:19:11.296590       1 prlimit.go:156] failed to kill the process; os: process already finished
I1210 07:19:11.296699       1 qemu.go:115] Running qemu-img with args: [convert -t writeback -p -O raw /scratch/disk/disk.img /dev/cdi-block-volume]
I1210 07:19:11.301478       1 qemu.go:273] 0.00
I1210 07:19:11.316723       1 qemu.go:273] 1.01
I1210 07:19:11.386639       1 qemu.go:273] 2.01
I1210 07:19:11.397491       1 qemu.go:273] 3.02
I1210 07:19:11.489886       1 qemu.go:273] 4.03
I1210 07:19:11.499107       1 qemu.go:273] 5.03
I1210 07:19:11.588887       1 qemu.go:273] 6.28
I1210 07:19:11.593774       1 qemu.go:273] 7.53
...(snip)
I1210 07:19:20.453555       1 qemu.go:273] 97.92
I1210 07:19:20.481321       1 qemu.go:273] 98.98
I1210 07:19:20.533138       1 qemu.go:273] 99.99
E1210 07:19:20.790409       1 prlimit.go:156] failed to kill the process; os: process already finished
I1210 07:19:20.790441       1 data-processor.go:247] New phase: Resize
I1210 07:19:20.791396       1 data-processor.go:247] New phase: Complete
I1210 07:19:20.791544       1 importer.go:231] {"scratchSpaceRequired":false,"preallocationApplied":false,"labels":{"instancetype.kubevirt.io/default-instancetype":"u1.medium","instancetype.kubevirt.io/default-preference":"fedora"},"message":"Import Complete"}

インポートが完了すると Importer Pod は自動的に Terminate し、Data Volume は "Succeeded" となって、VM から利用できるようになります。

まとめ

以上で「OpenShift Virtualization の仮想マシンの rootdisk ってどうやって作っているの?」がおわかりになったかと思います。

よく考えると、一般的な PVC を使うコンテナアプリでは、PVC が使用可能な状態となれば Pod はすぐにマウントして起動しようとします。
OCP-V の VM の骨格は Pod と PVC ですから、インポートされ切ってない半生状態の PVC で VM が上がろうとしてもおかしくないわけです。

しかし、そうはならない。PVC にディスクイメージのインポートが終わるまでは Pod は絶対に起動しません。
これは CDI の枠組みの中で、仮想ディスクを生の PVC ではなく Data Volume として扱っているおかげです。そのおかげで、毎回キレイに VM が起動するようになっているのです。

ぶっちゃけ、イメージを指定したらいい感じに上がってきてくれるブラックボックスとして扱っても全然問題ないんですが、細かいところまできちんと作られているのが KubeVirt ひいては OpenShift Virtualization なんですよということが伝えたい独り言でした。

というわけで、今回はここまで。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。