hostPathとlocalのPersistentVolumeの違い

PodにPersistentVolmueをマウントしたいとき、特にオンプレのKubernetesで、都合のいいNFSサーバーがなかったりすると、ノード上のローカルのファイルシステムをマウントしたいことがあります。その場合`hostPath`が使えますが、`local`というのもあります。違いがよくわからなかったので調べたメモです。

## VolumeとPersistentVolume

- [Volumes](https://kubernetes.io/docs/concepts/storage/volumes/)
- [Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)

わかりにくいですが、VolumeとPersistentVolumeは別です。Volumeとしての`hostPath`と、PersistentVolumeとしての`hostPath`があります。

`local`については、[その説明がVolumeページにある](https://kubernetes.io/docs/concepts/storage/volumes/#local)ので混乱しますが、VolumeではなくPersistentVolumeです。以下のAPIドキュメントを確認すると、`local`というPesistentVolumeはありますが、`local`というVolumeはないことが確認できます。

- [Volume v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#volume-v1-core)
- [PersistentVolume v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#persistentvolume-v1-core)

## hostPath（Volume）

`hostPath`のVolumeをPodのSpecに直接定義してコンテナにマウントできます。

```yaml:mypod.yaml
kind: Pod
apiVersion: v1
metadata:
  name: mypod
spec:
  containers:
  - name: liberty
    image: websphere-liberty:18.0.0.4-kernel
    volumeMounts:
    - mountPath: /logs
      name: my-volume
  volumes:
  - name: my-volume
    hostPath:
      path: /data
```

単純なPodではなくDeployment等から管理されるPodの場合、PodのSpecは基本的には同じになるので、上記のようなPodTemplateを書いたとすると、各Podはスケジュールされたノードの`/data`をマウントします。PodはnodeSelector等で指定していなければどのノードで稼働するかわかりません。また、1つのノードで複数のPodが稼働すると、同じ`hostPath`をマウントしてしまいます。このため`hostPath`のVolumeをマウントしても問題がない状況はかなり限定されます。

`hostPath`のVolumeと相性がよいのは、各ノードで1つのみPodが稼働することが保証されるDaemonSetの場合です。[IBM Cloud Private](https://www.ibm.com/cloud-computing/jp-ja/products/ibm-cloud-private/)の実装をみても、DaemonSetとして稼働しているコンテナから`hostPath`のVolumeをマウントしているケースが多くあります。

（補足）
以下のPersistentVolumeの場合も同様ですが、ノード上の`/data`ディレクトリーには適切な権限が必要です。上記のLibertyコンテナの場合は`1001`のUIDで起動するので`chown 1001:0 /data`してあげる必要があります。

## hostPath（PersistentVolume）

PersistentVolumeとして`hostPath`はAPIドキュメントに以下のように記載されており、シングルノード環境でのテスト用としてのみ使うように記載されています。Volumeとしての`hostPath`のAPIドキュメントにはこのような記載はありません。

> This is useful for single-node development and testing only! On-host storage is not supported in any way and WILL NOT WORK in a multi-node cluster.

以下のように`hostPath`のPersistentVolumeを定義できます。

```yaml:my-pv-hostpath.yaml
kind: PersistentVolume
apiVersion: v1
metadata:
  name: my-pv-hostpath
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  hostPath:
    path: /data
```

```yaml:my-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  storageClassName: manual
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
```

コンテナからはPersistentVolumeClaimをマウントします。

```yaml:mypod.yaml
kind: Pod
apiVersion: v1
metadata:
  name: mypod
spec:
  containers:
  - name: liberty
    image: websphere-liberty:18.0.0.4-kernel
    volumeMounts:
    - mountPath: /logs
      name: my-volume
  volumes:
  - name: my-volume
    persistentVolumeClaim:
      claimName: my-pvc
```

単純なPodではなくDeployment/DaemonSetから管理されるPodの場合、全てのPodのSpecは基本的には同じになるので、上記のようなPodTemplateを書いたとすると、各Podが同じPersistentVolumeClaimをマウントしようとしてしまいます。しかし`hostPath`は`ReadOnlyMany`や`ReadWriteMany`を[サポートしていない](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes)ため問題がありそうです（同じノード上で稼働する複数のPodからマウントするのもOKかもしれません）。このためDeploymentの場合はレプリカ数1でしか使えません。StatufulSetの場合はPersistentVolumeClaimTemplateを使ってPod毎に異なるPersistentVolumeClaimを定義できるので、各Podに異なるPersistentVolumeをアサインできます。

## local

`local`はVolumeではなくPersistentVolumeです。`hostPath`との違いはなんでしょうか。[ドキュメント](https://kubernetes.io/docs/concepts/storage/volumes/#local)によると、`hostPath`と比べると、Node Affinityを使ってボリュームが実際に存在するノードを指定できるので、Podを手動でスケジュールする必要がないことが書かれています。

> Compared to hostPath volumes, local volumes can be used in a durable and portable manner without manually scheduling Pods to nodes, as the system is aware of the volume’s node constraints by looking at the node affinity on the PersistentVolume.

試してみたところ、PersistentVolumeであれば`local`だけでなく`hostPath`でも`nfs`でもNode Affinityは設定でき機能したので、上記の"Compared to hostPath volumes"という部分は、PersistentVolumeの`hostPath`ではなくVolumeの`hostPath`を指しているように思われます。

`local`の場合はNode Affintyの設定が必要です。設定していないと下記のように怒られます。

```shell-session
$ kubectl apply -f pv-local-test.yaml
The PersistentVolume "my-pv-local-test" is invalid: metadata.annotations: Required value: Local volume requires node affinity
$
```

ただし、Node Affinityで例えば以下のように全てのノードにマッチするような表現を書くことも可能です。

```yaml
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: Exists
```

機能的にはNode Affinityの設定が必須であるくらいしか差異はなさそうですが、PersistentVolumeの`hostPath`はマルチノードがサポートされていないことが明確に書かれています。`local`のほうはv1.10からのBetaフィーチャーですが、以下のドキュメントに書かれているように、`hostPath`が持つ課題を解決するための新しい機能であり、精力的に開発がされているようなので、基本的にはこちらを使うべきと思います。

- [Local Persistent Volumes for Kubernetes Goes Beta](https://kubernetes.io/blog/2018/04/13/local-persistent-volumes-beta/)

[IBM Cloud Private](https://www.ibm.com/cloud-computing/jp-ja/products/ibm-cloud-private/)での実装をみてみると、ローカルにデータを持つためにStatefulSetとして稼働しているコンテナからPersistentVolumeマウントする場合は、`local`のPersistentVolumeをマウントしています。

`local`のPersistentVolumeは以下のように使います。

```yaml:my-pv-local.yaml
kind: PersistentVolume
apiVersion: v1
metadata:
  name: my-pv-local
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  local:
    path: /data
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - node1
```

```yaml:my-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  storageClassName: manual
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
```

```yaml:mypod.yaml
kind: Pod
apiVersion: v1
metadata:
  name: mypod
spec:
  containers:
  - name: liberty
    image: websphere-liberty:18.0.0.4-kernel
    volumeMounts:
    - mountPath: /logs
      name: my-volume
  volumes:
  - name: my-volume
    persistentVolumeClaim:
      claimName: my-pvc
```

単純なPodではない場合、PersistentVolumeの`hostPath`の場合と同じく、レプリカ数1のDeploymentか、StatufulSetから使用する必要があります。

## まとめ

PersistentVolumeとしての`hostPath`と`local`には明確な機能的な違いはなさそうですが、`local`はBetaフィーチャー（追記：1.14でGAとなりました）である一方、`hostPath`はマルチノード環境ではサポートされていないことが書かれています。利用方法についてまとめると以下です。

|リソースタイプ|ボリュームタイプ|VolumeNodeAffinity|主な利用方法|
|---|---|---|---|
|Volume|hostPath|N/A|DaemonSetを構成するPodから使う|
|PersistentVolume|hostPath|設定可能|シングルノード環境でテスト目的でのみ使う|
|PersistentVolume|local|設定必須|StatefulSetを構成するPodから使う|

## 参考

- [Local Persistent Storage User Guide](https://github.com/kubernetes-incubator/external-storage/tree/master/local-volume)
- [sig-storage-local-static-provisioner](https://github.com/kubernetes-sigs/sig-storage-local-static-provisioner)