@amsy810's Blog

~Kubernetes / Cloud Native / Docker / etc~

おうち Kubernetes 向けに Synology CSI Driver に VolumeExpansion を実装した話

こんにちは。 青山(amsy810 )です。

この記事は Kubernetes Advent Calendar の 1 日目の記事です。

いよいよアドベントカレンダーが始まりましたね。1か月間わいわいしていきましょう!

どこかの誰かに実装系の記事のほうが面白いよねってフリをされたので、今年は Synology CSI Driver に VolumeExpansion を実装しつつ、内部実装の紹介 をします。 VolumeExpansion は 1日あれば実装が終わるくらいの規模感でした。

おうち Kubernetes

みなさんのおうちの Kubernetes はどこにありますか? ちなみに私は下駄箱にあります。下駄箱DC。上流スイッチもここに集約されています。

f:id:masaya_aoyama:20201130210026j:plain

そして皆さんストレージはどうしてますか? hostPath や Local PV で我慢してる人がいたり、Longhorn や Rook/Ceph で分散ストレージを組んでいる人もいるかと思います。 我が家では Synology の NAS をストレージバックエンドとして使っています。つまり、自宅に安価に箱物ストレージがある気分に浸れるのです。 (写真で青色 LED が光っているのが筐体です。オールフラッシュかつ、UPS につないで可愛がってます。)

余談ですが、Synology の公式サイトには、「個人向け」「中小企業向け」以外にも、「IT 愛好家」向けの項目があって好感が持てます。いわゆる 逸般の誤家庭 向けってやつですね。

f:id:masaya_aoyama:20201201085133p:plain

Synology CSI Driver

昨今はコンテナオーケストレーターとストレージ機器の連携には、CSI(Container Storage Interface)を介して行われるのが一般的です。 有志によって Synology の NAS 向けの CSI Driver が公開されています。つまり、Kubernetes から Synology の NAS をバックエンドとして利用することができるようになっています。なお、iSCSI でのブロックデバイスのボリュームマウントに対応しています。ReadWriteMany で利用したい場合は、別途 NFS マウントなどを検討してください。

github.com

CSI の Document にも、Sample Drivers として取り上げられています。この Driver は Synology 社に公式サポートされているものではなく、非公式な Driver なので商用での利用などは注意したほうが良いでしょう。

kubernetes-csi.github.io

Synology CSI Driver では、Synology の Volume(複数の物理 Disk を束ねた StoragePool を論理的に切り出したもの)単位で StorageClass を切って使うようになっています。そのため、Kubernetes 向けと自宅向けに分けて使うこともできます。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    # set this to false if you do not want this storage class to be default
    storageclass.kubernetes.io/is-default-class: "true"
  name: synology-iscsi-storage
provisioner: csi.synology.com
parameters:
  location: '/volume1' # ここに Kubernetes 向けに切った Volume を指定する
  type: 'BLUN'
reclaimPolicy: Retain
allowVolumeExpansion: true

CSI Driver ことはじめ

CSI Driver では Controller PluginNode Plugin の 2 つのコンポーネントがあります。Controller Plugin はコンテナオーケストレーターで 1 つ、Node Plugin は各ノードに 1 つ起動します。Kubernetes の場合は、Controller Plugin は StatefulSet、Node Plugin は DaemonSet で起動させることが多くなっています。Controller Plugin はどこで実行可能な処理を、Node Plugin は各ノード上で直接実行する必要があるマウントなどの処理を担当します。

CSI には仕様があり、ボリューム作成時やアタッチ時のライフサイクルや、RPCで呼ばれる関数が取り決められています。

github.com

例えば、Volume が Dynamic Provisioning でデプロイされ、Pod が起動する際の最低限の実装は下記のとおりです。リクエストが発行されると、CreateVolume() によりボリュームが作成されます。その後、ControllerPublishVolume() によりノードに対してボリュームをアタッチし、NodePublishVolume() によりノードでアタッチされたボリュームをマウントする流れになっています。削除する際には、逆の順番で行われていきます。

CreateVolume() は Controller Plugin 側で処理を行います。ControllerPublishVolume() と NodePublishVolume() はそれぞれ Controller Plugin と Node Plugin 用に用意されており、それぞれで実行します。

f:id:masaya_aoyama:20201201055115p:plain

(なお、いくつかの CSI Driver の実装を眺めてみましたが、実際に各関数で行われている処理は Driver によってまちまちだったので注意が必要そうです。ボリュームをノードで使えるようにするまでに、Controller側での処理とNode側での処理の2つがあると考えておくと良いでしょう。)

Synology CSI Driver では、Dynamic Provisioningされた Volume は LUN として払い出されます。

f:id:masaya_aoyama:20201201070310p:plain

VolumeExpansion の実装について

さて、本題です。 ボリューム拡張には、ControllerExpandVolume()NodeExpandVolume() の 2つの Interface を利用します。Synology CSI Driver ではこれら2つの関数が用意されていないため、実装する必要があります。

大まかには、ControllerExpandVolume() ではストレージバックエンドにボリューム自体の拡張を行うリクエストを行い、NodeExpandVolume() ではアタッチされたボリュームのファイルシステムの拡張を行います。

Synology CSI Driver の場合、ControllerExpandVolume() では Synology の API を叩いて LUN の拡張を行います。 https://github.com/jparklab/synology-csi/pull/46/files#diff-80793083e0532aa32cf03aecc5ee3efefced14c5b0d7af3ca7cfc0b03e2b9af8R58-R124

この実装のみを入れた状態で LUN を 10GB から 15GB に拡張後にノード側でブロックデバイスの情報を見てみると、拡張された値になっていません。

