DAOS用のCSI driverを書いて、KubernetesのPodからアクセス出来るようにした話。
コードはここ
はじめに
私は元々スパコンのファイルシステム開発をしていたので、その辺りの知見は若干あった。一方で、最近気が向いた時にKubernetesを触るようになって、CSI(Container Storage Interface)というものの存在を知った。スパコンのファイルシステムのCSI driverはあまり実装されてなさそうだし、勉強がてら試しに作ってみるかというのがスタートだったのだが、調べてみたらスパコン界隈でデファクトのLustreのCSI driverは既に存在していたし、BeeGFSのそれも既に実装されていた。それでまだ誰も手をつけていなさそうだったのがDAOSだったので、DAOSのCSI driverを書いてみることにした。
DAOSとは
DAOS (Distributed Asynchronous Object Storage)とは、Intelが主導で開発している不揮発性メモリ向けのオブジェクトストレージである。国内での導入例は未だ聞かないが、海外では使われ始めている(今年のUsers Groupのアジェンダを見る限りロシアのRSJ GroupやCERNなどで導入されている様子)。特に、2022年稼働予定の米国Auroraでは230PBという大規模な導入が決まっており、Lustre(こちらは150PB)とどのように組み合わせて運用されるか個人的にとても楽しみなところである。Auroraの運用実績次第では、現在スパコンのファイルシステムとしてデファクトのLustreを置き換えるものになると思われる。
DAOSの基本
DAOSのノード種別はStorage NodeとClient Nodeの二種類に分かれる。前者がいわゆるサーバで後者がクライアントである。Storage Nodeのコンポーネントは、ストレージの管理操作を処理するControl Planeとデータアクセスを処理するData Planeの二つのコンポーネントで構成され、Client Nodeで実行されるアプリケーションやコマンドなどはこれらのコンポーネントにリクエストを送ることでストレージ領域の予約やファイルアクセスを行う。(下図参照)
引用元: https://github.com/daos-stack/daos/tree/master/src#daos-components
Client NodeからControl Planeにアクセスするコンポーネントがdmg
とdaos_agent
の2種類存在するが、前者は管理者向け、後者はユーザ向けのコンポーネントであり、それぞれで出来ることが若干異なる。DAOSを利用する際には、最初にプールというストレージ領域を作成(予約)し、その中に各種APIに沿ったコンテナ(S3のバケットのようなもの)を作成し、利用する。プールの管理(作成やACLの設定など)は管理者の仕事でdmg
コマンドで可能になっており、コンテナの管理は一部を除いてユーザの仕事でありdaos_agent
を通じて行われる(実際に使うのはdaos
コマンド)。後ほど実際のコマンドを示しながら再度簡単に説明する。
Data Planeも気になるところだが、今回の記事ではあまりここを知っていても意味がない(+私がまともに説明できない)ので、説明は省略する。公式ドキュメントやGitHubのページ、Communityのページなど資料はたくさんあるので興味がある方はそちらを参照いただきたい。一つだけ補足しておくと、DAOSはオブジェクトストレージと先に述べたが、オブジェクトストレージのIFはどちらかというと開発者向けのもので、ユーザはその上に実装されたPOSIXやMPI-IOなど各種APIを使ってデータにアクセスする。今回はDAOS v1.2を使っているが、ドキュメント化されている最新バージョンのv2.2だと、Hadoop、Sparkなどもサポートしているようである。
DAOSが登場した背景については過去に記事を書いたので興味があれば読んでいただきたい。
プール作成~ファイルアクセスまで
上で述べたプール作成~ファイルアクセスまでをコマンドを合わせて紹介する。今回作ったCSI driverの中ではこれから説明する処理の内、コンテナの作成、マウントのコマンドを直にたたいている(DAOSのコマンドはGoで実装されているが、とりあえず動くことを優先してサボっている)。
以下で紹介するコマンドはここに則ってDAOSサーバ/クライアントを起動した後に、クライアントにログインすると使える。ストレージとしてメモリを不揮発性メモリ代わりに使っているので、ホストメモリは64GBほど必要になっている。
起動した時点でファイルシステムのマウントまで終わっているのだが、説明の都合上元々マウントされているファイルシステムはアンマウントし、コンテナやプールは削除した後の実行結果を紹介する。
プールの作成
不揮発性メモリとSSDのサイズを個別に指定することも出来るが、ここでは一括で指定している。
今回は不揮発性メモリ(実体はメモリ)のみの環境のため全てSCM割当になっている。
# dmg pool create -i --size=8G
Creating DAOS pool with automatic storage allocation: 8.0 GB NVMe + 6.00% SCM
Pool created with 100.00% SCM/NVMe ratio
-----------------------------------------
UUID : e4b1427c-092c-48bd-9761-462eea30e306
Service Ranks : 0
Storage Ranks : 0
Total Size : 8.0 GB
SCM : 8.0 GB (8.0 GB / rank)
NVMe : 0 B (0 B / rank)
作成されたpoolのownerはdmg
を実行したユーザ/グループになっている。
# dmg pool get-acl --pool=e4b1427c-092c-48bd-9761-462eea30e306
# Owner: daos_server@
# Owner Group: daos_server@
# Entries:
A::OWNER@:rw
A:G:GROUP@:rw
コンテナの作成
先に作成したプールとコンテナのタイプを指定することでコンテナを作成することが出来る。
# daos cont create --pool=e4b1427c-092c-48bd-9761-462eea30e306 --type=POSIX
Successfully created container 3f9687fc-de8f-49e9-b1c2-7eb681464e2e
マウント、ファイルアクセス
DAOSをファイルシステムとしてマウントする場合はdfuse
コマンドを使う。DAOSではFUSEを用いているためアンマウントにはfusermount3
コマンドを使う。
# dfuse --mountpoint dfuse --pool=e4b1427c-092c-48bd-9761-462eea30e306 --cont=3f9687fc-de8f-49e9-b1c2-7eb681464e2e
# df dfuse
Filesystem 1K-blocks Used Available Use% Mounted on
dfuse 7812500 144 7812357 1% /home/daos/dfuse
# ls dfuse
# echo hoge > dfuse/file
# fusermount3 -u dfuse
# dfuse --mountpoint dfuse --pool=e4b1427c-092c-48bd-9761-462eea30e306 --cont=3f9687fc-de8f-49e9-b1c2-7eb681464e2e
# cat dfuse/file
hoge
後片付け
作成の逆をやるだけ。
# fusermount3 -u dfuse
# daos cont destroy --pool=e4b1427c-092c-48bd-9761-462eea30e306 --cont=3f9687fc-de8f-49e9-b1c2-7eb681464e2e
Successfully destroyed container 3f9687fc-de8f-49e9-b1c2-7eb681464e2e
# dmg pool destroy --pool=e4b1427c-092c-48bd-9761-462eea30e306
Pool-destroy command succeeded
# dmg pool list
No pools in system
CSIとは
Container Storage Interface (CSI)はKubernetesなどのコンテナオーケストレータ上で実行されるコンテナに対して、ファイルシステムなどのストレージを公開するための標準仕様である。サードパーティのストレージプロバイダはCSI driverを実装し、クラスタにデプロイすることでストレージをコンテナに公開することが出来る。
引用元: https://github.com/kubernetes/design-proposals-archive/blob/main/storage/container-storage-interface.md#recommended-mechanism-for-deploying-csi-drivers-on-kubernetes
CSI driverはController PluginとNode Pluginというコンポーネントに別れてデプロイする(上図参照)。
- Controller Plugin: ボリュームの管理(作成やノードへのアタッチなど)を行うPodで、StatefulSetまたはDeploymentとしてデプロイされる。Controller Pluginは、Kubernetes APIを監視し必要な処理を行う。例えば、StorageClassのprovisionerとしてCSI Driverが指定されていて、そのStorageClassを指定してPVCが定義された場合、Controller Pluginはこれを検知しボリュームの作成を行う。
- Node Plugin: ノード上でボリュームのフォーマットやマウントを行う。ノード毎に必要なPodのためDaemonSetとしてデプロイされる。Node Pluginはホスト上のパス(図では/var/lib/kubeletと書かれている)にファイルシステムをマウントし、MountPropagationという仕組みを使ってアプリケーションのPodにもマウントを伝搬する。
CSI driverの実体はIdentityサービス、Nodeサービス、Controllerサービスの3種類のgRPCサービスである。Identityサービスはdriverのバージョンなどを返すサービス、NodeサービスはNode Pluginの処理をするサービス、ControllerサービスはController Pluginの処理をするサービスである。Controller Pluginを実行するにはControllerサービスとIdentityサービス、Node Pluginを実行するにはNodeサービスとIdentityサービスが必要になる。
CSI driverをKubernetes環境にデプロイし使えるようにするためは、driverの登録やKubernetes APIなどの監視も行わないと行けない。Kubernetesコミュニティはこれらの処理を行うコンテナ(図の赤で塗りつぶされたコンテナ)を準備しておいてくれていて、これらのコンテナをCSI driverのコンテナ(図の緑で塗りつぶされたコンテナ)のサイドカーとして用いることで、CSI driverの可搬性を損なわずにKubernetes環境にデプロイすることが出来る。サイドカーコンテナとCSI driver間の通信はUnix Domain Socketで行われる。
より詳細な情報は、参考リンクを参照していただきたい。
簡単な作りの説明
今回は既にDAOSのプールは事前に管理者が作成済みという想定で、CSI driverはコンテナの作成、マウントのみを行うため、Node Pluginのみデプロイしていている(コンテナの作成はNode Pluginの仕事ではない気がしているが)。Contoller PluginはデプロイしていないしControllerサービスも一切実装していない。
また、現状はEphemeral Volumesのみ使える。以下のようなマニフェストでPodをデプロイすると、Node PluginのCSI driverコンテナで動いているNodeサービスに対してNodePublishVolume
リクエストが発行される。リクエストにはマニフェストにかかれているuid
, poolid
に加えてホスト上のマウントパスが含まれているで、これらの情報を使って先に書いたDAOSコンテナ作成、ファイルシステムのマウントをコマンド直書きでやっている。実装はここ(汚い)。
kind: Pod
apiVersion: v1
metadata:
name: test-app
spec:
...
volumes:
- name: dfuse
csi:
driver: dfuse.csi.k8s.io
volumeAttributes:
uid: "0"
poolid: "7170ec68-52d5-4f39-98fb-27494cabb47c"
Pod終了時にはNodeUnpublishVolume
リクエストが発行されるのでそこでアンマウント、コンテナの削除を行っている。
動作例
動かし方については、READMEに書いた以上のことはないのだが、Podからアクセスできている出力を貼っておく。
# cat app.yaml
kind: Pod
apiVersion: v1
metadata:
name: test-app
spec:
containers:
- name: test
image: busybox
volumeMounts:
- mountPath: "/data"
name: dfuse
command: [ "sleep", "1000000" ]
volumes:
- name: dfuse
csi:
driver: dfuse.csi.k8s.io
volumeAttributes:
uid: "0"
poolid: "7170ec68-52d5-4f39-98fb-27494cabb47c"
# kubectl create -f app.yaml
pod/test-app created
# kubectl get po
NAME READY STATUS RESTARTS AGE
test-app 1/1 Running 0 6s
# kubectl exec -it test-app -- /bin/sh
/ # df
Filesystem 1K-blocks Used Available Use% Mounted on
overlay 959863856 174735984 736299664 19% /
tmpfs 65536 0 65536 0% /dev
tmpfs 49402308 0 49402308 0% /sys/fs/cgroup
dfuse 7812500 281 7812219 0% /data ★
/dev/nvme0n1p2 959863856 174735984 736299664 19% /dev/termination-log
/dev/nvme0n1p2 959863856 174735984 736299664 19% /etc/resolv.conf
/dev/nvme0n1p2 959863856 174735984 736299664 19% /etc/hostname
/dev/nvme0n1p2 959863856 174735984 736299664 19% /etc/hosts
shm 65536 0 65536 0% /dev/shm
tmpfs 49402308 12 49402296 0% /var/run/secrets/kubernetes.io/serviceaccount
tmpfs 49402308 0 49402308 0% /proc/acpi
tmpfs 65536 0 65536 0% /proc/kcore
tmpfs 65536 0 65536 0% /proc/keys
tmpfs 65536 0 65536 0% /proc/timer_list
tmpfs 65536 0 65536 0% /proc/sched_debug
tmpfs 49402308 0 49402308 0% /proc/scsi
tmpfs 49402308 0 49402308 0% /sys/firmware
/ # dd if=/dev/zero of=/data/file bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.0GB) copied, 0.867793 seconds, 1.2GB/s
おわりに
とりあえず無理やり動くようにしただけなので、色々ひどい状況になっているが、作る中で仕組みが何となくわかってきたので、今後アップデートしていきたい。
CSI関連の参考資料
-
KubernetesにおけるContainer Storage Interface (CSI)の概要と検証
日本語で書かれているページ。全体像を把握するのに良い。ちなみに検証しているドライバはEOLでサイドカーコンテナのバージョンが古いとかでそのままでは動かない(はず)。 -
Storage | Kubernetes
CSIに限った話ではないが、KubernetesのVolumeについての公式ドキュメント。わからないことがあればまずはこれを読む。 -
Production Kubernetes
VMwareから無料で配布されている今年オライリーから出た本。4章のCSIの説明は比較的わかりやすい。というかそれ以外も良さそう。 -
How to write a Container Storage Interface (CSI) plugin
作り終わった後に見つけたのだが、実装について簡潔にまとめられている。記事の中で出ている実装も参考になりそう。CSI driverを作りたくなったらまずここを読んでみて、後は↓で紹介するspecや実装例を参考にしてがちゃがちゃやるのが良さそう。 -
Kubernetes CSI in Action: Explained with Features and Use Cases
How to write...
と同じブログ。他の情報で補完出来る気もするがとりあえず目を通しておくと良さそう。 -
Introduction - Kubernetes CSI Developer Documentation
こちらも他の情報で保管できる気がするが、公式ドキュメントなので困ったら参照すると良い。 -
Container Storage Interface (CSI)
CSIのspec。RPCの説明やエラーコードの定義など書かれている。 - CSI Volume Plugins in Kubernetes Design Doc
-
CSIドライバサンプル(EOL)
EOLだがNFSのCSI driverの実装(pkg/nfs
)は小さいので最初に読むのに良い -
csi-driver-nfs
今回はこちらのコードを一部流用改造して作った。 -
csi-driver-hostpath
ワーカーノードのパスをコンテナに見せるためのCSI driver。簡単に試せるのでとりあえず触りたければこれを触ってみると良い。 -
csi-test
Kuberenetes-CSIから出されてるテスト実装。記事を書いてる中で見つけたのでほとんど目を通していないが、これをベースに作れば色々はかどりそうな気がする。(もっと早く知りたかった)