#はじめに
今回はKubernetes-incubator/external-storageにて公開されているKubernetes NFS-Client Provisionerのnfs-clientについて検証します。2019/3時点では、Kubernetes-incubator/external-storageでは、NFS関連として今回検証するnfs-clientとnfsの2つ提供されています。以下に違いを示します。
- nfs-clientは、NFS ClientのProvisionerを提供。NFS Serverは別途用意する必要あり。
- nfsは、Kubernetes上でNFS Serverを提供。ただし、Podが削除されるとデータも削除。
特に今回検証するnfs-clientは、以下のような場合に便利です。
- ブロックストレージを準備するのが面倒
- Pod間でデータをシェアするのではなくブロックストレージのように1Pod:1NFSで利用したい
- NFSでDynamic Provisioningを使いたい (KubernetesのオリジナルのNFS対応(in-tree)では不可)
個人ユースなどで利用する場合においては、最後のDynamic Provisioningを使いたいという方は多いのではないでしょうか。
検証
この検証では、Kubernetesとは別にNFS Serverを準備した後、StorageClassで指定されるProvisionerとなるnfs-client-provisionerをKubernetes上へデプロイし、これを利用したStorageClassを作成します。その後、このStorageClassを使ってDynamic Provisioningの動作検証を行います。
検証環境
以下に、検証環境を示します。
- NFS Server: macOS (version 10.12.6)
- Kubernetes v1.13.1 (Master x 1, Worker x 2)
NFS Serverの準備
最初にNFS Serverを準備します。本検証ではmacOS(IP: 192.168.0.11)をNFS Serverとして利用します。まず、NFSで共有するディレクトリを作成します。
$ sudo mkdir -p /share/kubernetes
$ sudo chmod -R 777 /share/kubernetes
次に、NFS Serverの設定ファイル/etc/exports
ファイルを編集します。
/share/kubernetes -alldirs 192.168.0.23 192.168.0.24 192.168.0.25
今回検証するnfs-clientは、上記の共有ディレクトリ配下に{namespace}-${pvcName}-${pvName}
のディレクトリを自動生成します。
そのため、NFS Serverの設定では共有ディレクトリ配下のサブディレクトリのマウントを許可する-alldirs
オプションを付与する必要があります。また、上記の設定では、KubernetesのNode以外からマウントできないようにNodeのIPアドレスを指定しています。
次に、Kubernetesの全NodeにNFS Clientのパッケージ(nfs-common
)をインストールします。
下記は、1台の例ですが、全てのNodeで行います。
$ ssh 192.168.0.23
$ sudo apt-get update
$ sudo apt-get install -y nfs-common
Provisioner(nfs-client-provisioner)のデプロイ
まず、Provisionerのデプロイ先のNamespaceを作成します。
ここではnfs-client-provisioner
のNamespaceを作成します。
$ kubectl create ns nfs-client-provisioner
次に、RBACをデプロイします。
RBACのManifest(rbac.yaml
)を以下に示します。
kind: ServiceAccount
apiVersion: v1
metadata:
name: nfs-client-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- 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
namespace: nfs-client-provisioner
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
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
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: nfs-client-provisioner
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
上記のManifestをデプロイします。
$ kubectl create -f rbac.yaml
次に、Provisionerをデプロイします。
ProvisionerのManifest(deployment.yaml
)を以下に示します。
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: nfs-client-provisioner
namespace: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
nodeSelector:
beta.kubernetes.io/arch: amd64
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
# NFS Serverのホスト指定
- name: NFS_SERVER
value: 192.168.0.11
# 共有ディレクトリ
- name: NFS_PATH
value: /share/kubernetes
volumes:
- name: nfs-client-root
nfs:
# NFS Serverのホスト指定
server: 192.168.0.11
# 共有ディレクトリ
path: /share/kubernetes
- .spec.template.spac.containers.env の
NFS_SERVER
の値としてNFS Serverのホスト名(or IPアドレス)を設定します。本検証ではNFS ServerのmacOSのIPアドレス(192.168.0.11
)を指定しています。 -
NFS_PATH
の値として、NFS Serverで共通した共有ディレクトリ名を指定します。 - .spec.template.spac.volumes.nfsも上記と同様にNFS Serverのホスト名(or IPアドレス)と共有ディレクトリ名を指定します。
上記のManifest(deployment.yaml
)をデプロイします。
$ kubectl create -f deployment.yaml
次に、StorageClass(SC)をデプロイします。
以下に、SCのManifest(storageclass.yaml
)を示します。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: fuseim.pri/ifs
parameters:
archiveOnDelete: "false"
上記のManifest(storageclass.yaml
)をデプロイします。
$ kubectl create -f storageclass.yaml
デプロイしたSCを確認すると、SC(managed-nfs-storage
)がデプロイされています。
$ kubectl get sc
NAME PROVISIONER AGE
managed-nfs-storage fuseim.pri/ifs 3h24m
以上で、nfs-clientのセットアップが完了しました。
Dynamic Provisioningの動作検証
nfs-clientの動作検証を行います。
nfs-clientによりブロックストレージと同様にDynamic Provisioningが利用できるようになります。
そこで、volumeClaimTemplatesを使ったDynamic Provisioningを行うManifestを使って検証します。Manifest(nginx.yaml
)を以下に示します。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
serviceName: nginx
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: managed-nfs-storage
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
このManifest(nginx.yaml
)は、.spec.volumeClaimTemplatesの、storageClassNameにnfs-clientのSC(managed-nfs-storage
)を指定しています。
Manifest(nginx.yaml
)をデプロイします。
$ kubectl create -f nginx.yaml
デプロイされたPod、PVC, PVを確認します。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 64s
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-nginx-0 Bound pvc-1a9b86ad-172e-11e9-951e-080027571559 1Gi RWO managed-nfs-storage 31s
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-1a9b86ad-172e-11e9-951e-080027571559 1Gi RWO Delete Bound default/data-nginx-0 managed-nfs-storage 36s
すると、spec.volumeClaimTemplatesで指定したPVCが生成され、これに対応したPVがSC(managed-nfs-storage
)より自動生成されているのが確認できます。
次にmacOSの共有ディレクトリを確認します。
macOS(NFS Server)上でtreeコマンドで/share
ディレクトリを確認すると、default-data-nginx-0-pvc-1a9b86ad-172e-11e9-951e-080027571559
が生成されているのが確認できます。
$ tree /share/
/share/
└── kubernetes
└── default-data-nginx-0-pvc-1a9b86ad-172e-11e9-951e-080027571559
以上でDynamic Provisioningの動作検証は終わりです。
PVC,PVの削除の動作検証
デプロイしたPod, PVC, PVを削除します。
まず、デプロイしたnginxのPodを削除します。
$ kubectl delete -f nginx.yaml
続いて、PVC、PVを削除します。
$ kubectl delete pvc data-nginx-0
PVはRECLAIM POLICYにDelete
が設定されているため、PVCが削除されると自動で削除されます。
ここで、macOS(NFS Server)の共有ディレクトリを確認してみます。
$ tree /share/
/share/
└── kubernetes
すると、/share/kubernetes
ディレクトリ配下にあったdefault-data-nginx-0-pvc-1a9b86ad-172e-11e9-951e-080027571559
ディレクトリが削除されているのが確認できます。
以上でPVC,PVの削除に関する動作検証は終わりです。
このように、nfs-clientを使うことで、共有された/share/kubernetes
ディレクトリ配下にDynamic ProvisioningでPVが作成される都度、ディレクトリが自動作成され、PVが削除されると自動削除されます。
おまけ:StorageClassにDefaultを設定
今回の検証のnfs-clientとは関係ありませんが、SCが増えてくると、優先的に利用するSCにDefault設定しておくと便利です。これにより、PVCなどでSCの指定を省略した場合、Defaultに設定されたSCを選択し使用してくれるようになります。
以下に、SCにDefaultを設定する例を示します。
$ kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
Default設定されたSCは、Kubectl getコマンドでみると、(default)
が付与されます。
$ kubectl get sc
NAME PROVISIONER AGE
local-disks kubernetes.io/no-provisioner 70d
managed-nfs-storage (default) fuseim.pri/ifs 71d
感想
今回は、Kubernetes-incubator/external-storageのnfs-clientの動作検証を行いました。
オリジナルのKubernetesのNFS(in-tree)ではDynamic Provisioningが行えないため、使い所が限定されていました。しかし、nfs-clientによりDynamic Provisioningが利用できるようになるため、NFSの利用シーンも広がるのではないでしょうか。個人環境では非常に便利です。ただし、注意しなくてはいけないのは、NFSはやはりNFSです。ブロックストレージのようにraw deviceとしては利用できないです。
さらに、NFSのバッファサイズはファイルシステム&NFSプロトコルのデータ転送に適した値となっています。nfs(5)のmanページによると1024バイトがデフォルトになります。HDD中心からSSD(Flash)が主力になりつつあるストレージからみたら、NFSのバッファサイズはパフォーマンスボトルネックの原因にもなります。SSD(Flash)内部のコントローラでは4Kチューニングされている製品が多く、SSD(Flash)を利用する場合は、4K単位でRead/Writeをするのがベターパフォーマンスを発揮できます。
また、NFSはファイルロック制御を全くもたないブロックストレージとは異なり、ファイルシステム&NFSコントロールのプロトコル層(RPC)でロック制御の機構を持っています。そのため、MySQLなどのリレーショナルデータベースでNFSを使うのは、特別な事情がない限りは避けた方が良いでしょう。「MySQL Documentation: Using NFS with MySQL」に、NFSに関する注意点がまとまっているので、リレーショナルデータベースでNFSを使う場合には、一読されることをお勧めします。
DB屋さん&ストレージ屋さんからみたらリレーショナルデータベースでNFSを使うなんてあり得ないと20年以上も言われ続けているコモンセンスとも言えるKnow-Howだと思います。しかし、コンテナの普及によりリレーショナルデータベースが簡単に構築できるようになってきた今日では、下位のインフラを気にしなくても簡単に動いてしまうのです。そのためこのような下位のインフラに付随するコモンセンスは、コモンセンスでなくなってきたと感じます。
ストレージやデータベースについて、ディープな内容を書きましたがNFSは便利です。便利な反面、注意点も多いことも理解した上で利用するのが良いです。特に、StorageClassを準備するKubernetesのクラスタ管理者は、コンテナをデプロイするユーザが安全に利用できるようStorageClassのDefault設定などをうまく使い、下位のインフラのKnow-Howを隠蔽しつつ安全にユーザに使わせるのが良いのではないでしょうか。