この記事は2019年12月時点におけるvirtiofsの簡単な紹介です。
概要
公式サイト: https://virtio-fs.gitlab.io/
virtiofsはホストと(複数の)ゲストVM間でディレクトリを共有するための新しいファイルシステムで、redhatのエンジニアを中心に開発されています。
主なユースケースとしては、まず軽量VM(kata-containerなど)のルートファイルシステムにvirtiofsを使うことが挙げられます。ゲストへの不要なファイルコピーを削減することによりブート時間が短縮される等の利点があります。また他のユースケースとして、ゲストからファイルシステムの詳細が隠蔽されることも挙げられます。ゲストからは共有されているディレクトリのファイルシステムの詳細は見えないので、例えばネットワークファイルシステムのIPやセキュリティ等の設定をゲストで気にする必要がなくなります。
ホストとゲスト間でディレクトリを共有する方法としてNFSや9pfsなどのネットワークファイルシステムが既にあります。しかしこれらはネットワークスタック/プロトコルを利用しており、仮想環境(同一マシンに存在するホストとゲストの通信)の利用に最適化されているわけではありません。またネットワークファイルシステムのsemanticsはローカルファイルのsemanticsと異なる面が多くあり、(ゲストの)アプリケーションの挙動が影響を受ける場合があります。
virtiofsではこれらの問題を改善すべく、(1) IO性能が高く、(2) ゲストに対してローカルファイルシステム同様のsemanticsを提供するファイルシステムであることを目的としています。そしてこれを実現するため、ネットワークスタックに依存がなく、かつlinux VFSのインタフェースに近いFUSEプロトコルをベースに(一部拡張して)開発が行われています1。
通常のFUSEファイルシステムではユーザー空間で動くファイルシステムデーモンがkernelからFUSEリクエストを受け取り、リクエストに沿った処理を行います。virtiofsではデーモンがホスト(のユーザー空間)に存在し、ゲストからFUSEリクエストを受け取り、必要に応じてホストのファイルシステムとやり取りを行います。なおゲストとデーモン間のやりとりはDPDKやSPDKと同様にvirtio vhost-userにより行われます2。
virtiofsについては公式のdesign documentやメイン開発者によるkvm forum 2019のスライド等も参照して下さい。
status
virtiofsを利用するためにはlinuxおよびqemuの対応が必要です。現時点(2019/12)の状況ですが、基本機能の開発は概ね完了しており、kernelパートはLinux 5.4でマージされています(ただし後述するDAXの機能はまだ含まれていません)。一方でqemu側はvhost-user-fs-pciのコードは4.2でマージされたものの、デーモンのコードは現在レビュー中となっています。Linux+qemuで実際に動かしてみたいという方は公式サイトの説明を参照してください。
一方、kata-containerのvirtiofs対応はすでに活発に行われており、experimentalではあるもののv1.7からvirtiofsを使用することができます。簡単に動くのでその方法を次に説明します。
kata-containerでの利用方法
とりあえず現時点で簡単にvirtiofsを試すにはkata-containerを使うことです。実際にコンテナのrootfsとして共有ディレクトリが使用されていることを確認してみます。
※ 以下はkata-container v1.9での挙動です。繰り返しになりますがexperimentalのため今後色々と変更があると思います。事実すでにkata-containerに使用されているvirtiofsとupstreamにマージされたvirtiofsを比較するとマウントオプション等に違いがあります
インストールとコンテナの立ち上げ
まず最新版のkata-containerをkata-deployコマンドを使用してインストールします (/opt/kata以下にファイルが置かれます):
# docker run --runtime=runc -v /opt/kata:/opt/kata -v /var/run/dbus:/var/run/dbus -v /run/systemd:/run/systemd -v /etc/docker:/etc/docker -it katadocker/kata-deploy kata-deploy-docker install
あとはdockerのruntimeにkata-qemu-virtiofsを指定するだけでvirtiofsが使用されます:
# docker run --runtime=kata-qemu-virtiofs -it busybox
コンテナの中でmountを確認してみると、ルートファイルシステムにvirtiofsが使用されていることがわかります3:
(container内)
/ # mount -t virtio_fs (注: upstreamの版ではvirtio_fsではなくvirtiofsになっています)
kataShared on / type virtio_fs (rw,nodev,relatime,user_id=0,group_id=0,default_permissions,allow_other,dax)
kataShared on /etc/resolv.conf type virtio_fs (rw,nodev,relatime,user_id=0,group_id=0,default_permissions,allow_other,dax)
kataShared on /etc/hostname type virtio_fs (rw,nodev,relatime,user_id=0,group_id=0,default_permissions,allow_other,dax)
kataShared on /etc/hosts type virtio_fs (rw,nodev,relatime,user_id=0,group_id=0,default_permissions,allow_other,dax)
いくつかの設定ファイルもマウントされているように見えますが、これはbind mountされているためです。
ホストのプロセスを確認するとvirtiofsデーモン(virtiofsd)が起動していることがわかります:
(host内)
$ pgrep -a virtiofsd
13154 /opt/kata/bin/virtiofsd --fd=3 -o source=/run/kata-containers/shared/sandboxes/<container ID> -o cache=always --syslog -o no_posix_lock -f
ここでsourceに指定されているディレクトリがvirtiofsで使用する共有ディレクトリになります。
共有ディレクトリの確認
実際にホストとゲストでディレクトリが共有されていることを確認します。
まず、ホストにおいてsourceに指定されているディレクトリを見てみます:
(host内)
# ls /run/kata-containers/shared/sandboxes/<container ID>
<container ID>
<container ID>-resolv.conf
<container ID>-hosts
<container ID>-hostname
# ls /run/kata-containers/shared/sandboxes/<container ID>/<container ID>
rootfs
# ls /run/kata-containers/shared/sandboxes/<container ID>/<container ID>/rootfs
bin dev etc home mnt proc root sys tmp usr var
共有ディレクトリの直下にはいくつかの設定ファイルがあり、更にもう一つのcontainer IDのディレクトリの下にrootfsがあることがわかります4。
このディレクトリの内容がゲストと共有されているため、実際にゲストで適当なファイルを作成するとその内容がホストから読めることが確認できます。
(container内)
/ # echo abc > XXX
/ # ls
XXX bin dev etc home mnt proc root sys tmp usr var
(host内)
# ls /run/kata-containers/shared/sandboxes/<container ID>/<container ID>/rootfs
XXX bin dev etc home mnt proc root sys tmp usr var
# cat /run/kata-containers/shared/sandboxes/<container ID>/<container ID>/rootfs/XXX
abc
この逆にホストからファイルを作った場合もゲストから読むことができます。
DAXについて
virtiofsの目標の1つは高いIO性能を実現することですが、このための機能の1つがDAXです。DAXはDirect Accessの略でよく不揮発メモリの文脈で使用される言葉です5。ただしvirtiofsのDAXは実際の不揮発メモリは関係がなく、ゲストがゲストのページキャッシュを使用せずにホストメモリにアクセスすること = (複数の)ゲストとホストでホストメモリ(ページキャッシュ)を共有する、という意味です。データがメモリ上にある場合はゲスト-ホスト間のコミュニケーションは一切ないため性能が向上します。またページキャッシュを共有することでデータの変更が即座に他のゲスト/ホストに見える(ローカルファイルのsemanticsと同じ)という利点やメモリ使用量が減るという利点もあります。
DAXの仕組みについてざっくりと説明します。まずvirtioを使用するためにはqemuの起動時にvirtioデバイスを加える必要がありますが、これはゲストからはPCIとして認識されます6。そしてPCIにはそのデバイスのメモリ領域を示すBARという制御レジスタがあります。DAXの機能はこのBARから見えるメモリ領域にデータをmmapしてアクセスすることで実現します7。当然ながら領域のサイズは限られているので、どのデータをBAR空間のどこにmap/unmapするのかをvirtiofsで制御します(DAX windowと呼んでいます)。またこのためにmap/unmapする領域をリクエストするためのFUSEプロトコルも追加されています。
なおkernel 5.4にマージされたvirtiofsにはまだDAXサポートの機能は入っていないので、この機能を試したい人は開発ブランチを使用して自分でコンパイルするか、kata-containerを使用してください (kata-container版のvirtiofsには既に組み込まれています)。
さいごに
qemuにマージ予定のvirtiofsデーモン(virtiofsd)はCで実装されていますが、当然ながら別の実装も可能です。実際、rust(crosvm)によるデーモンの実装も行われています8。またあまり詳しい情報は見つかりませんでしたが、すでにvirtiofsとSPDKを組み合わせて使用する方法を考えている人もいるようです9。upstreamにコードがマージされてきているので、来年はvirtiofsの利用ケースも増えていくのではないかと思います。
-
FUSEには長年の実績があるという利点もあります。 ↩
-
vhost-userについて大雑把に説明をすると、初期化等の制御処理はqemuを介して行うものの、データのやり取りについては共有メモリ上に作成されたvirtqueueを利用することでqemuを介さずにゲストとホストユーザー空間上のプロセス(ここではvirtiofsデーモン)がやり取りできるという仕組みです。vhost-userがでてきた背景などについては例えばこの記事のシリーズが参考になります ↩
-
runtimeにkata-qemuを使用すると従来の9pfsが使用されていることがわかります ↩
-
共有ディレクトリ直下がそのままゲストに見えていないのはpivot_rootしているためでしょう ↩
-
ページキャッシュを利用せずにデバイスへ直接アクセスすること ↩
-
virtiofsではvhost-user-fs-pciというデバイスをqemuの起動オプションに加えることで、ゲストがvirtiofsが利用できることを認識します。 ↩
-
なおこの領域はゲストからは不揮発デバイスのように見えているので、kernelコードはDAXのコードを利用します ↩
-
Introduction of SPDK Vhost-fs target to accelerate file access in VMs and containers, SDC 2019 ↩