[Kubernetes]Dynamic Provisioningの動作を確認する 

#はじめに
Dynamic Provisioningの動作を確認するために、StorageClassの設定が必要になります。
これまでのPersistentVolume（PV）/PersistentVolumeClaim（PVC）の操作ではPVを事前に作成してましたが、Dynamic Provisioningを使ってClaimする場合、PVが動的に作成されるようになります。
今回はこのStorageClassを設定して、Dynamic Provisioningの動作を確認したいと思います。

#Storageの準備
StorageClassを設定する際に、Provisionerを指定する必要があります。

>[The StorageClass Resource](https://kubernetes.io/docs/concepts/storage/storage-classes/#the-storageclass-resource)

Provisionerは利用する各クラウドサービスなどのVolumePluginに応じて指定します。残念ながらクラウドサービスを利用する予算がありませんので、ここはローカル環境にZFSを構築したいと思います。
ZFSはSolarisで慣れ親しんだファイルシステム兼ボリューム管理ツールで、Linuxにも移植されています。今回はCentOS 7上に構築します。

##ZFSストレージの構築
少々手こずってしまったので、ログが整理できていません。。。
これらを参考に構築しました。検索すると他にも出てきます。


https://github.com/openzfs/zfs/wiki/RHEL-and-CentOS
https://ameblo.jp/mikan2k/entry-12319758936.html
http://bobgosso.blog13.fc2.com/blog-entry-547.html

構築した環境はこちらです。

```
[zfs_server]$ cat /etc/redhat-release
CentOS Linux release 7.8.2003 (Core)
[zfs_server]$ zpool list
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
pool1  1.88G   273K  1.87G        -         -     0%     0%  1.00x    ONLINE  -
[zfs_server]$ zpool status
  pool: pool1
 state: ONLINE
  scan: none requested
config:

        NAME        STATE     READ WRITE CKSUM
        pool1       ONLINE       0     0     0
          sdb       ONLINE       0     0     0
          sdc       ONLINE       0     0     0

errors: No known data errors
[zfs_server]$ zpool version
zfs-0.8.3-1
zfs-kmod-0.8.3-1
[zfs_server]$ zpool upgrade
This system supports ZFS pool feature flags.

All pools are formatted using feature flags.

Every feature flags pool has all supported features enabled.
```
CentOSでZFSが使えるとは感激です。
1GBの内蔵ディスク2本でストライピングという可用性も何もない構成ですが、検証用ですしいいでしょう。そもそも同じPCのHDDから切り出した仮想ディスクですしね。
versionの表示とかがSolarisとは異なりますね。今回はZFSの検証がメインではないので、ZFSについてはこの辺にしておきます。

#Provisionerの設定

構築したZFSに対して、以下に沿ってProvisionerの設定をしていきます。
>[kubernetes-zfs-provisioner](https://github.com/gentics/kubernetes-zfs-provisioner)


##環境構築
GO言語を使うんですね。初めて使います。
パッケージをインストールします。

```
[zfs_server]# yum install golang-go go-dep
読み込んだプラグイン:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
epel/x86_64/metalink
・・・
インストール:
  golang-bin.x86_64 0:1.13.6-1.el7

依存性関連をインストールしました:
  golang.x86_64 0:1.13.6-1.el7                                          golang-src.noarch 0:1.13.6-1.el7                                          mercurial.x86_64 0:2.6.2-11.el7

完了しました!
```

```
[zfs_server]#  yum install git mercurial
読み込んだプラグイン:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: ftp-srv2.kddilabs.jp
 * epel: ftp.jaist.ac.jp
 * extras: ftp-srv2.kddilabs.jp
 * updates: ftp-srv2.kddilabs.jp
パッケージ git-1.8.3.1-22.el7_8.x86_64 はインストール済みか最新バージョンです
パッケージ mercurial-2.6.2-11.el7.x86_64 はインストール済みか最新バージョンです
何もしません
```

使用するバイナリなどをCloneします。

```
[zfs_server]# git clone https://github.com/gentics/kubernetes-zfs-provisioner
Cloning into 'kubernetes-zfs-provisioner'...
remote: Enumerating objects: 232, done.
remote: Total 232 (delta 0), reused 0 (delta 0), pack-reused 232
Receiving objects: 100% (232/232), 54.06 KiB | 0 bytes/s, done.
Resolving deltas: 100% (110/110), done.
```
GOPATHなどを設定します。

```
[zfs_server]# mkdir -p ~/go
[zfs_server]# export GOPATH=$HOME/go
[zfs_server]# mkdir $GOPATH/bin
[zfs_server]# export PATH=$PATH:$GOPATH/bin
[zfs_server]# mkdir -p $GOPATH/src
[zfs_server]# ln -s ~/kubernetes-zfs-provisioner $GOPATH/src/kubernetes-zfs-provisioner
[zfs_server]# cd $GOPATH/src/kubernetes-zfs-provisioner
```

depをインストールします。

```
[zfs_server]# curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  5230  100  5230    0     0   5032      0  0:00:01  0:00:01 --:--:--  5038
ARCH = amd64
OS = linux
Will install into /root/go/bin
Fetching https://github.com/golang/dep/releases/latest..
Release Tag = v0.5.4
Fetching https://github.com/golang/dep/releases/tag/v0.5.4..
Fetching https://github.com/golang/dep/releases/download/v0.5.4/dep-linux-amd64..
Setting executable permissions.
Moving executable to /root/go/bin/dep
```
インストール、ビルドします。

```
[zfs_server]# dep ensure
dep: WARNING: branch, version, revision, or source should be provided for "github.com/spf13/viper"
dep: WARNING: branch, version, revision, or source should be provided for "k8s.io/apimachinery"
dep: WARNING: branch, version, revision, or source should be provided for "k8s.io/client-go"
[zfs_server]# make build
mkdir -p bin
env GOOS=linux go build -o bin/zfs-provisioner cmd/zfs-provisioner/main.go
```
WARNINGは出ていますが、インストールはできたようです。

##デプロイ/インストール

###systemd service fileの作成
以下のファイルを作成します。

```/etc/systemd/system/zfs-provisioner.service
[Unit]
Description=kubernetes zfs provisioner
After=nfs-kernel-server.service
After=kubelet.service

[Service]
TimeoutStartSec=0
Restart=on-failure
Environment=ZFS_PARENT_DATASET=pool1/pv
Environment=ZFS_SHARE_SUBNET=10.xx.xx.xx/24
Environment=ZFS_SERVER_HOSTNAME=10.xx.xx.yy
Environment=ZFS_KUBE_CONF=/etc/kubernetes/admin.conf
Environment=ZFS_KUBE_RECLAIM_POLICY=Retain
Environment=ZFS_METRICS_PORT=8081
ExecStart=/usr/local/bin/zfs-provisioner

[Install]
WantedBy=multi-user.target
```

|変数|概要|
|:---|:---
|ZFS_PARENT_DATASET|PVが作成されるデータセットを指定します。この指定するデータセットはあらかじめ作成しておきます。
|ZFS_SHARE_SUBNET|ZFS ServerとKubernetesクラスタがあるサブネットを指定します。
|ZFS_SERVER_HOSTNAME|ZFS Serverのホスト名、またはIPアドレスを指定します。
|ZFS_KUBE_CONF|Kubernetesクラスタへ接続する際に使用するコンフィグファイルを指定します。
|ZFS_KUBE_RECLAIM_POLICY|Reclaim Policyを指定します。「Delete」もしくは「Retain」が指定できます。
|ZFS_METRICS_PORT|PrometheusのメトリクスをExportするポートを指定します。

###zfs-provisionerのコピー
ビルドしたzfs-provisionerを/usr/local/bin配下にコピーします。

```
[zfs_server]# cp kubernetes-zfs-provisioner/bin/zfs-provisioner /usr/local/bin/
```

### kubernetes configuration fileのコピー
Masterノードのkubernetes configuration fileをZFS ServerのZFS_KUBE_CONFで指定したディレクトリにコピーします。


```
[k8s-master ~]$ sudo scp /etc/kubernetes/admin.conf user-name@zfs_server:~
user-nanme@zfs_server's password:
admin.conf                                          100% 5451   175.3KB/s   00:00

[zfs_server]$ sudo mkdir /etc/kubernetes
[zfs_server]$ sudo mv admin.conf /etc/kubernetes/
```

###zfs-provisioner serviceの起動

```
[zfs_server]# systemctl start zfs-provisioner.service
[zfs_server]# systemctl status zfs-provisioner.service
● zfs-provisioner.service - kubernetes zfs provisioner
   Loaded: loaded (/etc/systemd/system/zfs-provisioner.service; disabled; vendor preset: disabled)
   Active: active (running) since 土 2020-05-09 22:48:08 JST; 13s ago
 Main PID: 4241 (zfs-provisioner)
    Tasks: 5
   CGroup: /system.slice/zfs-provisioner.service
           mq4241 /usr/local/bin/zfs-provisioner
[zfs_server]# systemctl enable zfs-provisioner.service
Created symlink from /etc/systemd/system/multi-user.target.wants/zfs-provisioner.service to /etc/systemd/system/zfs-provisioner.service.
```

#StorageClassとPodの作成
Provisionerの設定が終わりましたので、StorageClassとPodを作成していきます。

##namespaceの作成

```zfs-namespace.yml
apiVersion: v1
kind: Namespace
metadata:
  name: zfs-system
```

```
$ kubectl apply -f zfs-namespace.yml
namespace/zfs-system created
```

##StorageClassの作成

```storageclass.yml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: zfs-sc
  namespace: zfs-system
provisioner: gentics.com/zfs
reclaimPolicy: Retain
```

```
$ kubectl apply -f storageclass.yml
storageclass.storage.k8s.io/zfs-sc created
$ kubectl get storageclasses.storage.k8s.io
NAME     PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
zfs-sc   gentics.com/zfs   Retain          Immediate           false                  13s
```

##PVCの作成

```pvc.yml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: example-pvc
  namespace: zfs-system
  annotations:
    volume.beta.kubernetes.io/storage-class: "zfs-sc"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 100Mi
```

```
$ kubectl apply -f pvc.yml
persistentvolumeclaim/example-pvc created
$ kubectl -n zfs-system get pvc
NAME          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
example-pvc   Bound    pvc-dd4120d9-f4d9-4018-b917-2d9efe9085f6   100Mi      RWX            zfs-sc         7s
```
PVが作成されました。

ZFS Server側でも確認してみます。

```
[zfs_server]$ zfs list
NAME                                                USED  AVAIL     REFER  MOUNTPOINT
pool1                                               100M  1.65G     25.5K  /pool1
pool1/pv                                            100M  1.65G     25.5K  /pool1/pv
pool1/pv/pvc-dd4120d9-f4d9-4018-b917-2d9efe9085f6   100M   100M       24K  /pool1/pv/pvc-dd4120d9-f4d9-4018-b917-2d9efe9085f6
```
指定したデータセット配下に新しいデータセットが作られていますね。また、PVCで指定した容量になっています。

```
[zfs_server]$ zfs get type pool1/pv/pvc-dd4120d9-f4d9-4018-b917-2d9efe9085f6
NAME                                               PROPERTY  VALUE       SOURCE
pool1/pv/pvc-dd4120d9-f4d9-4018-b917-2d9efe9085f6  type      filesystem  -
```
ZVOL(Volume)が作成されるのかと思いましたが、ファイルシステムが作成されていますね。

##Podの作成と動作確認
PVCまでが作成できましたので、Podを作成して動作を確認します。

```nginx-pod.yaml
kind: Pod
apiVersion: v1
metadata:
  name: nginx-example
  namespace: zfs-system
spec:
  containers:
  - name: nginx-example
    image: nginx:latest
    volumeMounts:
      - name: zfs-volume
        mountPath: /cache
  volumes:
    - name: zfs-volume
      persistentVolumeClaim:
        claimName: example-pvc
```
```
$ kubectl apply -f nginx-pod.yaml
pod/nginx-example created
$ kubectl -n zfs-system exec -it nginx-example /bin/bash
root@nginx-example:/# df -h
Filesystem                                                     Size  Used Avail Use% Mounted on
overlay                                                         17G  7.7G  9.4G  46% /
tmpfs                                                           64M     0   64M   0% /dev
tmpfs                                                          1.4G     0  1.4G   0% /sys/fs/cgroup
k8s-client:/pool1/pv/pvc-dd4120d9-f4d9-4018-b917-2d9efe9085f6  100M     0  100M   0% /cache
/dev/mapper/centos-root                                         17G  7.7G  9.4G  46% /etc/hosts
shm                                                             64M     0   64M   0% /dev/shm
tmpfs                                                          1.4G   12K  1.4G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs                                                          1.4G     0  1.4G   0% /proc/acpi
tmpfs                                                          1.4G     0  1.4G   0% /proc/scsi
tmpfs                                                          1.4G     0  1.4G   0% /sys/firmware
root@nginx-example:/# touch /cache/testfile
touch: cannot touch '/cache/testfile': Permission denied
```
マウントはできていますが、Permission denied
んー。

試しに、ZFS Server側でファイルを作成して読み込めるか試してみます。

```
root@nginx-example:/# ls /cache/
testfile
root@nginx-example:/# cat /cache/testfile
hello from zfs server
```
読み込みはできるようです。

#調査
kubernetes-zfs-provisionerのREADMEには以下の一文があります。作成したZFSのデータセットはNFSでShareしているようですので、NFS周りの設定を確認してみます。

> It creates ZFS datasets and shares them via NFS to make them mountable to pods. 

##ZFS Serverの確認と設定変更
PVCによって作成されたデータセットのNFSの設定値を確認します。

```
[zfs_server]# zfs get sharenfs pool1/pv/pvc-dd4120d9-f4d9-4018-b917-2d9efe9085f6
NAME                                               PROPERTY  VALUE           SOURCE
pool1/pv/pvc-dd4120d9-f4d9-4018-b917-2d9efe9085f6  sharenfs  rw=@10.0.0.0/8  local
```

「rw=@10.0.0.0/8」になっていますが、これはデフォルト値ですね。
systemd service fileで指定できるのですが、指定していないためデフォルト値になっています。
これはこれで大丈夫そうなのですが、root権限でマウントしてないのかなと思って調べてみました。ZFSのsharenfsはデフォルトでroot権限でマウントできるようになっているという記事があったのですが、明示的にroot権限を与えてみたいと思います。

systemd service fileを編集します。

```/etc/systemd/system/zfs-provisioner.service
[Unit]
Description=kubernetes zfs provisioner
After=nfs-kernel-server.service
After=kubelet.service

[Service]
TimeoutStartSec=0
Restart=on-failure
Environment=ZFS_PARENT_DATASET=pool1/pv
Environment=ZFS_SHARE_SUBNET=10.20.30.0/24
Environment=ZFS_SERVER_HOSTNAME=k8s-client
Environment=ZFS_KUBE_CONF=/etc/kubernetes/admin.conf
Environment=ZFS_KUBE_RECLAIM_POLICY=Retain
Environment=ZFS_METRICS_PORT=8081
Environment=ZFS_SHARE_OPTIONS=rw=@k8s-master,rw=@k8s-worker01,rw=@k8s-worker02,no_root_squash #追記
ExecStart=/usr/local/bin/zfs-provisioner

[Install]
WantedBy=multi-user.target
```

ZFS_SHARE_OPTIONSを追記しました。「no_root_squash」を指定して、root権限でマウントできるようにします。
また、マウントできるノードをKubernetesクラスタを構成するノードのみにしておきます。

サービスをリスタートします。

```
[zfs_server]# systemctl daemon-reload
[zfs_server]# systemctl restart zfs-provisioner.service
```

##PVC/Podの再作成
PVC/Podは削除して同じマニフェストで再作成しました。

```
$ kubectl -n zfs-system get pvc
NAME          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
example-pvc   Bound    pvc-a3c27f70-3afa-470b-8f4e-2a1a5f5ab91b   100Mi      RWX            zfs-sc         4s
$ kubectl -n zfs-system get pod
NAME            READY   STATUS    RESTARTS   AGE
nginx-example   1/1     Running   0          13s
```

ZFS Server側でsharenfsの値を確認しておきます。

```
[zfs_server]# zfs get sharenfs pool1/pv/pvc-a3c27f70-3afa-470b-8f4e-2a1a5f5ab91b
NAME                                               PROPERTY  VALUE                                                            SOURCE
pool1/pv/pvc-a3c27f70-3afa-470b-8f4e-2a1a5f5ab91b  sharenfs  rw=@k8s-master,rw=@k8s-worker01,rw=@k8s-worker02,no_root_squash  local
```
想定通りに設定できていますね。

コンテナにログインして、書き込みできるか確認します。

```
$ kubectl -n zfs-system exec -it nginx-example /bin/bash
root@nginx-example:/# df -h
Filesystem                                                     Size  Used Avail Use% Mounted on
overlay                                                         17G  7.7G  9.4G  46% /
tmpfs                                                           64M     0   64M   0% /dev
tmpfs                                                          1.4G     0  1.4G   0% /sys/fs/cgroup
k8s-client:/pool1/pv/pvc-a3c27f70-3afa-470b-8f4e-2a1a5f5ab91b  100M     0  100M   0% /cache
/dev/mapper/centos-root                                         17G  7.7G  9.4G  46% /etc/hosts
shm                                                             64M     0   64M   0% /dev/shm
tmpfs                                                          1.4G   12K  1.4G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs                                                          1.4G     0  1.4G   0% /proc/acpi
tmpfs                                                          1.4G     0  1.4G   0% /proc/scsi
tmpfs                                                          1.4G     0  1.4G   0% /sys/firmware
root@nginx-example:/# echo "hello from nginx-example" > /cache/testfile
root@nginx-example:/# cat /cache/testfile
hello from nginx-example
```

書き込みできました！

#まとめ
Dynamic Provisioningを利用するとPVを予め作成する手間が省けます。また、PVCがPVを要求する際は「こういうPV」という指定ですので、要求よりも大きいサイズのPVがBoundされることもあり、これも回避できますね。

今回はCentOS上でZFSを構築して、それをProvisionerにしてPVを作成するという、Production環境ではありえないだろう構成で検証しました。ただ、個人的には久しぶりにZFSを使えて懐かしさを感じながら楽しくできたので、とても満足しています。

#追記（allowVolumeExpansion）
StrageClassのパラメータの「allowVolumeExpansion」を「true」にすることによってPVを拡張することができます。
allowVolumeExpansionをサポートしているVolume Typeは以下に記載されています。もちろん、ZFSは記載されていません。試してみましたが、できませんでした。
予想通りではあります。

>[The StorageClass Resource -> Allow Volume Expansion](https://kubernetes.io/docs/concepts/storage/storage-classes/#the-storageclass-resource)

マニフェストに「allowVolumeExpansion: true」を追記するだけです。

```storageclass-resize.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: zfs-sc-resize
  namespace: zfs-system
provisioner: gentics.com/zfs
reclaimPolicy: Retain
allowVolumeExpansion: true
```

StrageClassは作成できますが、PVCを作成後、PVCの容量の変更はできませんでした。

```
$ kubectl -n zfs-system describe pvc example-pvc-resize
Name:          example-pvc-resize
・・・
Events:
  Type     Reason                 Age                From                                                             Message
  ----     ------                 ----               ----                                                             -------
  Normal   ExternalProvisioning   21m (x2 over 21m)  persistentvolume-controller                                      waiting for a volume to be created, either by external provisioner "gentics.com/zfs" or manually created by system administrator
  Normal   Provisioning           21m                gentics.com/zfs k8s-client 47e7e117-9385-11ea-9d63-08002777e78c  External provisioner is provisioning volume for claim "zfs-system/example-pvc-resize"
  Normal   ProvisioningSucceeded  21m                gentics.com/zfs k8s-client 47e7e117-9385-11ea-9d63-08002777e78c  Successfully provisioned volume pvc-7c2c5169-5cd6-407e-ab41-21ec0ad6f08e
  Warning  ExternalExpanding      90s                volume_expand                                                    Ignoring the PVC: didn't find a plugin capable of expanding the volume; waiting for an external controller to process this PVC.
```

pluginがないとWarningが出てますね。
ZFS自体はファイルシステムの拡張はできますので、作り込めばできるんでしょうが、そこまではね。