おうち Kubernetes に最適な Synology 公式の CSI Driver がリリースされた話と Cloning / Snapshot feature deep dive
こんにちは。青山(@amsy810)です。
この記事は、Kubernetes Advent Calendar 2021 の 1日目の記事です。
いよいよ今年もアドベントカレンダーの季節が始まりました。今年も1ヶ月間わいわいしていきましょう!
Agenda
おうち Kubernetes
今回はかわいい下駄箱 DC に佇む、箱物ストレージの Synology に新たな CSI Driver を適用していきます。
去年の jparklab/synology-csi CSI Driver に機能実装した話
去年はおうちKubernetes向けのストレージを Synology に刷新したのもあって、jparklab/synology-csi という有志によって作られた 非公式の Synology CSI Driverに対して、PVCとファイルシステムの動的なリサイズを行う VolumeExpansion の機能を実装しつつ、様々な CSI Driver での内部実装の紹介する話をしました。
- CSIではどうやってブロックデバイスの拡張を検知しているか
- CSIではどうやってファイルシステムもPod再起動なしに動的に拡張されているか
- Controller PluginとNode Pluginの役割分担
といった詳細な話が気になる方は、ぜひ去年のも見てみてください。
また、CSI とは何かといった話や、CSI Driver の大枠の仕組みについては去年の記事を見てみてください。
Synology 公式の CSI Driver
今年は VolumeSnapshot あたりを実装しつつ、内部実装を紹介する話でもしようかなぁと思っていました。 しかし、2021-08-31 に Synology 公式 の CSI Driver(SynologyOpenSource/synology-csi)がリリースされていました。しかも Cloning や Snapshot featureにも対応。
そこで今回は公式の Synology CSI Driver について紹介しようかと思います。
SynologyOpenSource/synology-csi の概要
公式の Synology CSI Driver では、事前に NAS 側に複数の HDD/SSD を束ねたストレージプールを定義し、Synology Volume を作成しておきます。 StorageClass は Synology Volume 単位で定義できるようになっているため、HDDとSSD混在環境であれば、一つの筐体でも性能差のある複数の StorageClass を提供することができます。
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: synology-iscsi-storage parameters: location: '/volume1' fsType: 'ext4' dsm: 'xxx.xxx.xxx.xxx' reclaimPolicy: Delete
その後、StorageClass を利用して Dynamic Provisioning されると、PV の単位で LUN が払い出されていきます。 そして、払い出された LUN は iSCSI 経由でアタッチされ、ReadWriteOnceな状態で利用していきます。
Requirements 周りの話
DSM の Requirements
Synology の NAS には、DSM(DiskStation Manager)というオペレーティングシステム(兼 API)が搭載されています。 公式の SynologyOpenSource/synology-csi を利用するには、2021年7月ごろにリリース されたDSM 7.0系以降が必要です。
Kubernetes の Requirements と v1.22 への対応
また、公式にサポートがアナウンスされている Kubernetes version は v1.19 までになっています。 私の Kubernetes 環境は最新の v1.22 ですが、下記の対応をすることで v1.22 でも動作確認済みです。
CSI attacher(external-attacher)が v2.1 系で古いため、VolumeAttachment リソースの API Version が存在しない v1beta1 を参照している
I1125 17:11:55.649047 1 reflector.go:188] Listing and watching *v1beta1.VolumeAttachment from k8s.io/client-go/informers/factory.go:135 E1125 17:11:55.650747 1 reflector.go:156] k8s.io/client-go/informers/factory.go:135: Failed to list *v1beta1.VolumeAttachment: the server could not find the requested resource
=> 対応: CSI attacher のバージョンを上げる(v2.1.0 > v3.3.0)
CSI attacher v3.x 系から attach を行う reconcile 時にも volumeattachment/status patch リクエストが送られるようになり、権限不足でエラーを返す
I1125 17:19:16.077875 1 controller.go:208] Started VA processing "csi-daee6d88cfec18e000fbc38dfa5326a180e73dd47bf62a63eca30a435852cdae" I1125 17:19:16.077894 1 trivial_handler.go:53] Trivial sync[csi-daee6d88cfec18e000fbc38dfa5326a180e73dd47bf62a63eca30a435852cdae] started I1125 17:19:16.077914 1 util.go:38] Marking as attached "csi-daee6d88cfec18e000fbc38dfa5326a180e73dd47bf62a63eca30a435852cdae" W1125 17:19:16.083865 1 trivial_handler.go:57] Error saving VolumeAttachment csi-daee6d88cfec18e000fbc38dfa5326a180e73dd47bf62a63eca30a435852cdae as attached: volumeattachments.storage.k8s.io "csi-daee6d88cfec18e000fbc38dfa5326a180e73dd47bf62a63eca30a435852cdae" is forbidden: User "system:serviceaccount: synology-csi:csi-controller-sa" cannot patch resource "volumeattachments/status" in API group "storage.k8s.io" at the cluster scope
=> 対応: volumeattachment/status への RBAC の権限を追加する
※ Status フィールド自体は v1beta1 からももともと利用されていたため、そもそもvolumeattachment/status への権限は必要そう。PR も出されているのでいずれ修正予定。
# kustomization.yaml patches: - ./patches.yaml patchesJson6902: - target: group: rbac.authorization.k8s.io version: v1 kind: ClusterRole name: synology-csi-controller-role patch: |- - op: add path: /rules/0 value: {"apiGroups": ["storage.k8s.io"], "resources": ["volumeattachments/status"], "verbs": ["get", "list", "watch", "update", "patch"]}
# ./patches.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: synology-csi-controller namespace: synology-csi spec: template: spec: containers: - name: csi-attacher image: k8s.gcr.io/sig-storage/csi-attacher:v3.3.0
Volume の Cloning と Snapshot
SynologyOpenSource/synology-csi では、Cloning と Snapshot をサポートしています。
PVC 作成時に spec.dataSource に複製元 PVC を指定して、複製元の PVC をクローンして PVC 作成する
kind: PersistentVolumeClaim spec: dataSource: kind: PersistentVolumeClaim name: sample-pvc-dynamic
PVC 作成時に spec.dataSource に Snapshot リソースを指定して、PVCを作成する
kind: VolumeSnapshot metadata: name: sample-volumesnapshot spec: source: persistentVolumeClaimName: sample-pvc --- kind: PersistentVolumeClaim metadata: name: sample-pvc-restored spec: dataSource: kind: VolumeSnapshot name: sample-volumesnapshot
今回はせっかくなので SynologyOpenSource/synology-csi を例に、CSI Driver の Cloning と Snapshot の実装を少し深ぼって紹介したいと思います。
Cloning の実装
Cloning の場合、PVCに指定された spec.dataSource の PVC の情報を元に、新たな PVC を作成します。 そのため、CSI Driver の Controller Plugin 側の CreateVolume() 関数に手を加えて実装していきます。 例えば SynologyOpenSource/synology-csi では、spec.dataSource に PVC が指定されていた場合には、その PVC の LUN を元に PV を生成するようになっています。
func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { ...(省略)... if volContentSrc != nil { if srcVolume := volContentSrc.GetVolume(); srcVolume != nil { srcVolumeId = srcVolume.VolumeId } else { return nil, status.Errorf(codes.InvalidArgument, "Invalid volume content source") } } ...(省略)... spec := &models.CreateK8sVolumeSpec{ ...(省略)... SourceVolumeId: srcVolumeId, } lunInfo, dsmIp, err := cs.dsmService.CreateVolume(spec) ...(省略)... } func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (webapi.LunInfo, string, error) { if spec.SourceVolumeId != "" { /* Create volume by exists volume (Clone) */ k8sVolume := service.GetVolume(spec.SourceVolumeId) if k8sVolume == nil { return webapi.LunInfo{}, "", status.Errorf(codes.NotFound, fmt.Sprintf("No such volume id: %s", spec.SourceVolumeId)) } dsm, err := service.GetDsm(k8sVolume.DsmIp) if err != nil { return webapi.LunInfo{}, "", status.Errorf(codes.Internal, fmt.Sprintf("Failed to get DSM[%s]", k8sVolume.DsmIp)) } lunInfo, err := service.createVolumeByVolume(dsm, spec, k8sVolume.Lun) return lunInfo, dsm.Ip, err } ...(省略)... }
synology-csi/controllerserver.go at release-v1.0.0 · SynologyOpenSource/synology-csi · GitHub
synology-csi/dsm.go at release-v1.0.0 · SynologyOpenSource/synology-csi · GitHub
Snapshot の実装
Snapshot の実装は、大きく分けて Snapshot と Restore の 2 つのパートに分かれています。
Snapshot
VolumeSnapshot リソースでスナップショットを管理する機能を実装するには、CSI Driver の Controller Plugin 側に CreateSnapshot()・DeleteSnapshot()・ListSnapshot() の 3 つの関数を実装する必要があります。ストレージバックエンド側でボリュームやスナップショットを生成すればよいため、Node Plugin 側の実装はありません。
例えば SynologyOpenSource/synology-csi では、CreateSnapshot()関数で VolumeSnapshotリソースに指定されている PVC の情報を元に、LUNに対してスナップショットを生成しています。
synology-csi/controllerserver.go at release-v1.0.0 · SynologyOpenSource/synology-csi · GitHub
Restore
Restore 部分は、Cloning と同様に spec.volumeSource に Snapshot が指定された場合の処理を実装することを意味します。 そのため、CSI Driver の Controller Plugin 側の CreateVolume 関数に手を加えていきます。 例えば SynologyOpenSource/synology-csi では、spec.dataSource に VolumeSnapshot リソースが指定されていた場合には、その VolumeSnapshot が紐付いているスナップショットを元に PV を生成するようになっています。
func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { ...(省略)... if volContentSrc != nil { if srcSnapshot := volContentSrc.GetSnapshot(); srcSnapshot != nil { srcSnapshotId = srcSnapshot.SnapshotId } else if srcVolume := volContentSrc.GetVolume(); srcVolume != nil { srcVolumeId = srcVolume.VolumeId } else { return nil, status.Errorf(codes.InvalidArgument, "Invalid volume content source") } } ...(省略)... spec := &models.CreateK8sVolumeSpec{ ...(省略)... SourceSnapshotId: srcSnapshotId, SourceVolumeId: srcVolumeId, } lunInfo, dsmIp, err := cs.dsmService.CreateVolume(spec) ...(省略)... } func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (webapi.LunInfo, string, error) { if spec.SourceSnapshotId != "" { /* Create volume by snapshot */ for _, dsm := range service.dsms { snapshotInfo, err := dsm.SnapshotGet(spec.SourceSnapshotId) if err != nil { continue } lunInfo, err := service.createVolumeBySnapshot(dsm, spec, snapshotInfo) return lunInfo, dsm.Ip, err } return webapi.LunInfo{}, "", status.Errorf(codes.NotFound, fmt.Sprintf("No such snapshot id: %s", spec.SourceSnapshotId)) } ...(省略)... }
synology-csi/controllerserver.go at release-v1.0.0 · SynologyOpenSource/synology-csi · GitHub
synology-csi/dsm.go at release-v1.0.0 · SynologyOpenSource/synology-csi · GitHub
実装関連の参考情報
Controller Server
Node Server
Future work
今回公開された Synology CSI Driver でサポートしているのは SAN 向けの機能です。 NetApp Trident のように ReadWriteMany 向けの Dynamic Provisioning も Synology の Shared folder の機能を使いつつ実装できると、更にユースケースが広がりそうですね。
ソースコードを見てる範囲では、Shared folder 用の DSM API を操作する関数は用意されているが使われていない状態だったので、将来的にはサポートされるかもしれません。 12月に手が空けば、実装しつつ、別日のアドベントカレンダーでも書こうかと思います。
まとめ
あんまり特出して書くことがない程度には完成度が高い状態でした。 Synology CSI Driver を利用することで、箱物ストレージを簡単に得ることができるのでロマンがあります。
また、Stateful なアプリケーションを Kubernetes で運用していく場合、Snapshot の CSI Features を利用したくなったり、エコシステムによってはこの機能自体が必要なケースも出てくるかと思います。その点では、今回公開された公式の Synology CSI Driver は多くの機能がサポートされているため、検証用途にはとても十分です。
余談ですが、Synology の公式サイトには、「個人向け」「中小企業向け」以外にも、「IT 愛好家」向けの項目があって好感が持てます。いわゆる 逸般の誤家庭 向けってやつですね。
逸般の誤家庭 な皆さん、年末年始は Synology CSI Driver を整備して、ぜひ楽しんでみてはいかがでしょうか。
以上、青山(@amsy810)からでした〜