【KubernetesのPersistentVolume】その１- 外部ストレージをPodにmountする

## 概要
KubernetesのPersistentVolume(永続ストレージ)を使って、NFSサーバーをPodにmountする。
Dynamic Provisioningについてはこのページでは触れない。以下を参照。
[【KubernetesのPersistentVolume】その２- Dynamic Provisionerを使ってNFSサーバーをPodにmountする](https://qiita.com/Esfahan/items/68e2d97545091cb6d0ac)

## 目次
- [Kubernetesハンズオン目次](https://qiita.com/Esfahan/items/4fcf51622fed20dffb88)

## 前提知識
### PersistentVolumeの仕組み
まず、PersistentVolumeがPodにmountされる仕組みについて説明する。
以下の表の用に、3つの定義ファイルを用意する。

| filename | Resource | description |
| --- | --- | --- |
| pod.yml | Pod | Podの定義 |
| persistent-volume-claim.yml | PersistentVolumeClaim | Podが要求するVolumeのspecを定義する |
| persistent-volume.yml | PersistentVolume | Volumeの定義 |


- persistent-volume.ymlでは、Volumeの定義をする。
- pod.ymlでpodの定義と、関連付けるpersistent-volume-claim.ymlを定義する。
- persistent-volume-claim.ymlには、Podが要求するspecのVolumeの定義をする。
- PersistentVolumeClaimは、要求に一番近いspecのPersistentVolume探し、mountする。

つまりPodは、PersistentVolumeClaimを通して「こういうspecのVolumeが欲しい」と要求を出すと、その要求に一番近いPersistentVolumeがmountされるという仕組みになっている。
例えば、Podが5GiのVolumeが欲しいと要求を出したが、実際には10Gi, 20GiのVolumeしか存在していない場合、一番近い10GiのVolumeがmountされる。

ただし、persistent-volume.ymlとmetadata.name、metadata.labelsと、persistent-volume-claim.ymlのspec.selector.matchLabels、spec.selector.matchExpressionsの値を合わせることで、PersistentVolumeとPersistentVolumeClaimのマッチングをより明示的に、期待したとおりにできる。
ここで指定した条件が見つからない場合には自動割当、最悪いずれも割り当てられないという状態になる。

以下のページが分かりやすい。
参考：[KubernetesのConfig＆Storageリソース（その2）](https://thinkit.co.jp/article/14195)

### Dynamic Provisioningについて
Dynamic Provisioningを利用することで、PersistentVolumeClaimが要求を出したタイミングで、動的にPersistentVolumeを作成することが可能となるが、ここでは触れず、以下のページで説明する。
[【KubernetesのPersistentVolume】その２- Dynamic Provisionerを使ってNFSサーバーをPodにmountする](https://qiita.com/Esfahan/items/68e2d97545091cb6d0ac)

## ハンズオン
実際にやってみる。
今回はNFSサーバーの/var/share/nfsディレクトリを、Podの/usr/share/nginx/htmlにmountする。

### 環境
- CentOS 7.6
- Minikube v1.1.1
- Kubernetes v1.14.3

ここではMinikubeで構築した環境のPodに、NFSサーバーをmountする。
**関連記事：**[Minikubeを使ってローカルにkubernetes環境を構築](https://qiita.com/Esfahan/items/f5c846088281c39f73a4)


### 事前準備
#### NFSサーバーの構築
以下を参考にNFSサーバーを構築する。

- [CentOS7にNFSサーバーを秒で構築](https://qiita.com/Esfahan/items/1b5b3a74dcd86fd30e5c)

### PersistentVolume
Volumeの定義をする。

```yaml:persistent-volume.yml
apiVersion: v1
kind: PersistentVolume
###################################################################
# persistent-volume-claim.ymlのspec.selector.matchLabels、
# spec.selector.matchExpressions以下の値と合わせることで
# PersistentVolumeとPersistentVolumeClaimのマッチング精度を高める。
###################################################################
metadata:
  # persistent-volume-claim.ymlのspec.selector.matchLabels.typeと合わせる
  name: nfs001
  labels:
    type: nfs
    environment: stg
  # Annotaion: https://blog.a-know.me/entry/2018/08/13/223633
  annotations:
    volume.beta.kubernetes.io/storage-class: "slow"
spec:
  # Volumeの容量を定義する
  capacity:
    storage: 1Gi
  ##################################################
  # ReadWriteOnce: 1つのノードからR/Wでマウントできる
  # ReadOnlyMany: 複数のノードからReadOnlyでマウントできる
  # ReadWriteMany: 複数のノードからR/Wでマウントできる
  ##################################################
  accessModes:
    - ReadWriteMany
  # PersistentVolumeClaim を削除した時の動作
  persistentVolumeReclaimPolicy: Recycle
  # persistent-volume-claim.ymlのspec.storageClassNameと合わせる
  storageClassName: slow
  # mountコマンドの-oオプション。hard, soft, nolock
  mountOptions:
    - hard
  nfs:
    # mountするNFSサーバーのディレクトリ
    path: /var/share/nfs
    # NFSサーバーのホスト
    server: xxx.xxx.xxx.xxx
```

### PersistentVolumeClaim
Podが要求するPersistentVolumeの内容を定義する。

```yaml:persistent-volume-claim.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  # pod.ymlのspec.volumes.persistentVolumeClaim.classNameと合わせる
  name: nfs-claim1
  # Annotaion: https://blog.a-know.me/entry/2018/08/13/223633
  annotations:
    "volume.beta.kubernetes.io/storage-class": "slow"
# 要求するpersistentVolumeのspec
spec:
  selector:
    matchLabels:
      # persistent-volume.ymlのmetadata.labels.typeと合わせる
      type: "nfs"
    matchExpressions:
      ###################################################################
      # persistent-volume.ymlのmetadata.labels以下の値と合わせることで
      # PersistentVolumeとPersistentVolumeClaimのマッチング精度を高める。
      # operatorはIn, NotIn, Exists, DoesNotExistのどれかを選ぶ。
      # https://kubernetes.io/docs/concepts/storage/persistent-volumes/
      ###################################################################
      - {key: environment, operator: In, values: [stg]}
  ##################################################
  # ReadWriteOnce: 1つのノードからR/Wでマウントできる
  # ReadOnlyMany: 複数のノードからReadOnlyでマウントできる
  # ReadWriteMany: 複数のノードからR/Wでマウントできる
  ##################################################
  accessModes:
    - ReadWriteMany
  # PersistentVolumeに要求するリソース
  resources:
    requests:
      storage: 1Gi
  # persistent-volume.ymlのspec.storageClassNameと合わせる
  storageClassName: slow
```

### Pod
Podを定義する。

```yaml:pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: my-nginx-pod
spec:
  # コンテナの定義
  containers:
  - name: nginx-container
    image: nginx
    ports:
      - containerPort: 80
    # マウントするディレクトリを指定
    volumeMounts:
      # 以下のspec.volumes.nameと合わせる
      - name: mydata
        # mountするコンテナ内のディレクトリ
        mountPath: "/usr/share/nginx/html"
  volumes:
    # 上記のspec.containers.volumeMounts.nameと合わせる
    - name: mydata
      # 要求(mount)するPersistentVolumeClaimを指定
      persistentVolumeClaim:
        # persistent-volume-claim.ymlのmetadata.nameと合わせる
        claimName: nfs-claim1
```

## 作成

```shell-session
$ sudo kubectl apply -f persistent-volume.yml -f persistent-volume-claim.yml -f pod.yml
persistentvolume/nfs001 created
persistentvolumeclaim/nfs-claim1 created
pod/my-nginx-pod created
```

## 起動確認
Podが起動した。

```diff
$ sudo kubectl get pod
NAME           READY   STATUS    RESTARTS   AGE
+my-nginx-pod   1/1     Running   0          32s
```

PersistentVolumeが作成された。正常に作成されるとSTATUSがBoundになる。

```diff
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   REASON   AGE
+nfs001   1Gi        RWX            Recycle          Bound    default/nfs-claim1   slow                    36s                                               50s
```

PersistentVolumeClaimが作成された。

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

## mountされているか確認
NFSサーバーの/var/share/nfsディレクトリを、Podの/usr/share/nginx/htmlにmountしたので、
まず、NFSサーバーの/var/share/nfsにテスト用ファイルを作成する。

```shell-session
$ echo 'This is a sample page.' > /var/share/nfs/index.html
```

次に、Podで/var/share/nfsが/usr/share/nginx/htmlにmountされているか確認。
index.htmlが見えればOK。

```shell-session
$ sudo kubectl exec -it my-nginx-pod ls /usr/share/nginx/html
index.html

$ sudo kubectl exec -it my-nginx-pod cat /usr/share/nginx/html/index.html
This is a sample page.
```

## その他
PersistentVolumeが完全にCreateされる前にPodを起動すると以下のWarningが出るが、最終的にmountされていることが確認できれば問題ない。

```
$ sudo kubectl describe pod my-nginx-pod
Events:
  Type     Reason            Age                    From               Message
  ----     ------            ----                   ----               -------
  Warning  FailedScheduling  6m25s (x2 over 6m25s)  default-scheduler  pod has unbound immediate PersistentVolumeClaims
  Normal   Scheduled         6m23s                  default-scheduler  Successfully assigned default/my-nginx-pod to minikube
  Normal   Pulling           6m23s                  kubelet, minikube  Pulling image "nginx"
  Normal   Pulled            6m19s                  kubelet, minikube  Successfully pulled image "nginx"
  Normal   Created           6m19s                  kubelet, minikube  Created container nginx
  Normal   Started           6m19s                  kubelet, minikube  Started container nginx
```

## 関連記事
- [【KubernetesのPersistentVolume】その２- Dynamic Provisionerを使ってNFSサーバーをPodにmountする](https://qiita.com/Esfahan/items/68e2d97545091cb6d0ac)


## 参考
- [KubernetesのConfig＆Storageリソース（その2）](https://thinkit.co.jp/article/14195)
- [Kubernetes: PersistentVolume概要とminikubeでの検証](https://qiita.com/ysakashita/items/924786aa24ef3bb864ef)