※本記事は OpenShift Virtualization アドベントカレンダーの 22 日目の記事です。
皆さんこんにちは、OpenShift Virtualization とストレージを生業にしている Red Hat のうつぼ(宇都宮)です。
もう30年くらい前ですが、ウゴウゴルーガというテレビ番組がありました。私は小学生のくせにあのシュールさが大好きなクソガキだったのですが、大阪に住んでいて観られませんでした。
だから先に上京した兄に録画を頼んで VHS を送ってもらっていたんですね。
ところがある朝ビデオを見ると全然知らん映画が録画されてたんですよ。どうも父が上から録画していたようです。
それは超文句言いましたよ。めっちゃ怒りました。そして「なんで俺はツメを折らんかったんや」と悔いました。悔いに悔いました。
何が言いたいかというと、大事なデータはちゃんと保護して管理しましょうということです。
仮想化環境ってゲスト OS のゴールデンイメージは最高に大事ですよね。ということで今日のテーマはゲスト OS のイメージです。
はい、愚にもつかないような昔話で始まったのですが、ゲスト OS のイメージをどのように扱っているでしょうか?
OpenShift Virtualization(以下 Virt)には標準でいくつかの便利な VM テンプレートを提供しており、特に Red Hat Enterprise Linux(RHEL)や Fedora、CentOS Streams など、一部のテンプレートではゲストOSのイメージも一緒に提供されています。
今回は、この「標準で提供されるゲスト OS イメージ」がどのように管理され、どのように利用されているのか。そして「独自のゲスト OS イメージ」を利用するにはどうすれば良いのかについて紹介したいと思います。
標準のゲスト OS のイメージ
コンテナイメージとしてのゲストOS
Virt の標準テンプレートを利用して RHEL などの VM を作成する際、VM のディスクイメージはどこから来ているのでしょうか。
実は、これらのゲスト OS イメージは、従来の qcow2 ファイルなどの形で提供されているわけではなく、レッドハットのコンテナレジストリからコンテナイメージの形式で提供されています。
Virt のゲスト OS イメージは、CDI という仕組みによって DataVolume や DataSource というカスタムリソースで管理されます。CDI については別の記事で紹介していますので、よかったらご覧ください。
OpenShift Virtualization のディスクに関する独り言(CDI関連) - 赤帽エンジニアブログ
さてこの CDI は何の略かというと、Containerized Data Importer の略です。思いっきり Containerized と書かれているのです。
ゲスト OS イメージをコンテナイメージとして扱う必要性はないのですが、こうすることで、Kubernetes/OpenShift のエコシステムに馴染んだ形でイメージを配信・管理することが可能になっているのです。
実際、標準の OS イメージが格納されている DataSource リソースを見てみましょう。
[root@bastion ~]# oc get datasource rhel9 -n openshift-virtualization-os-images -oyaml
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataSource
metadata:
annotations:
operator-sdk/primary-resource: openshift-cnv/ssp-kubevirt-hyperconverged
operator-sdk/primary-resource-type: SSP.ssp.kubevirt.io
creationTimestamp: "2025-12-09T00:42:42Z"
generation: 9
labels:
app.kubernetes.io/component: storage
app.kubernetes.io/managed-by: cdi-controller
app.kubernetes.io/part-of: hyperconverged-cluster
app.kubernetes.io/version: 4.20.3
cdi.kubevirt.io/dataImportCron: rhel9-image-cron <== ここに注目
instancetype.kubevirt.io/default-instancetype: u1.medium
instancetype.kubevirt.io/default-preference: rhel.9
kubevirt.io/dynamic-credentials-support: "true"
name: rhel9
namespace: openshift-virtualization-os-images
resourceVersion: "54250414"
uid: 6b04315c-3e81-4a58-90f5-92d5f352acfb
spec:
source:
snapshot:
name: rhel9-ab4ec16077fe
namespace: openshift-virtualization-os-images
status:
...
ここに、cdi.kubevirt.io/dataImportCron: rhel9-image-cron というのがあります。この dataImportCron なるやつが、データソースから定期的にイメージを引っ張って来ているわけです。
これを見てみましょう。
[root@bastion ~]# oc get dataImportcron rhel9-image-cron -n openshift-virtualization-os-images -oyaml
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataImportCron
metadata:
annotations:
cdi.kubevirt.io/storage.bind.immediate.requested: "true"
cdi.kubevirt.io/storage.import.imageStreamDockerRef: registry.redhat.io/rhel9/rhel-guest-image@sha256:ab4ec16077fe00e3c7efd0b2f6a77571f3645f5c95befc4d917757dc88b2f423
cdi.kubevirt.io/storage.import.lastCronTime: "2025-12-22T08:18:00Z"
cdi.kubevirt.io/storage.import.nextCronTime: "2025-12-22T20:18:00Z"
cdi.kubevirt.io/storage.import.sourceDesiredDigest: sha256:ab4ec16077fe00e3c7efd0b2f6a77571f3645f5c95befc4d917757dc88b2f423
cdi.kubevirt.io/storage.import.storageClass: truenas-iscsi-csi
operator-sdk/primary-resource: openshift-cnv/ssp-kubevirt-hyperconverged
operator-sdk/primary-resource-type: SSP.ssp.kubevirt.io
creationTimestamp: "2025-12-09T00:42:42Z"
generation: 35
labels:
app.kubernetes.io/component: templating
app.kubernetes.io/managed-by: ssp-operator
app.kubernetes.io/name: data-sources
app.kubernetes.io/part-of: hyperconverged-cluster
app.kubernetes.io/version: 4.20.3
kubevirt.io/dynamic-credentials-support: "true"
name: rhel9-image-cron
namespace: openshift-virtualization-os-images
resourceVersion: "57560415"
uid: dc4769f6-1e70-4380-8711-cae6449903c0
spec:
garbageCollect: Outdated
managedDataSource: rhel9
schedule: 18 8/12 * * *
template:
metadata: {}
spec:
source:
registry:
imageStream: rhel9-guest <== ここに注目
pullMethod: node
storage:
resources:
requests:
storage: 30Gi
status: {}
status:
...
この DataImportCron を見ると、imageStream: rhel9-guest から Cron で持ってきていることがわかります。最後にこの ImageStream を見てみましょう。
[root@bastion ~]# oc get imagestream rhel9-guest -n openshift-virtualization-os-images -oyaml
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
annotations:
openshift.io/image.dockerRepositoryCheck: "2025-12-10T17:32:28Z"
creationTimestamp: "2025-12-09T00:42:41Z"
generation: 4
labels:
app: kubevirt-hyperconverged
app.kubernetes.io/component: compute
app.kubernetes.io/managed-by: hco-operator
app.kubernetes.io/part-of: hyperconverged-cluster
app.kubernetes.io/version: 4.20.3
name: rhel9-guest
namespace: openshift-virtualization-os-images
resourceVersion: "54248360"
uid: effd91b3-df58-469f-899d-b743067c9935
spec:
lookupPolicy:
local: false
tags:
- annotations: null
from:
kind: DockerImage
name: registry.redhat.io/rhel9/rhel-guest-image <== ここに注目
generation: 4
importPolicy:
importMode: PreserveOriginal
scheduled: true
name: latest
referencePolicy:
type: Source
status:
dockerImageRepository: ""
tags:
- items:
- created: "2025-12-09T00:42:45Z"
dockerImageReference: registry.redhat.io/rhel9/rhel-guest-image@sha256:ab4ec16077fe00e3c7efd0b2f6a77571f3645f5c95befc4d917757dc88b2f423
generation: 4
image: sha256:ab4ec16077fe00e3c7efd0b2f6a77571f3645f5c95befc4d917757dc88b2f423
tag: latest
はい、この ImageStream は、registry.redhat.io/rhel9/rhel-guest-image というコンテナイメージを Pull していることがわかります。
イメージの自動アップデート
このようになんだかんだありますが元は registry.redhat.io にあるコンテナイメージを引っ張ってきていることがわかりました。
ちょっと待てとここで相席食堂的に止めたい方、鋭いです。
ImageStream が参照しているコンテナイメージのパスには、タグがありません。これはつまり、暗黙的に latest のイメージを Pull していることになります。
実際、上の ImageStream の status.tags.items[].tag を見ると、tag: latest と書かれています。
これはつまり、registry.redhat.io/rhel9/rhel-guest-image が、新しいイメージに update したら、自動的にアップデートされるということになります。
ゆえに テンプレートで参照しているゲスト OS イメージもアップデートされるんですね。
セキュリティパッチや最新のマイナーバージョンがリリースされた際に、VM テンプレートから常に最新の、そしてより安全なゲストOSイメージを利用できるという点で、これは非常に便利です。お為ごかしでもなんでもなく、本当に。
しかしユーザーさんにとっては「こちらにも都合があるので勝手に上げられるのは困るんだが」という方も少なくないでしょう。
そういった場合は、このように GUI の Virt の設定画面から、各イメージごとに自動アップデートを止めることができます。

