オンプレ環境でのStorageClass

## 環境
- ubuntu：22.0.4.1 LTS
- kubernetes：1.28.2
- containerd：1.6.24

## StorageClassについて
StorageClassを用いずにPersistentVolumeを使用するには、クラスターの管理者が事前にPersistentVolumeを作成して、ユーザーがPersistentVolumeClaimを作成する流れになると思います。

ですが、もう少し運用を楽にしたい。クラスターの管理者を介さずに、PersistentVolumeを作成したいといった要件があると思います。

そこで、使えるのがStorageClassです。
今回、オンプレ環境でStorageClassを利用するための外部プロビジョナー（対する「内部プロビジョナー」はデフォルトで使用できるプロビジョナーを指す）を用いて、NFS使った動的プロビジョンについて書いていきます。

## NFSサーバーの準備
NFSサーバーを構築していきます。詳細は「nfs-kernel-server」で検索していただきたいですが、以下の通り、インストールします。

```cmd
# sudo apt-get update
# sudo apt-get install nfs-kernel-server
```

インストールが完了したら公開するディレクトリの作成および権限設定をします。

今回は、/export/nfsという公開用のディレクトリを作成し、然る権限を付与します。

```cmd
# sudo mkdir /export/nfs
# sudo chmod 1777 /export/nfs/.
```

次に、NFSの設定をします。
```diff:/etc/exports
/export/nfs/ 172.21.0.0/24(rw,sync,no_subtree_check,no_root_squash,no_all_squash
```
最後に設定を反映させるためにNFSサービスを再起動させます。

```cmd
# sudo systemctl reload nfs-kernel-server
```

これで準備完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2604097/89c55e02-e718-7b2d-810d-33e643fe79d4.png)

## NFSクライアントのインストール
ワーカーノードすべてでNFSクライアントをインストールする。
```cmd
# sudo apt-get update
# sudo apt-get install nfs-common
```

## NFSクライアントの確認
NFSクライアントが正常に動作することを確認する。
```cmd
# sudo mount -t nfs 172.21.0.50:/export /mnt
# ls /mnt/nfs
<show something>
# sudo umount /mnt
```

## プロビジョナーの導入
プロビジョナーの導入をしていきます。
以下のレポジトリのREADME通りに実施しています。HelmやKustomizeでの導入も可能ですが、今回は、マニュアルで導入していきます。

https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner

### gitクローンするディレクトリを作成する。
```cmd
# mkdir dynamic-provisioner-nfs && cd dynamic-provisioner-nfs
# git init --initial-branch main
# Initialized empty Git repository in /dynamic-provisioner-nfs/.git/
```

### gitの設定
/deployディレクトリのみ必要なので、特定のディレクトリのみクローンできるように設定していきます。併せてリモートレポジトリを追加します。

```diff
# git config core.sparsecheckout true
# git config --list
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
+ core.sparsecheckout=true
# git remote add origin https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner.git
# echo /deploy >> .git/info/sparse-checkout
```
### gitクローン
それでは、/deployディレクトリをクローンします。
```cmd
# git pull origin master
From https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
 * branch            master     -> FETCH_HEAD

# ls
deploy
```

これで準備が完了しました。
次に各yamlファイルの作成・修正をしていきます。

### namespaceの作成
まずは、プロビジョナー用Namespaceのマニフェストを作成します。
ここではNamespace名を`dynamic-provisioner-nfs`とします。

```cmd:namespace-dynamic-provisioner-nfs.yaml
sioner-nfs.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: dynamic-provisioner-nfs
spec: {}
status: {}
```

上記で作成したマニフェストを'apply'します。

```cmd
# kubectl apply -f namespace-dynamic-provisioner-nfs.yaml
namespace/dynamic-provisioner-nfs created
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2604097/8d509d07-5207-b26f-5ee8-e0653bad5b60.png)


### rbac.yaml、deployment.yamlの修正
/deployには、それぞれDeployment、ServiceAccount、StorageClass、テスト用PVC、テスト用Podのマニフェストが入っております。rbac.yamlやdeployment.yamlはデフォルトでNamespaceが`default`になっているので、上記で作成したNamespace `dynamic-provisioner-nfs`に変更します。

```cmd
# NAMESPACE=dynamic-provisioner-nfs
# echo $NAMESPACE
dynamic-provisioner-nfs
# sed -i "s/default/$NAMESPACE/g" ./deploy/rbac.yaml ./deploy/deployment.yaml
```

### ServiceAccountの作成
ここで、rbac.yamlの中身をみてみます。

```cmd:./deploy/rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: dynamic-provisioner-nfs
---
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
    # replace with namespace where provisioner is deployed
    namespace: dynamic-provisioner-nfs
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
  # replace with namespace where provisioner is deployed
  namespace: dynamic-provisioner-nfs
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
  # replace with namespace where provisioner is deployed
  namespace: dynamic-provisioner-nfs
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: dynamic-provisioner-nfs
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io
```

Node、PVC、PVを管理するための権限と、上記で作成したnameSpaceのEndpointsを管理するための権限を付与したServiceAccountを作成しています。Endpointsの権限はなぜ必要なのかわからないですが、一旦そのままにしておきます。

このServiceAccountを、後で作成するDeploymentに渡して、上記のリソースを管理するPodをデプロイします。

では、'apply'します。

```cmd
# kubectl create -f deploy/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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2604097/720a5622-8bd0-9a60-f407-9eb98d0d5863.png)

