Kubernetes: PersistentVolume概要とminikubeでの検証

## Kubernetes Persistent Volume  の概要
　Kubernetes では，Persistent Volume として，外部ストレージに Pod のデータの格納する方法が提供されています．Persistent Volume を使うことにより，(1）Pod の削除してもデータを保存し続ける，(2)複数Pod間でファイルを共有することが可能となります．また，Persistent Volume では，外部ストレージにデータが保存されるため，外部ストレージが持つバックアップ機能やリモートコピー機能によりPodのデータを超長期保存やデザスタリカバリに備えることが可能となります．　　
　Kubernetes v1.9 までは，Kubernetes volume plugin として，Kubernetes のソースコードに直に組み込まれた実装("in-tree")がされていました．そのため，3rd パーティのストレージベンダは Kubernetesのソースコードへアップストリームする必要がありました．v1.9 では，まだAlpha ながらも3rd パーティのベンダがKubernetesのソースコードにアップストリームする必要なく独自に開発できる[Container Storage Interface (CSI)](http://blog.kubernetes.io/2018/01/introducing-container-storage-interface.html)が登場しています．
　今回の検証では，CSIではなく，ソースコードに直に組み込まれた実装("in-tree")を使っています．

## 外部ストレージの準備  
　事前準備として，Persistent Volume として利用する外部ストレージを準備します．
外部ストレージとしては，[Volumes](https://kubernetes.io/docs/concepts/storage/volumes/)に記載されているストレージを利用することが出来ます． 
本検証では，minikube の母艦となる Mac OSX(10.12.6) を NFS Server にして設定します．
共有するディレクトリを作成し，NFS Server の設定ファイル(/etc/exports)を作成します. 
minikube のIP が192.168.99.100 のため，下記例では192.168.99.0 のネットワークからのアクセスを許可しています．

```
$ sudo mkdir /share
$ sudo chmod 777 /share
$ sudo vi /etc/exports
/share -mapall=nobody:wheel -network 192.168.99.0 -mask 255.255.255.0
```

次に，NFS Server のデーモンを起動します.

```
$ sudo nfsd start
$ sudo nfsd update  
$ sudo showmount -e  
Exports list on localhost:
/share                              192.168.99.0
```

NFS Server の設定が出来ているかの確認のために，minikube からマウントしてみます．
minikube から母艦の Mac へは 192.168.99.1 でアクセス出来ます．
df コマンドで確認し，Mac の /share ディレクトリを NFS でマウント出来ているのが確認できます．

```
$ minikube ssh
                         _             _            
            _         _ ( )           ( )           
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __  
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ sudo mkdir /share
$ sudo mount -t nfs 192.168.99.1:/share /share
$ df -h
Filesystem           Size  Used Avail Use% Mounted on
devtmpfs             933M     0  933M   0% /dev
tmpfs               1001M     0 1001M   0% /dev/shm
tmpfs               1001M   22M  979M   3% /run
tmpfs               1001M     0 1001M   0% /sys/fs/cgroup
/dev/sda1             17G  977M   15G   7% /mnt/sda1
/Users               931G   50G  882G   6% /Users
192.168.99.1:/share  931G   49G  882G   6% /share
```

minikube から抜ける前に，マウントを外しておきましょう．

```
$ sudo umount /share
```

## Persistent Volume の定義  
　次に，Kubernetes の Persistent Volume を定義してみましょう．
下記内容を，`nfs-pv.yaml`として保存してください．

```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs001
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  # PersistentVolumeClaim を削除した時の動作
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
    - hard
  ## マウント先のNFS Serverの情報を記載
  nfs:
    path: /share
    server: 192.168.99.1
```
- Persistent Volume へのアクセスモード(accessModes) は次の3つから指定
    - ReadWriteOnce: 1 つのノードからRead/Write でマウントできる
    - ReadOnlyMany: 複数のノードからRead Onlyでマウントできる
    - ReadWriteMany: 複数のノードからR/Wでマウントできる

accessMode は使用する外部ストレージにより指定できるものが決まっているため，
[kuberbetes.io の persistent volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes)で事前に確認してください．

- Persistent Volume Claim が削除された時のポリシー(persistentVolumeReclaimPolicy)は次の3つから指定
    - Retain: 手動でデータを消す(データは自動で削除されない)
    - Recycle: データが削除される(rm -rf /thevolume/*)
    - Delete: 関連するストレージリソースを削除する(AWS EBS, GCE PD, Azure Disk, or OpenStack Cinder volume)
 
## Persistent Volume の作成
　定義した Persistent Volume を作成をしてみましょう．

```
$ kubectl create -f nfs-pv.yaml
persistentvolume "nfs001" created
```
作成できたか確認してみます．

```
$ kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
nfs001    1Gi        RWX            Recycle          Available             slow                     1m
```

# Persistent Volume Claim の定義
　Persistent Volume Claim は，Kubernetes の Pod から Persistent Volume をマウントする際に利用します．Kubernetes は，Persistent Volume Claim で要求された性能を満たすストレージを Persistent Volume の中から選択します．
　Persistent Volume Claim を定義してみましょう．
下記内容を，`nfs-pvc.yaml`として保存してください．

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-claim1
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  storageClassName: slow
```
## Persistent Volume Claim の作成
　定義した Persistent Volume Claim を作成をしてみましょう．

```
$ kubectl create -f nfs-pvc.yaml
persistentvolumeclaim "nfs-claim1" created
```
作成できたか確認してみます．

```
$ kubectl get pvc
NAME         STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
nfs-claim1   Bound     nfs001    1Gi        RWX            slow           57s

```
Persistent Volume の CLAIM 情報を見てみると，nfs-claim1 の Persistent Volume Claim にマッチされているのがわかります．

```
$ kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                STORAGECLASS   REASON    AGE
nfs001    1Gi        RWX            Recycle          Bound     default/nfs-claim1   slow                     13m
```
## Persistent Volume をマウントしたPodをデプロイ
　次に，Persistent Volume をマウントした Pod を定義してみましょう．
下記内容を，`my-nginx-pod.yaml` として保存してください．

```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
      # マウント対象となる Persistent Volume に対応する
      # Persistent Volume Claimを指定
      persistentVolumeClaim:
        claimName: nfs-claim1
```
定義した Pod をデプロイします．

```
$ kubectl create -f my-nginx-pod.yaml
pod "my-nginx-pod" created
```

## マウントされた Persistent Volume の確認
　`kubectl describe` にて，デプロイした Pod を確認すると，nfs-claim1 の Persistent Volume Claim を使い，マウントしているのがわかります．

```
$ kubectl describe pod my-nginx-pod
Name:         my-nginx-pod
Namespace:    default
Node:         minikube/192.168.99.100
Start Time:   Mon, 15 Jan 2018 15:40:39 +0900
IP:           172.17.0.3
Containers:
  nginx:
    Container ID:   docker://f71d3e738d8eaf6ac1e856b6c8515f2de1987f0e23485618e36e9a991031ce60
    Image:          nginx
    Image ID:       docker-pullable://nginx@sha256:285b49d42c703fdf257d1e2422765c4ba9d3e37768d6ea83d7fe2043dad6e63d
    Port:           80/TCP
    State:          Running
      Started:      Mon, 15 Jan 2018 15:40:44 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /usr/share/nginx/html from mydata (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-6mqpq (ro)
Conditions:
  Type           Status
  Initialized    True
  Ready          True
  PodScheduled   True
Volumes:
  mydata:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  nfs-claim1
    ReadOnly:   false
  default-token-6mqpq:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-6mqpq
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     <none>
Events:
  Type    Reason                 Age   From               Message
  ----    ------                 ----  ----               -------
  Normal  Scheduled              1m    default-scheduler  Successfully assigned my-nginx-pod to minikube
  Normal  SuccessfulMountVolume  1m    kubelet, minikube  MountVolume.SetUp succeeded for volume "default-token-6mqpq"
  Normal  SuccessfulMountVolume  1m    kubelet, minikube  MountVolume.SetUp succeeded for volume "nfs001"
  Normal  Pulling                1m    kubelet, minikube  pulling image "nginx"
  Normal  Pulled                 1m    kubelet, minikube  Successfully pulled image "nginx"
  Normal  Created                1m    kubelet, minikube  Created container
  Normal  Started                1m    kubelet, minikube  Started container
```
実際に，デプロイしたコンテナにログインし確認してみましょう．

```
$ kubectl exec my-nginx-pod -ti /bin/bash
root@my-nginx-pod:/# df -h
Filesystem           Size  Used Avail Use% Mounted on
overlay               17G  978M   15G   7% /
tmpfs               1001M     0 1001M   0% /dev
tmpfs               1001M     0 1001M   0% /sys/fs/cgroup
/dev/sda1             17G  978M   15G   7% /etc/hosts
shm                   64M     0   64M   0% /dev/shm
192.168.99.1:/share  931G   49G  882G   6% /usr/share/nginx/html
tmpfs               1001M   12K 1001M   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs               1001M     0 1001M   0% /sys/firmware
```
/use/share/nginx/html ディレクトリは，192.168.99.1:/share をマウントしたものであることが確認できます．
試しに，/use/share/nginx/html ディレクトリにファイルを作ってみましょう．

```
root@my-nginx-pod:~# echo -n "Hello Persistent Volume" > /usr/share/nginx/html/index.html
root@my-nginx-pod:~# ls /usr/share/nginx/html/          
index.html
root@my-nginx-pod:~# cat /usr/share/nginx/html/index.html
Hello Persistent Volume
```
my-nginx-pod 上で作られたファイルが，母艦の Mac に格納されているかを確認します．
別のターミナルを開き，共有したディレクトリを確認します．

```
$ ls -l /share
total 8
-rw-r--r--  1 nobody  wheel  23  1 15 15:55 index.html
$ cat /share/index.html
Hello Persistent Volume
```

my-nginx-pod 上で書き込まれたファイルが母艦の Mac に格納されています．
また，ファイルのユーザは，/etc/exports で指定した nobody:wheel となります．

# Pod, Persistent Volume Claim, Persistent Volume の削除
　作成した Pod を削除します．

```
$ kubectl delete pod my-nginx-pod
pod "my-nginx-pod" deleted
```
Pod を削除しても，Persistent Volume に保存したファイルは削除されません．
母艦の Mac にてファイルが削除されていないことを確認します．

```
$ ls -l /share
total 8
-rw-r--r--  1 nobody  wheel  23  1 15 15:55 index.html
```
このように Pod が削除されても，再度 Pod より同じディレクトリをマウントすれば，
永続的にデータを保存し続けることが可能となります．
次に，Persitent Volume Claim を削除します．

```
$ kubectl delete pvc nfs-claim1
persistentvolumeclaim "nfs-claim1" deleted
```

Persistent Volume Claim が削除されると，母艦の Mac から作成されたファイルも削除されます．

```
$ ls -l /share
$
```
Persistent Volume Claim の削除した時の動作は，Persistent Volume の定義，`nfs-pv.yaml` の persistentVolumeReclaimPolicyに記載された動作に従います．
Persistent Volume を削除します．

```
$ kubectl delete pv nfs001
persistentvolume "nfs001" deleted
```

## まとめ
　Kubernetes の PersistentVolume, Persistent Volume Claim の動作は上記の検証で試した通りです. ストレージの専門家の視点からみたら，Persistent Volume は，外部ストレージへのエントリーポイントとして比較的理解は出来るかと思いますが，難しいのは, Persistent Volume Claim の存在だと思います．
　Kubernetes では，仮想化されたインフラの世界を作り上げることで，ユーザに物理リソースを意識させず，物理障害などを回避するセルフヒーリングなどを実現しています．そのため，Persistent Volume Claim は，仮装化されたインフラの世界を実現するための，ストレージの定義になります．
　これを一歩進め，運用シーンについて考えてみると，Persistent Volume は，Kunernetes の下回りのインフラ環境を理解して Kubernetesを構築するインフラ管理者(ストレージ管理者含む)が定義し，Persistent Volume Claim は Kunernetes 上でPod を定義するユーザが定義するのが，しっくりくる運用ではないでしょうか．

## 参考情報
- [Kubernetes Concept - Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)
- [Introducing Container Storage Interface (CSI) Alpha for Kubernetes](http://blog.kubernetes.io/2018/01/introducing-container-storage-interface.html)