OpenShift環境でNFSの動的プロビジョニングを試してみた

## 1. 背景
- コンテナで稼働するアプリケーションでRWX（複数ノードからの読み書きが可能）なストレージが必要な場合はBlock Storageは使用できず、Object Storageはアプリ側の接続方法を変える必要があるため、File storageが必要なアプリケーションがまだまだ存在する。
- IBM CloudのOpenShiftクラスターをVPC(Virtual Private Cloud)インフラストラクチャーで利用する場合、[File Storage for VPC](https://cloud.ibm.com/docs/vpc?topic=vpc-file-storage-vpc-about&locale=en)というソリューションが存在するが、承認されたユーザーだけが利用できることや使えるリージョンが限られることなど、利用するにはまだ敷居が高い。
- OpenShift Container Storage (新しい名前はOpenShift Data Foundation）もあるが、必要なクラスターのリソースなどの要件が色々あり、これも気軽に利用できるとは言い難い。
- File storageとして、NFSサーバーを利用する方法もあるが、動的プロビジョニングができないため、管理者が事前にNFSエクスポートを準備するなど管理に手間がかかる。
- 調べたところNFSサーバーの動的プロビジョニングに関する情報（以下、「3. 参考にさせていただいた情報」の1.および2.)が公開されているため利用してみる。

## 2. 目的
- NSFサーバーを構築する。
- OpenShiftクラスターからNFSサーバーの動的プロビジョニングを可能にする。

## 3. 参考にさせていただいた情報
1. [kubernetes-sigs/nfs-subdir-external-provisioner](https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner)
1. [How do I create a storage class for NFS dynamic storage provisioning in an OpenShift environment?](https://www.ibm.com/support/pages/how-do-i-create-storage-class-nfs-dynamic-storage-provisioning-openshift-environment)
1. [Db2 11.5.4 on OpenShift をデプロイしてみた - NFS構成 -](https://qiita.com/mi-kana/items/fed0d47fca56853fdd4e)
1. [OpenShift Container Platform>4.6>ストレージ>3.11. NFS を使用した永続ストレージ](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.6/html/storage/persistent-storage-using-nfs)
1. [NFS : サーバーの設定](https://www.server-world.info/query?os=CentOS_8&p=nfs&f=1)
1. [CentOS 8 でNFSサーバー構築](https://lig-log.com/build-an-nfs-server-with-centos-8/)
1. [Red Hat Enterprise Linux>8>ファイルシステムの管理>第3章 NFS 共有のマウント](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/managing_file_systems/mounting-nfs-shares_managing-file-systems)
1. [Red Hat Enterprise Linux>8>ファイルシステムの管理>第4章 NFS 共有のエクスポート](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/managing_file_systems/exporting-nfs-shares_managing-file-systems)
1. [Kubernetes標準のストレージ機能をおさらいしよう](https://speakerdeck.com/hhiroshell/kubernetesbiao-zhun-falsesutoreziji-neng-woosaraisiyou)

## 4.環境構成
今回は以下のようにIBM Cloud上のOpenShiftクラスターと仮想サーバーを利用する構成で検証した。
![NFS動的プロビジョニング.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279924/41274196-c83e-4a93-14e6-42e895198856.png)


 - IBM CloudのVirttual Private Cloud環境
   - Virtual Server Instance(VSI） -Bastion（踏み台）サーバー
     - NFSサーバーとして構築し、ファイルシステムの1つをNFSボリュームとして共有する。
   - Red Hat OpenShift on IBM Cloud クラスター
     - ワーカーノード 3台
        - podはいずれかのワーカーノードで稼働する。
        - 共有されたNFSボリュームをPersistent Volumeとしてマウントして利用する。

動的プロビジョニングの利用の流れは以下の図のイメージとなる。
![動的プロビジョニングの利用イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279924/95d449ea-9e37-813f-5b76-096f7388cd2b.png)


## 5. 手順

### 5.1. NFSサーバーの設定

- VSIへログインし、NFSサーバを利用するために必要なnfs-utilsパッケージをインストールする。

```
sudo dnf -y install nfs-utils

```

- VSIにディレクトリーを作成する。
    - ここでは例として`/work/nfsdir/roksNFS-dynamic`というディレクトリーとする。VSIにマウントしているパスに合わせてパスは変更いただきたい。
    - VSIの追加ストレージをマウントする手順は[IBM Cloud の 仮想サーバーに 追加ストレージ（ポータルブル・ストレージ）をマウントする](https://qiita.com/Asuka_Saito/items/128bf1148f4d0e048724)という記事が参考になる。

```
sudo mkdir -p /work/nfsdir/roksNFS-dynamic
```

- 設定ファイル /etc/exports を編集してNFS共有を構成する。
    - ワーカーノードのIPアドレスはサブネットのアドレス範囲から動的に割り当てられるため、CIDR表記にする。


```/etc/exports
/<NFS共有するディレクトリー> <openshiftワーカーノード1のIPアドレスのCIDR表記>(rw,sync,no_root_squash,no_all_squash) <openshiftワーカーノード2のIPアドレスのCIDR表記>(rw,sync,no_root_squash,no_all_squash) <openshiftワーカーノード3のIPアドレスのCIDR表記>(rw,sync,no_root_squash,no_all_squash)

```

実行例

```
$ sudo vi /etc/exports
$ sudo cat /etc/exports
/work/nfsdir/roksNFS-dynamic 10.240.0.0/24(rw,sync,no_root_squash,no_all_squash) 10.240.64.0/24(rw,sync,no_root_squash,no_all_squash) 10.240.128.0/24(rw,sync,no_root_squash,no_all_squash)
```

- /etc/exports　の設定を反映し、確認する。

```
exportfs -ra
exportfs
```
実行例

```
$ sudo exportfs -ra
$ sudo exportfs -v
/work/nfsdir/roksNFS-dynamic
		10.240.0.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/work/nfsdir/roksNFS-dynamic
		10.240.64.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/work/nfsdir/roksNFS-dynamic
		10.240.128.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
```

rpcbindサービスの状態を確認する。以下の例ではmaskedになっている。

```
$ systemctl list-unit-files -t service | grep rpcbind
rpcbind.service                            masked  
```

maskedになっている場合はサービスの手動起動禁止のため、マスキング解除してrpcbindサービスを起動できるようにする。

```
sudo systemctl unmask rpcbind
```
実行例

```
$ sudo systemctl unmask rpcbind
Removed /etc/systemd/system/rpcbind.service.
```

rpcbindサービスのステータスがdisabledになったことを確認。

```
$ systemctl list-unit-files -t service | grep rpcbind
rpcbind.service                            disabled
```

- rpcbindサービスを起動する。

```
sudo service rpcbind start
```

実行例

```
$ sudo service rpcbind start
Redirecting to /bin/systemctl start rpcbind.service
```

- rpcbindサービスを有効にしておく。

```
systemctl enable --now rpcbind
```

実行例

```
$ sudo systemctl enable --now rpcbind
Created symlink /etc/systemd/system/multi-user.target.wants/rpcbind.service → /usr/lib/systemd/system/rpcbind.service.
$ systemctl list-unit-files -t service | grep rpcbind
rpcbind.service                            enabled 
```

- NFSサービスを起動する。

```
sudo systemctl start nfs-server
```

- NFSサービスを自動起動するように以下のコマンドを実行する。
```
sudo systemctl enable --now nfs-server
```

実行例

```
$ sudo systemctl start nfs-server
$ sudo systemctl enable --now nfs-server
Created symlink /etc/systemd/system/multi-user.target.wants/nfs-server.service → /usr/lib/systemd/system/nfs-server.service.
$ sudo systemctl list-unit-files -t service | grep nfs-server
nfs-server.service                         enabled 
```

- ファイアーウォールでNFSを許可する設定を行う。

```
firewall-cmd --add-service=nfs --permanent
firewall-cmd --add-service={nfs3,mountd,rpc-bind} --permanent
firewall-cmd --reload 
```

実行例

```
$ sudo firewall-cmd --add-service=nfs --permanent
success
$ sudo firewall-cmd --add-service={nfs3,mountd,rpc-bind} --permanent
success
$ sudo firewall-cmd --reload 

success
```

### 5.2. NFSサーバーの動的プロビジョニング設定
- OpenShiftクラスターにNSF用のプロジェクト`nfs-test`を作成する。

```
oc new-project nfs-test
```

ここからは、以下に沿って進める
- [How do I create a storage class for NFS dynamic storage provisioning in an OpenShift environment?](https://www.ibm.com/support/pages/how-do-i-create-storage-class-nfs-dynamic-storage-provisioning-openshift-environment)

- Service AccoutやRole設定のためのrabc.yamlファイルの作成
  - Gitリポジトリーからファイルを取得する。
    
    ```
    git clone https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner.git
    ```
  - rabc.yamlファイル内のnamespaceをdefaultからnfs-testへ変更する。
 
    ```rabc.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: nfs-client-provisioner
      # provisionerをデプロイするプロジェクトに変更する
      namespace: nfs-test
    ---
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: nfs-client-provisioner-runner
    rules:
      - apiGroups: [""]
        resources: ["nodes"]
        verbs: ["get", "list", "watch"]
      - apiGroups: [""]
        resources: ["persistentvolumes"]
        verbs: ["get", "list", "watch", "create", "delete"]
      - apiGroups: [""]
        resources: ["persistentvolumeclaims"]
        verbs: ["get", "list", "watch", "update"]
      - apiGroups: ["storage.k8s.io"]
        resources: ["storageclasses"]
        verbs: ["get", "list", "watch"]
      - apiGroups: [""]
        resources: ["events"]
        verbs: ["create", "update", "patch"]
    ---
    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: run-nfs-client-provisioner
    subjects:
      - kind: ServiceAccount
        name: nfs-client-provisioner
        # provisionerをデプロイするプロジェクトに変更する
        namespace: nfs-test
    roleRef:
      kind: ClusterRole
      name: nfs-client-provisioner-runner
      apiGroup: rbac.authorization.k8s.io
    ---
    kind: Role
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: leader-locking-nfs-client-provisioner
      # provisionerをデプロイするプロジェクトに変更する
      namespace: nfs-test
    rules:
      - apiGroups: [""]
        resources: ["endpoints"]
        verbs: ["get", "list", "watch", "create", "update", "patch"]
    ---
    kind: RoleBinding
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: leader-locking-nfs-client-provisioner
      # provisionerをデプロイするプロジェクトに変更する
      namespace: nfs-test
    subjects:
      - kind: ServiceAccount
        name: nfs-client-provisioner
        # provisionerをデプロイするプロジェクトに変更する
        namespace: nfs-test
    roleRef:
      kind: Role
      name: leader-locking-nfs-client-provisioner
      apiGroup: rbac.authorization.k8s.io
    ```
- rbac.yamlを適用する。

```
oc apply -f rbac.yaml

```
実行例

```
$ oc apply -f rbac.yaml
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
```

- SCC (Security Context Constraints) の設定を行う。
    - SCCの設定にはOpenShiftクラスターの管理者権限が必要なため、権限のあるユーザーで実施する。

```
oc adm policy add-scc-to-user hostmount-anyuid system:serviceaccount:nfs-test:nfs-client-provisioner
```

実行例

```
$ oc adm policy add-scc-to-user hostmount-anyuid system:serviceaccount:fs-test:nfs-client-provisioner
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:hostmount-anyuid added: "nfs-client-provisioner"
```

- Provisioner用のdeployment.yamlを以下のように変更する。
  - metadata.namespaceをdefaultからnfs-testに変更
  - spec.template.spec.containers.envのname.PROVISIONER_NAMEのvalueをわかりやすいように`nfs-storage`に変更
  - spec.template.spec.containers.envのname.NFS_SERVERのvalueとspec.template.spec.containers.volumes.nfs.serverの値をNFSサーバーのIPアドレス`10.240.0.11`に変更
  - spec.template.spec.containers.envのNFS_PATHのvalueとspec.template.spec.containers.volumes.nfs.pathの値を作成したNFS共有のパス`/work/nfsdir/roksNFS-dynamic`に変更

```deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # namespaceをdefaultから作成したネームスペースへ変更
  namespace: nfs-test
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: nfs-storage # provisioner名を指定する
            - name: NFS_SERVER
              value: 10.240.0.11 # NFSサーバーのホスト名またはIPアドレスに変更
            - name: NFS_PATH
              value: /work/nfsdir/roksNFS-dynamic # 作成したNFS共有のパスに変更
      volumes:
        - name: nfs-client-root
          nfs:
            server: 10.240.0.11 # NFSサーバーのホスト名またはIPアドレスに変更
            path: /work/nfsdir/roksNFS-dynamic # 作成したNFS共有のパスに変更
```

- Provisioner用のdeploymentをデプロイする。

```
oc apply -f deployment.yaml
```

実行例

```
$ oc apply -f deployment.yaml 
deployment.apps/nfs-client-provisioner created
```

- deploymentの状態を確認する。

```
oc get deployment
```

deploymentが1/1 READYとなり、nfs-client-provisionerのpodが起動したことを確認。

実行例

```
$ oc get deployment
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
nfs-client-provisioner   1/1     1            1           49m
$ oc get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-84745899cf-nwtpr   1/1     Running   0          41s
```

- storage classのyamlファイルを作成する。  
  - matadata.nameに名前を`managed-nfs-storage`と指定する。
  - provisionerはdeploymentの env PROVISIONER_NAMEと合わせて`nfs-storage`とする。

```nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage  # storage class名を指定
provisioner: nfs-storage # ここはdeploymentのenv PROVISIONER_NAMEと同じ値にする
parameters:
  archiveOnDelete: "false"
```

- nfs-client-provisionerのstorage classをデプロイする。

```
oc apply -f nfs-storageclass.yaml
```
実行例

```
$ oc project
Using project "nfs-test" on server "***".
$ oc apply -f nfs-storageclass.yaml
I0810 00:57:53.003659  580455 request.go:645] Throttling request took 1.088788215s, request: GET:***/apis/ws.cpd.ibm.com/v1beta1?timeout=32s
storageclass.storage.k8s.io/managed-nfs-storage created
```
- storage class `managed-nfs-storage` が作成されたことを確認

```
$ oc get storageclass
NAME                                          PROVISIONER                             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
ibmc-vpc-block-10iops-tier (default)          vpc.block.csi.ibm.io                    Delete          Immediate              false                  11d
ibmc-vpc-block-5iops-tier                     vpc.block.csi.ibm.io                    Delete          Immediate              false                  11d
ibmc-vpc-block-custom                         vpc.block.csi.ibm.io                    Delete          Immediate              false                  11d
ibmc-vpc-block-general-purpose                vpc.block.csi.ibm.io                    Delete          Immediate              false                  11d
ibmc-vpc-block-metro-10iops-tier              vpc.block.csi.ibm.io                    Delete          WaitForFirstConsumer   false                  11d
ibmc-vpc-block-metro-5iops-tier               vpc.block.csi.ibm.io                    Delete          WaitForFirstConsumer   false                  11d
ibmc-vpc-block-metro-custom                   vpc.block.csi.ibm.io                    Delete          WaitForFirstConsumer   false                  11d
ibmc-vpc-block-metro-general-purpose          vpc.block.csi.ibm.io                    Delete          WaitForFirstConsumer   false                  11d
ibmc-vpc-block-metro-retain-10iops-tier       vpc.block.csi.ibm.io                    Retain          WaitForFirstConsumer   false                  11d
ibmc-vpc-block-metro-retain-5iops-tier        vpc.block.csi.ibm.io                    Retain          WaitForFirstConsumer   false                  11d
ibmc-vpc-block-metro-retain-custom            vpc.block.csi.ibm.io                    Retain          WaitForFirstConsumer   false                  11d
ibmc-vpc-block-metro-retain-general-purpose   vpc.block.csi.ibm.io                    Retain          WaitForFirstConsumer   false                  11d
ibmc-vpc-block-retain-10iops-tier             vpc.block.csi.ibm.io                    Retain          Immediate              false                  11d
ibmc-vpc-block-retain-5iops-tier              vpc.block.csi.ibm.io                    Retain          Immediate              false                  11d
ibmc-vpc-block-retain-custom                  vpc.block.csi.ibm.io                    Retain          Immediate              false                  11d
ibmc-vpc-block-retain-general-purpose         vpc.block.csi.ibm.io                    Retain          Immediate              false                  11d
managed-nfs-storage                           nfs-storage                             Delete          Immediate              false                  92s
ocs-storagecluster-ceph-rbd                   openshift-storage.rbd.csi.ceph.com      Delete          Immediate              true                   5d10h
ocs-storagecluster-cephfs                     openshift-storage.cephfs.csi.ceph.com   Delete          Immediate              true                   5d10h
openshift-storage.noobaa.io                   openshift-storage.noobaa.io/obc         Delete          Immediate              false                  5d10h
```

### 5.3. 動的プロビジョニングの稼働確認

 [nfs-subdir-external-provisionerリポジトリーのREADME](https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/blob/master/README.md)に記載されている手順で動作確認を行う。

 - テスト用のPVCとpodのyamlを用意する。
    - ここでのポイントは、PVCのyamlでstorageClassNameに作成した`managed-nfs-storage`を指定することである。これによって、NFS動的プロビジョニング用のprovisionerがPVC用のディレクトリーを作成して利用できるようにする。
 
```test-claim.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
spec:
  storageClassName: managed-nfs-storage
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi
 ```


 ```test-pod.yaml
 kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: gcr.io/google_containers/busybox:1.24
    command:
      - "/bin/sh"
    args:
      - "-c"
      - "touch /mnt/SUCCESS && exit 0 || exit 1"
    volumeMounts:
      - name: nfs-pvc
        mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: test-claim
 ```

 - テスト用のPVCとpodをデプロイする
 
```
 oc apply -f test-claim.yaml -f test-pod.yaml
 ```

 ログ
 
```
$ oc apply -f test-claim.yaml -f test-pod.yaml
persistentvolumeclaim/test-claim created
pod/test-pod created
```

- PVCとpodが作成されたことを確認する。

```
$ oc get pvc
NAME           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
test-claim     Bound    pvc-7e915759-a87a-4969-958b-72d62fa97c0a   1Mi        RWX            managed-nfs-storage   57s
$ oc get pod
NAME                                      READY   STATUS      RESTARTS   AGE
nfs-client-provisioner-84745899cf-nwtpr   1/1     Running     0          3d15h
test-pod                                  0/1     Completed   0          60s
```

 - NSF共有のディレクトリにSUCCESSファイルが作成されていることを確認する。
   - 動的プロビジョニング用のNFS共有ディレクトリー以下にpvc名のディレクトリーが作成され、その下にファイルが作られている。

```
# pwd
/work/nfsdir/roksNFS-dynamic/nfs-test-test-claim-pvc-7e915759-a87a-4969-958b-72d62fa97c0a
# ls -la
total 0
drwxrwxrwx. 2 root root 21 Aug 10 01:03 .
drwxr-x---. 3 root root 74 Aug 10 01:03 ..
-rw-r--r--. 1 root root  0 Aug 10 01:03 SUCCESS
```

 - テスト用のPVCとpodを削除する。
 
```
 oc delete -f test-claim.yaml -f test-pod.yaml
 ```