root@k8s1:/# fdisk -l | grep sdb
Disk /dev/sdb: 10 GiB, 10737418240 bytes, 20971520 sectors

ただ LUN を拡張しただけではブロックデバイスの拡張を検知できていないため、NodeExpandVolume() で rescan パスに対して書き込みを行い検知するようにします。こういった各ノード上で実行しなければならない処理のために、Controller Plugin と Node Plugin は別れています。 https://github.com/jparklab/synology-csi/pull/46/files#diff-4ff8ed7064d3ab0eae29d29d061a7a0799a0b630a9a5c76d6ab2bb333bef7124R307-R332

OpenStack Cinder CSI などでも rescan パスに対して書き込みを行い、変更を検知する実装になっています。

# 手動で実行した場合の例
root@k8s1:/# echo 1 > /sys/block/sdb/device/rescan

root@k8s1:/# fdisk -l | grep sdb
Disk /dev/sdb: 15 GiB, 16106127360 bytes, 31457280 sectors

ブロックデバイス拡張後には、ファイルシステムの拡張を行う必要があります。これも NodeExpandVolume() 側で行います。 ファイルシステムの拡張には、"k8s.io/kubernetes/pkg/util/resizefs" パッケージが利用できます。 https://github.com/jparklab/synology-csi/pull/46/files#diff-4ff8ed7064d3ab0eae29d29d061a7a0799a0b630a9a5c76d6ab2bb333bef7124R334-R338

このパッケージでは、内部的には resize2fs や xfs_growfs をコマンド実行で呼び出しているだけなので、Node Plugin のプログラムを実行する環境にはこれらのコマンドが必要です。 https://github.com/kubernetes/kubernetes/blob/master/pkg/util/resizefs/resizefs_linux.go#L29

なお、このパッケージを使わずに、resize2fs や xfs_growfs を直接使っている CSI 実装もありました。 思ったよりかは愚直な実装なのは少し意外でした。

今回のように Controller Plugin の処理のあとに Node Plugin 側でも Rescan や Filesystem の拡張などの処理が必要な場合、ControllerExpandVolumeResponse を返す際に NodeExpansionRequired=true で返してあげてください。 https://github.com/jparklab/synology-csi/pull/46/files#diff-80793083e0532aa32cf03aecc5ee3efefced14c5b0d7af3ca7cfc0b03e2b9af8R121-R124

これだけでは実は動きません。最後に、この CSI が VolumeExpansion に対応していることを示すように、Capabilities として登録しておく必要があります。

https://github.com/jparklab/synology-csi/pull/46/files#diff-e8a7e777d80a14b455bdbf7aae3f28ad8082ffa0a06579e11cc1af741b5f98f7R95 https://github.com/jparklab/synology-csi/pull/46/files#diff-4ff8ed7064d3ab0eae29d29d061a7a0799a0b630a9a5c76d6ab2bb333bef7124R369-R388

Synology CSI Driver の TODO

VolumeExpansion の後といえば、Snapshot の実装もしたいなと思っています。 Synology は Snapshot にも対応しているため、CSI 側に実装をいれることで使えるようになる見込みです。

また、現在は iSCSI を使った ReadWriteOnce なマウントのみサポートしていますが、共有フォルダ機能を使って ReadWriteMany な Dynamic Provisioning もサポートしたいなとも思っています。

また、今回の実装とは関係ありませんが、Synology CSI Driver を見ていていくつか修正したほうが良さそうな点がありました。 1つ目に、iSCSI Target が消えないときがあるようなのでその辺りも考慮した実装にする必要があります。 2つ目に、各 csi-provisioner や csi-resizer 単位で Pod が作られているため、sidecar をまとめた Pod にしたほうが良さそうです。

この辺りも暇を見つけて実装できたらと思います。

まとめ

今回は CSI の VolumeExpansion の実装について紹介しましたがいかがでしたか?

また、おうち Kubernetes がある方は、Synology の NAS と Synology CSI Driver を使って、更に快適な環境にしてみてはいかがでしょうか?

参考:Synology NAS について

Synology の NAS は、NFS や CIFS など様々なプロトコルをしゃべることができます。 我が家ではメディア系のファイルや PDF ファイルなどが保存されており、MaciPadiPhone などからいつでもアクセスできるようにしてあります。

また、Synology の NAS には、10G NIC がついたモデルとついていないモデルの 2 つがあります。 私が購入した DS620Slim は 10G NIC がついていないモデルで、1G * 2 の NIC がついています。250 MB/s 程度までしかスループットはでませんが、DS620Slim はその名の通りかなりコンパクトな筐体に 2.5 inch disk が 6 本まで刺さるので、かなり集約率は高い筐体です。 対向の Switch 側が対応していれば Bonding にも対応しているようですが、今回はそれぞれが影響しあわないように「おうち KubernetesCSI 経由で使う NIC」と「NFS などで各種デバイスから接続する NIC」 で分けています。

また、Synology 独自の RAID (Synology Hybrid RAID)では 1本〜6本まで順に増やしていくことが可能なため、スモールスタートするにはうってつけです。 またディスクサイズが揃っていなくてもチャンクに分割することで最大限ディスクを使えるような仕組みになっています。

www.synology.com

参考:Synology CSI Driver の API について

Synology API に接続するためのコードは Synology CSI Driver の pkg/synology/api/ 以下にLUN、Target、Volume を操作するための API が用意されています。ここでいう Volume は Synology の Volume(/Volume1 など)を指しています。

なお、現状の Synology CSI Driver に必要な部分しか実装されていないため、ボリューム拡張用に LUN API に Update() 関数を生やしています。