このトグルスイッチを Off にすると該当する DataImportCron が単純に削除されるだけなので、oc delete dataimportcron xxx でも大丈夫です。
独自のゲスト OS イメージを利用する
標準のテンプレートで提供されていない OS や、カスタマイズした独自のゲスト OS イメージを登録して使いたいというニーズは少なくありません。というかほとんどそうだと思います。
しかしこんなコンテナイメージにして云々、というのはちょっと。。。と思われるかも知れません。
ですが、私は先ほどにサラッと流しましたがこう書きました。「ゲスト OS イメージをコンテナイメージとして扱う必要性はない」
そうです。別にコンテナイメージにして扱う必要は全然無いんです。
可搬性が上がるので複数のクラスタで使う場合はめっちゃ便利なんですが、絶対そうしないといけないということはない。
Virt ではそういう場合でも簡単に独自のゲスト OS イメージを作ることができます。
大まかな流れは以下のとおりです。
- 既存イメージの準備:利用したいゲスト OS のイメージファイル(
qcow2 やiso` など)を準備します。 - PVCへのインポート:準備したイメージファイルを、DataVolume(または CDI のインポート機能)を使って PVC としてクラスタ内にインポートします。
- DataSourceの作成:インポートした PVC を参照する
DataSourceリソースを作成します。 - テンプレートへの差し込み:作成した
DataSourceを利用するように、カスタムの VM テンプレートを定義します。
独自のゲスト OS イメージを作ってみる。
1. 既存イメージの準備
VMware のテンプレートをそのまま使いたいケースが多いと思うので、それをやってみましょう。
まずは VMware のイメージをそのままインポートすることはできないので、インポートできる形式に変換します。qcow2 にしましょう。
データストアからダウンロードするなり何かしらの方法で対象のテンプレートの vmdk ファイルを入手して、qemu-img convert コマンドで qcow2 に変換します。
[root@bastion ~]# qemu-img convert -f vmdk -O qcow2 mig-test-rhel.vmdk mig-test-rhel.qcow2 [root@bastion ~]# ls -l mig-test-rhel.qcow2 -rw-r--r--. 1 root root 9437249536 Dec 23 09:36 mig-test-rhel.qcow2
2. PVCへのインポート
できあがった qcow2 のイメージをインポートします。GUI では PVC を作るメニューから、"With Data upload form" を選ぶことでできます。


しばらくすると upload が終わります。

この時点で PVC が作られています。あと自動的に PVC に紐づいた DataVolume も作られていますが、ここでは使わないです。
[root@bastion ~]# oc get pvc,dv -n demo NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE persistentvolumeclaim/mig-test-rhel-template Bound pvc-fcf0fa64-bc1c-4c6c-9774-3b23e0b74072 316Gi RWX truenas-iscsi-csi <unset> 2m32s NAME PHASE PROGRESS RESTARTS AGE datavolume.cdi.kubevirt.io/mig-test-rhel-template Succeeded N/A 2m32s
3. DataSourceの作成
作られた PVC を指定した DataSource を作ります。こんな具合で spec.source.pvc に指定します。
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataSource
metadata:
name: my-custom-rhel
namespace: demo
spec:
source:
pvc:
name: mig-test-rhel-template # インポート済みの PVC 名
namespace: demo # PVC がある namespace
[root@bastion ~]# oc get datasource -n demo NAME AGE my-custom-rhel 18s
4. テンプレートへの差し込み
独自のゲスト OS イメージを作るのは DataSource まで作れば終わりなのですが、これをテンプレートに差し込んで意味が出てきます。
テンプレートはゼロから自作してもよいですが、標準テンプレートをクローンして編集するのが簡単でよいと思います。
GUI で左メニューから "Template" を開き、"すべてのプロジェクト" を指定すると山のように出ててきます。この中から作りたいスペックに一番近そうなものを選んで、右の ⋮ から "クローン" をします。
必要な情報を入力して独自のテンプレートを作ります。


あともう一息です。テンプレートの画面が表示されるので "パラメータ" タブに移動して、"DATA_SOURCE_NAME" と "DATA_SOURCE_NAMESPACE" に、先程作った DataSource の情報を入力します。

以上で独自のゲスト OS イメージからなるテンプレートを作ることができました。
まとめ
標準のゲスト OS イメージは最初は便利ですが、本格的に本番運用するにはやっぱり独自のイメージを使うことになるでしょう。
ただ独自の OS イメージの管理をインフラ部門が一元的にやるにはメンテが結構大変だという話は聞きます。
なので、コンテナ的な運用管理のスタイルで、ユーザーさんが好きなように独自のイメージを作って管理してもらうというやり方はどうかなと考えます。
具体的には、インフラ部門側は標準のテンプレートをベースの具材として品揃えを管理しつつ、ユーザーさんにセルフサービスでテンプレートを作る方法を提供する、というパターンです。
ガバナンスの面から実現は難しいかもしれませんが、こういったアプローチなど使って従来の運用管理を新たなスタイルに変えていく営みは大事かなと考える年末です。
ということで今日はここまで。