経緯
Kubernetes(k8s)は永続ボリューム(PersistentVolume)に対応していますが、アプリケーション側がこれをコントロールする場合、サーバ上の物理的な情報を意識する必要があります。例えばNFSのPersistentVolumeの定義は以下のように行います。
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfsvol1
annotations:
volume.beta.kubernetes.io/storage-class: "nfs"
spec:
capacity:
storage: 8Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
nfs:
server: 192.168.11.25
path: /var/share/nfs
NFSサーバの接続情報が必要になっています。
k8sを1人や少数で利用する環境ならこれでもいいですが、商用システムの運用を想定すると、基盤系とアプリ系で役割分担や責務が分かれる場合が多いですし、何よりもアプリがサーバの物理的な情報を意識するのは、アプリと基盤の独立性を損なう要因にもなり、k8sの思想にも反します。
そこで、永続ボリューム要求(PersistentVolumeClaim)があります。アプリ側はあらかじめ基盤側で定義されたStorage Class名を指定するだけで、必要なボリュームの動的プロビジョンを行うことができます。
一方、動的プロビジョンを行うためにはそれに対応したストレージを利用する必要があります。具体的には、以下リンクのInternal Provisionerにチェックのついているものにk8sは対応しています。
https://kubernetes.io/docs/concepts/storage/storage-classes/#provisioner
クラウドストレージや比較的新しい分散ファイルシステムには対応していますが、NFSのような気軽なものには対応していないことがわかります。しかし、k8sをパブリッククラウドではなくオンプレミスで利用したいケース(ICP: IBM Cloud Privateなど)では、NFSのような枯れた仕組みを使いたいというニーズもあるはずです。
k8sにはExternal Provisionerという仕組みがあり、上表で対応していないストレージにも対応することが出来ます。今回はNFSのExternal Provisionerを利用して、NFSボリュームのPersistentVolumeClaimに対応してみます。
検証環境
- クラスタ: IBM Cloud Private 2.1.0.2 (Kubernetes 1.9.1)
- NFSサーバ: CentOS 7
手順
External Provisionerの準備
Provisionerの選定
以下のサイトにさまざまなExternal Provisionerが公開されています。
NFSに対応したProvisionerはnfsとnfs-clientの2つがあります。違いですが、nfsはWorkerノードにマウントされたNFSのパスをhostPathとしてボリューム定義し、PersistentVolumeClaimに割り当てる方式のようです。nfs-clientはマウントは不要で、NFSサーバのアドレスとパスを指定して直接ボリュームを定義します。
好みの問題かもしれませんが、WorkerノードにわざわざNFSマウントをしたくないので、今回はnfs-clientを使用します。
Storage Classの作成
以下のようにStorage Classを作成します。provisionerの値は任意のものに変更してください。
$ kubectl apply -f storage-class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs
provisioner: lab.hoge.jp/nfs
Deploymentの作成
以下のようにDeploymentを作成します。env.PROVISIONER_NAMEはStorage Classのprovisionerと同じ値を、env.NFS_SERVERとnfs.serverは実際のNFSサーバのアドレスを、env.NFS_PATHとnfs.pathは実際のエクスポートされたディレクトリのパスを指定してください。
$ kubectl apply -f deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
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: lab.hoge.jp/nfs
- name: NFS_SERVER
value: icp-nfs
- name: NFS_PATH
value: /var/share/nfs
volumes:
- name: nfs-client-root
nfs:
server: icp-nfs
path: /var/share/nfs
RBACの設定
ServiceAccountはdefaultではなくこれ用のものを用意するのがいいようです。deployment.yamlでserviceAccountName: nfs-client-provisioner
として宣言していました。このServiceAccountに権限を付与します。
$ kubectl apply -f rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
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: ["list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
テスト
上記サイトにテスト用のyamlも置かれていますので簡単にテストできます。volume.kubernetes.io/storage-class: "nfs"
は先ほど定義したStorage Class名です。それ以外に環境に依存する情報がないというところがポイントです。
provisionerはdefaultネームスペースに作りましたが、PersistentVolumeClaimは試しに別のネームスペースで作ってみます。成功すればNFSボリューム上にSUCCESSというファイルが作成されます。
$ kubectl apply -f test.yaml -n teru
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-claim
annotations:
volume.kubernetes.io/storage-class: "nfs"
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
---
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: gcr.io/google_containers/busybox:1.24
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim
結果を見てみます。
$ kubectl get pvc -n teru
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-claim Bound pvc-075b7858-2f24-11e8-8c09-000c29ebfd54 1Mi RWX nfs 22s
test-claimというPersistentVolumeClaimの状態がBoundとなり、pvc-〜というVolumeが割り当てられたことがわかります。
[NFSサーバ]$ find /var/share/nfs/ | grep pvc-075b
/var/share/nfs/teru-test-claim-pvc-075b7858-2f24-11e8-8c09-000c29ebfd54
/var/share/nfs/teru-test-claim-pvc-075b7858-2f24-11e8-8c09-000c29ebfd54/SUCCESS
NFS上にSUCCESSファイルも無事作成されていました。
以上です。