### Deploymentの作成
では、先に修正したDeployment.yamlをさらに修正していきます。
デフォルトでNFSサーバーのアドレスと、ディレクトリがサンプルのパラメータとして記載されています。
ここでは、以下のように修正します。

```diff:deploy/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: dynamic-provisioner-nfs
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: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: k8s-sigs.io/nfs-subdir-external-provisioner
            - name: NFS_SERVER
-              value: 10.3.243.101
+              value: 172.21.0.50
            - name: NFS_PATH
-              value: /ifs/kubernetes
+              value: /export/nfs
      volumes:
        - name: nfs-client-root
          nfs:
-            server: 10.3.243.101
+            server: 172.21.0.50
-            path: /ifs/kubernetes
+            path: /export/nfs
```

では、'apply'します。

```cmd
# kubectl apply -f deploy/deployment.yaml
deployment.apps/nfs-client-provisioner created
```

これで、対象のPodが立ち上がるのを待ちます。
少しだけ時間がかかった気がします。

```cmd
# kubectl get pods -n dynamic-provisioner-nfs
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-85975c94c6-lpxnt   1/1     Running   0          6m46s
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2604097/18265821-10ec-c3d8-69be-92deff211680.png)

### StrageClassの作成

最後にStorageClassの作成をしていきます。
必要に応じてマニフェストの修正が必要になりますが、用意されているclass.yamlをそのまま'apply'してみます。

```cmd
# kubectl apply -f deploy/class.yaml
storageclass.storage.k8s.io/nfs-client created
```

```cmd
# kubectl get sc
NAME         PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-client   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           false                  2m16s
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2604097/89b7c4b4-7cd8-8173-7771-85a22c66348a.png)

これで準備が整いました。
それでは、PersistentVolumeClaimを作成していきます。

## 動作確認
このGitでは親切にもテスト用のマニフェストが用意されていますので、そのまま使用してみたいと思います。用意されているPod、PVCマニフェストのNamespaceは`default`となっています。

```cmd
# kubectl apply -f deploy/test-pod.yaml -f deploy/test-claim.yaml
pod/test-pod created
persistentvolumeclaim/test-claim created
```

```cmd
# kubectl get pvc
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
test-claim   Bound    pvc-b1888a30-6361-4dd9-bfe4-77649c095733   1Mi        RWX            nfs-client     42s
# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   REASON   AGE
pvc-b1888a30-6361-4dd9-bfe4-77649c095733   1Mi        RWX            Delete           Bound    default/test-claim   nfs-client              71s
# kubectl get pods
NAME       READY   STATUS      RESTARTS        AGE
test-pod   0/1     Completed   0               82s
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2604097/e15fbab1-b39a-f896-e49d-86f0bca76afe.png)

上記で`test-claim`というPVCと、それに対応する`pvc-b1888a30-6361-4dd9-bfe4-77649c095733`というPVが作成されます。
NFSサーバーには、以下のようなディレクトリが作成されます。

`${namespace}-${pvcName}-${pvName}`

ここでは「`default`-`test-claim`-`pvc-b1888a30-6361-4dd9-bfe4-77649c095733`」といったディレクトリが公開ディレクトリに作成されます。

加えて、Pod `test-pod`で先に作成されたPVC `test-claim`をマウントします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2604097/2b7d1f65-ce61-25c5-d52d-9820763f851f.png)

ここで、NFSサーバーにアクセスし、公開ディレクトリを確認してみます。
```cmd
# ls /export/nfs/default-test-claim-pvc-b1888a30-6361-4dd9-bfe4-77649c095733/
SUCCESS
```

「SUCCESS」ファイルが作成されていれば完了です。
動作確認出来たら、先のPVCとPodを削除します。

```cmd
# kubectl delete -f deploy/test-pod.yaml -f deploy/test-claim.yaml
pod "test-pod" deleted
persistentvolumeclaim "test-claim" deleted
```

これでクラスター管理者を介さずにPersistentVolumeを作成できるようになりました。

今回は以上です。