K8s on Vagrant の NFS 永続ボリュームの利用メモ


この投稿は、Vagrantの仮想サーバーで構築したK8sクラスタの4回目で、NFSを利用した、永続ボリュームの利用についてのメモです。

これまでのk8s on vagrant 記事のリンクを以下にあげておきますので、必要に応じて参照いただければ幸いです。

* [Kubenetes v1.10 クラスタをVagrantで構築したメモ](https://qiita.com/MahoTakara/items/28cd766d0447140b7ae3)
* [K8s Node障害時の振る舞いについての検証記録](https://qiita.com/MahoTakara/items/9b106664492303e1ba01)
* [K8s v1.10 on Vagrantのダッシュボードのセットアップ](https://qiita.com/MahoTakara/items/b9b1d4bdc5a3e0d6ddfb)


## システム構成

図の左端に表したk8sクラスタの外側にNFSサーバーを設定して、図中の中央にあるワーカーノード(k8s2,k8s3)のポッドからマウントできる様にします。 NFSサーバーは、外部ストレージ・システムを想定して、パブリック・ネットワークにデプロイします。 もちろん、自宅環境にストレージ・システムを持つのはお金が掛かりますから、Vagrantで模擬的なストレージシステムを作ります。

![スクリーンショット 2018-04-21 23.34.34.png](https://qiita-image-store.s3.amazonaws.com/0/42829/a595b155-cd9a-e356-bffb-f89e1281cad2.png "スクリーンショット 2018-04-21 23.34.34.png")

NFSサーバーでエクスポートしたファイルシステムを k8sのクラスタの永続ボリュームとして定義してポッドから利用します。


## ワーカーノードへのNFSクライアントモジュールの追加

コンテナは、カーネルのサポートを必要としていますから、NFSのクライアント機能がインストールされていないと、ポッドはNFSサーバーのボリュームをマウントすることができません。 このため、ワーカーノードである k8s2, k8s3にNFSクライアントのパッケージをインストールします。

~~~k8s2シェル
root@k8s2:~# apt-get install nfs-common
~~~

~~~k8s3シェル
root@k8s3:~# apt-get install nfs-common
~~~


## NFSサーバーのセットアップ

vagrantの仮想サーバーとして、NFSサーバーをセットアップします。

NFSサーバーのディレクトリを作成して、移動します。

~~~
$ mkdir nfsserver
$ cd nfsserver
~~~

次のVagrantfileを作成します。 パブリックIPアドレスは、環境に合わせて変更してくださいね。

~~~Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/xenial64"
  config.vm.hostname = "nfsserver"
  #
  public_ip = "192.168.1.98"
  config.vm.network :public_network, ip: public_ip, bridge: "en0: Ethernet"
  #
  config.vm.provider "virtualbox" do |vb|
    vb.gui = false
    vb.memory = "512"
  end
  #
  config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y nfs-kernel-server
mkdir -p /export/users
cat >> /etc/exports <<EOF
/export       192.168.1.0/24(rw,fsid=0,insecure,no_subtree_check,async)
/export/users 192.168.1.0/24(rw,nohide,insecure,no_subtree_check,async)
EOF
exportfs -a
SHELL

end
~~~

以下のコマンドで、NFSサーバーが起動すると、ポッドからNFSマウントできる状態になります。

~~~
$ vagrant up
~~~


## ストレージについて

ストレージを管理する仕組みは、ポッドやデプロイメントを管理する仕組みから独立しています。 このストレージのサブシステムである PersistantVolumeサブシステムは、永続ボリュームを提供する方法を抽象化して、ユーザーが共通の方法で永続ボリュームを利用できる様にします。このために、二つのAPIが提供されており、それぞれの概要は以下になります。


**PersistentVolume（PV）**は、k8sクラスタの管理者によってプロビジョニングされたk8sクラスタ内のストレージです。 この記事の例では、管理者によってプロビジョニングされたNFSのボリュームをk8sクラスタで利用できる様に定義します。 具体的には固有の情報となり、ボリュームの提供方法であるNFSを指定, NFSサーバーのIPアドレス、そして、認証情報などを設定します。

**PersistentVolumeClaim（PVC）**は、ユーザによるストレージの要求(クレーム)です。このクレームは、サイズとアクセスモードを要求することができます。ただし、今回の例の様に、プロビジョニング時点でキャパシティが決められている場合には、PVCの指定は効力がありません。そして、アクセスモードとは以下の指定をすることができますが、今回はPVのNFSと結びつける様に設定しますから、形だけのものになります。

* ReadWriteOnce – the volume can be mounted as read-write by a single node
* ReadOnlyMany – the volume can be mounted read-only by many nodes
* ReadWriteMany – the volume can be mounted as read-write by many nodes

このPersistentVolumeClaimsは、ユーザが抽象的な記憶資源を利用することを可能しますが、NFS、GlusterFs, iSCSIなど様々な実装方法があり、このアクセスモードやサイズの指定では、ユーザーがボリュームの実装の詳細を考慮することなく、適切なボリュームを割り当てるための[StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/)のリソースが提供されています。

参考資料: https://kubernetes.io/docs/concepts/storage/persistent-volumes/



## PVの作成

ここでは前述の様に、k8sクラスタ内から利用できる様に、NFSへのアクセスに関する詳細な設定に名前(nfs-1)を付与して定義します。 今回の例では、詳細はIPアドレスとエクスポートのパスです。 NFSサーバーのアクセス制御は、IPアドレス範囲で指定したので、ユーザーIDやパスワードの設定はありません。 また、サイズの指定していますが効力は無く、NFSサーバーでexportしたファイルシステムの容量が優先されます。

PVCからPVを明示的に指定できる様に、metadata.labels.nameを設定します。 PVCではセレクターの設定にこのラベルを利用します。

~~~nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-1
  labels:
    name: pv-nfs-1
spec:
  capacity:
    storage: 100Mi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.1.98
    path: "/export"
~~~

PV作成のYAML適用と確認は、以下の様にします。

~~~k8s1マスターノードのシェル
$ kubectl create -f pv-nfs.yaml 
persistentvolume "nfs-1" created

$ kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
nfs-1     100Mi      RWX            Retain           Available                                      5s
~~~




## PVCの作成

ユーザーがポッドに指定するための設定と、事前にプロビジョニングしたPVを対応づける設定します。 spec.accessModesやspec.resources.requestsの指定は、ここでは有効に利用される事はありません。 ポッドの作成時に、metadata.name:の nfs-1 を指定してPVCをアクセスします。

~~~pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-1
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: ""
  resources:
    requests:
      storage: "100Mi"
  selector:
    matchLabels:
      name: pv-nfs-1
~~~


PVC作成も同意な方法で、以下の様に実行します。

~~~k8s1マスターノードのシェル
$ kubectl create -f pvc-nfs.yaml 
persistentvolumeclaim "nfs-1" created

$ kubectl get pvc
NAME      STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
nfs-1     Bound     nfs-1     100Mi      RWX                           4s
~~~



## ポッドからのマウント

次のYAMLは、ReplicationController を利用していて、Version 1.10 を利用する現在では、Deploymentを利用するべきと思いますが、そのまま利用できますので、これを利用して、PVC nfs-1 を利用するNFSクライアントのポッドを２つ作成します。 PVCのnfs-1の指定は、このYAMLの最終行にあり、claimNameの値にあります。

~~~
apiVersion: v1
kind: ReplicationController
metadata:
  name: nfs-web
spec:
  replicas: 2
  selector:
    role: web-frontend
  template:
    metadata:
      labels:
        role: web-frontend
    spec:
      containers:
      - name: web
        image: nginx
        ports:
          - name: web
            containerPort: 80
        volumeMounts:
            - name: nfs
              mountPath: "/usr/share/nginx/html"
      volumes:
      - name: nfs
        persistentVolumeClaim:
          claimName: nfs-1
~~~

次のコマンドで実行します。

~~~k8s1マスターノードのシェル
$ kubectl create -f nfs-client.yaml 
replicationcontroller "nfs-web" created
~~~


`replicas: 2` を指定したので、k8s2とk8s3のワーカーノード上にそれぞれ、ポッドが作成されていることが判ります。


~~~k8s1マスターノードのシェル
vagrant@k8s1:/vagrant/yaml$ kubectl get pods -o wide
NAME                               READY     STATUS    RESTARTS   AGE       IP            NODE
nfs-web-2slrx                      1/1       Running   0          7m        10.244.2.57   k8s3
nfs-web-8g9zr                      1/1       Running   0          7m        10.244.1.73   k8s2
<省略>
~~~


ポッドの一つで、対話的にシェルを動かして、NFSサーバーがマウントされている事を確認します。 ボリュームの容量に注目してください。 100Miの指定は有効でない事を確認してみてください。

~~~k8s1マスターノードのシェル
$ kubectl exec -it nfs-web-2slrx sh
# df -h
Filesystem            Size  Used Avail Use% Mounted on
none                  9.7G  2.0G  7.7G  21% /
tmpfs                1001M     0 1001M   0% /dev
tmpfs                1001M     0 1001M   0% /sys/fs/cgroup
/dev/sda1             9.7G  2.0G  7.7G  21% /etc/hosts
shm                    64M     0   64M   0% /dev/shm
192.168.1.98:/export  9.7G  1.1G  8.6G  11% /usr/share/nginx/html
tmpfs                1001M   12K 1001M   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs                1001M     0 1001M   0% /sys/firmware
# 
~~~



# まとめ

NFSサーバーのファイル・システムを、PersistentVolumeとして詳細を定義して、PersistentVolumeClaimで抽象化レイアを指定して、ポッドからNFSマウントできる事を確認しました。 そして、ポッドは、コンテナをホストしているカーネルのサポートを必要としますから、 パブリック・クラウド や k8sソフトウェア製品では、自動的にセットアップされるので、心配ないと思いますが、今回の例の様に、CNCFからダウンロードするケースでは、ワーカーノード k8s2, k8s3にNFSクライアントのパッケージを追加しなければならない事に注意を払う必要があることが判りました。

やっぱり、PVCだけで実行するダイナミック・プロビジョニングも試してみたいですね。。。