Kubernetes: Local Volumeの検証

# Kubernetes Local Volume  の概要
　Kubernetesではv1.10からBetaとしてLocal Volumeがサポートされました。Local Volumeとは、KubernetesのNodeのサーバ内蔵ディスク(SSD含む)をPodから利用する機能です。これにより、Podが削除されても、データを削除することなくNodeのサーバ内蔵ディスクに、データを保存し続けることが可能となります。
　外部ストレージを準備できない環境や、内蔵ディスクに余力がある方は使ってみると良いかと思います。ただし、あくまで内蔵ディスクですので、外部ストレージと異なりRAIDやErasure Codingなどのデータ保護は出来ませんので、Node障害でデータが消えてしまったり、アクセスできなくなって困るようなデータの格納には向かないので注意してください。
　Kubernetes v1.10での制限としては、StatefulSetなどと一緒に用いることの多いPod生成時に自動でVolumeを生成してくれる機能Dynamic Provisioningが非サポートとなっています。その代わり、Dynamic Provisioningほど強力ではありませんが、実際にPodにVolumeが割り当てられるまでVolumeを使用しないようにする設定が可能です。
# 検証
## 検証環境
- Kubernetes環境: Kubernetes v1.10.5
 ([kubeadm](https://kubernetes.io/docs/setup/independent/install-kubeadm/)を使いVirtual Box上に構築したMaster, WorkerNode x 2VMの合計3VMのクラスタ環境)

## Local Volumeの準備
はじめに workerノード(k8s-node1)に、sshでログインし、local volumeとして利用するディレクトリを作成します。
今回は、k8s-node1のLocal Volumeのみ割り当てます。

```
$ kubectl get node
NAME        STATUS    ROLES     AGE       VERSION
k8s         Ready     master    56d       v1.10.5
k8s-node1   Ready     <none>    56d       v1.10.5
k8s-node2   Ready     <none>    56d       v1.10.5
$ ssh k8s-node1
$ sudo mkdir -p /mnt/disks/vol1
$ sudo chmod 777 /mnt/disks/vol1
$ exit
```

## StorageClass の定義  
次に、Local Volumeに対応したStorageClassを`sc-lv.yaml`ファイルに定義します。

```sc-lv.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
```
- provisionerは、Dynamic Provisioningが非サポートとのため、`kubernetes.io/no-provisioner`を指定します。
- volumeBindingModeは、`WaitForFirstConsumer`を設定することで、実際にPodにVolumeが割り当てられるまでVolumeを使用しないようになります。

## StorageClass のデプロイ
kubectlを使ってStorageClassをデプロイします。

```
$ kubectl create -f sc-lv.yaml 
storageclass.storage.k8s.io "local-storage" created
```

StorageClassがデプロイされていることを確認します。

```
$ kubectl get sc        
NAME            PROVISIONER                    AGE
local-storage   kubernetes.io/no-provisioner   1m
```

## PersistentVolume の定義
続いて、PersistenvVolumeをpv-lv.yamlファイルに定義します。

```pv-lv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /mnt/disks/vol1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - k8s-node1
```

- spec.accessModesは、Local Volumeは他のPodから共有で利用することが出来ず、1つのPodからしかRead/Writeができないため、`ReadWriteOnce`を指定します。
- persistentVolumeReclaimPolicyは、PersistentVolumeClaimが削除された時にデータ削除のポリシーを指定できます。Local Volumeの場合は`Retain`を指定してください。persistentVolumeReclaimPolicy にはデータを削除する`Recycle`、Volumeを同時に削除する`Delete`が指定可能です。しかし、`Recycle`はデータを削除するのであれば、そもそもLocal Volumeを使う理由がありませんし、`Delete`はDynamic Provisioningが非サポートのため、指定してもPersistentVolumeClaimされるとPersistetVolumeのステータスがFAILになります。
- storageClassNameは、先ほどデプロイしたStorageClassのnameの`local-storage`を指定します
- spec.local.pathは、Local Volumeの準備で作成したディレクトリ`/mnt/disks/vol1`を指定します。
- nodeAffinityは、どのNodeを使うかの条件を指定します。今回は、Nodeの`k8s-node1`のみ選択されるように`kubernetes.io/hostname`をキーとして指定しています。

## PersistentVolume のデプロイ
kubectlを使ってPersistentVolumeをデプロイします。

 ```
$ kubectl create -f pv-lv.yaml
persistentvolume "local-pv" created
```

PersistentVolumeがデプロイされていることを確認します。

```
$ kubectl get pv
NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS    REASON    AGE
local-pv   1Gi        RWO            Retain           Available             local-storage             29s
```

## PersistentVolumeClaim の定義
次に、ユーザがVolumeを要求するための定義PersistentVolumeClaimをpvc-lv.yamlファイルに定義します。

```pvc-lv.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: local-claim
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: local-storage
  resources:
    requests:
      storage: 1Gi
```
- spec.storageClassName には、先に定義したStorageClassのnameである`local-storage`を指定します。

## PersistentVolumeClaim のデプロイ
kubectlを使ってPersistentVolumeClaimをデプロイします。
 
```
$ kubectl create -f pvc-lv.yaml 
persistentvolumeclaim "local-claim" created
```

PersistentVolumeClameがデプロイされていることを確認します。
この段階では、STATUSは`Pending`となります。

```
$ kubectl get pvc
NAME          STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS    AGE
local-claim   Pending                                       local-storage   52s
```

## Local Volumeを使ったPodの定義
定義したPersistentVolumeClaimの`local-claim`を使い、PodへLocal Volumeを割り当てます。
ここでは、nginx-lv.yamlとしてPodを定義します。

```nginx-lv.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-nginx-pod
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - mountPath: "/usr/share/nginx/html"
      name: mydata
  volumes:
    - name: mydata
      persistentVolumeClaim:
        claimName: local-claim
```

- spec.containers.volumeMounts.mountPathに、Pod内のLocal Volumeのマウント先を指定します。
- spec.volumes.persistentVolumeClaimに、先に定義したPersistentVolumeClaimのnameである`local-claim`を指定します。

## Local Volumeを使ったPodのデプロイ
kubectlを使ってLocal Volumeを使ったPodをデプロイします。

```
$ kubectl create -f nginx-lv.yaml 
pod "my-nginx-pod" created
```
Local Volumeを使ったPodがデプロイされていることを確認します。
このPodがPersistentVolumeClaimを経由しPersistentVolumeをマウントした段階で、PersistentVolumeClaimのSTATUSが`Bound`に変わります。
さらに、そのPersistentVolumeClaimから選出されたPersistentVolumeのSTATUSが`Bound`に変わり、CLAIMに`default/local-claim`が設定されます。
つまり、StorageClassにて定義したvolumeBindingModeの`WaitForFirstConsumer`の指定通り、PodがLocal Volumeをマウントした際に、初めてPersistentVolumeが使用され始めます。

```
$ kubectl get pvc
NAME          STATUS    VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
local-claim   Bound     local-pv   1Gi        RWO            local-storage   3m
$ kubectl get pv
NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                 STORAGECLASS    REASON    AGE
local-pv   1Gi        RWO            Retain           Bound     default/local-claim   local-storage             5m
```

## クリーンアップ
デプロイしたPod, PersistentVolumeClaimを削除します。

```
$ kubectl delete -f nginx-lv.yaml 
pod "my-nginx-pod" deleted
$ kubectl delete -f pvc-lv.yaml 
persistentvolumeclaim "local-claim" deleted
```
PersistentVolumeClaimを削除したことで、PersistentVolumeのSTAUTSが`Released`に変わります。

```
$ kubectl get pv
NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                 STORAGECLASS    REASON    AGE
local-pv   1Gi        RWO            Retain           Released   default/local-claim   local-storage             6m
```
続いて、PersistentVolumeとStorageClassを削除します。

```
$ kubectl delete -f pv-lv.yaml 
persistentvolume "local-pv" deleted
$ kubectl delete -f sc-lv.yaml 
storageclass.storage.k8s.io "local-storage" deleted
```

最後に、K8s-node1のNodeにsshでログインし、`/mnt/disks`ディレクトリ配下を削除します。

```
$ ssh k8s-node1
$ cd /mnt
$ sudo rm -rf disks/
$ exit
```

# まとめ
　今回、Local Volumeを検証しました。Local Volumeは、データの重要性は低いが、Podと一緒に削除されて欲しくない、そういうデータを保存するのに向いています。そういうケースはなんでしょうか？たとえば、データベースのSlaveノードなどには向いているかと思います。Slaveノードは、Masterノードが生きていれば、Slaveノードのデータは障害などでデータが削除したとしても復旧可能です。また、Slaveノードがバージョンアップによるローリングアップデートなどで再作成する場合には、データは保持されたままなので、MasterとSlave間の同期は短時間で完了します。
　その他、Local Volumeのユースケースはなにかあるでしょうか？感の良い人ならば思いつくかと思います。Kubernetesに対応していないレガシーの外部ストレージを利用するのに利用できます。つまり、レガシーの外部ストレージのVolumeをNodeにマウントし、そのマウントしたVolumeを、今回紹介したLocal Volumeとして利用します。これにより、レガシーの外部ストレージのVolumeをPodで使用することが可能となります。ただし、Nodeのサーバに外部ストレージのVolumeを割り当てないといけないため、管理者の負担は増えてしまいます。レガシーの外部ストレージをPod(コンテナ)で利用する場合の奥の手として持っておくのには、有効な方法かと思います。

# 参考情報
- [Storage Class #local](https://kubernetes.io/docs/concepts/storage/storage-classes/#local)
- [Local Persistent Volumes for Kubernetes Goes Beta](https://kubernetes.io/blog/2018/04/13/local-persistent-volumes-beta/)