1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kubernetes上でReadWriteManyなNFSサーバを作る

Last updated at Posted at 2024-10-27

本記事の概要

Kubernetesクラスター上にNFSサーバを作ることで、Kubernetesクラスター内でReadWriteMany(RWX)なボリューム、つまり「複数のPodから同時に読み書きできるボリューム」を割と簡単に利用可能にする方法を紹介します。

課題

Kubernetes上で、複数のPodから同時アクセスするRWXなボリュームを用意するのは意外と面倒です。例えばオンプレミスのKubernetesだとNFSサーバ等を用意する必要があります。パブリッククラウドのマネージドKubernetesの場合、EKSだとAmazon EFS、AKSだとAzure Files等が使えますが、リソース作成やKubernetesで利用可能にするためのセットアップにだいぶ手間がかかります。

特に検証用や小規模な環境の場合は、「なるべく手間とお金をかけずにReadWriteManyなボリュームを使える状態にしたい!」と思う方も多いでしょう。本記事はそのような時に活用できそうな内容を書いてみました。

背景

実は過去に同じようなことを試して、以下の記事にまとめたことがあります。

この方法だと、NFSサーバのコンテナイメージのビルドやKubernetesリソースの自分で作成する必要があり結構手間でした。また、NFSサーバのサービス公開手段に関してもいくつかの課題がありました。
「もっといい方法があればいいのに...」と消化不良な気持ちを抱えて記事を書き終えたのを覚えています。

それから一年とちょっと経過。改めて調べてみたら、nfs-server-provisionerという「NFSサーバ用のHelmチャート」が公開されていることに気づきました。

これを使うと、helm install相当のコマンドで、あっさりとNFSサーバを作れてしまいました。なぜ一年前にこれに気づけなかったのだろう...w

nfs-server-provisionerのHelmチャートのインストール

以後は、nfs-server-provisionerのインストール手順を記載します。

前提条件

  • Kuberenetesクラスターが構築済であること
  • helmhelmfile が使えること

事前準備

Kubernetesの各ノードに nfs-common をインストールしておく必要があります。ノードのOSがUbuntu系の場合は、以下のコマンドでインストールします。

sudo apt install nfs-common -y

ノードに nfs-common をインストールしないと、どうなるか?

nfs-common をインストールし忘れていると、NFSをマウントするPodを起動する際に次のようなメッセージがEventに出力されてマウントに失敗します。

  Warning  FailedMount       21s (x7 over 53s)  kubelet            MountVolume.SetUp failed for volume "pvc-756add44-f386-4714-956e-1a15028636ed" : mount failed: exit status 32
Mounting command: mount
Mounting arguments: -t nfs -o retrans=2,timeo=30,vers=3 10.107.158.61:/export/pvc-756add44-f386-4714-956e-1a15028636ed /var/lib/kubelet/pods/f479f261-76ca-410a-b33f-093e865f3216/volumes/kubernetes.io~nfs/pvc-756add44-f386-4714-956e-1a15028636ed
Output: mount: /var/lib/kubelet/pods/f479f261-76ca-410a-b33f-093e865f3216/volumes/kubernetes.io~nfs/pvc-756add44-f386-4714-956e-1a15028636ed: bad option; for several filesystems (e.g. nfs, cifs) you might need a /sbin/mount.<type> helper program.

※ 本当はノードで個別設定をするのは避けたいのですが、現状回避策が見当たりませんでした。もし見つかった場合は追記or修正予定です。

Helmfileの用意

事前準備ができたら、以下のHelmfile形式のファイルを用意します。

nfs-server-provisioner.yaml
repositories:

- name: nfs-ganesha-server-and-external-provisioner
  url: https://kubernetes-sigs.github.io/nfs-ganesha-server-and-external-provisioner

releases:

- name: nfs-server-provisioner
  namespace: nfs-server-provisioner
  createNamespace: true
  chart: nfs-ganesha-server-and-external-provisioner/nfs-server-provisioner
  version: 1.8.0
  values:
  - persistence: # 必須ではないが、設定を強く推奨(後述)
      enabled: true
      storageClass: "openebs-hostpath" # あくまで一例

最後の values[0].persistence は、NFSサーバのコンフィグやマウント後の作成ファイルの永続化に関する設定をします。

enabled: true

enabled はデフォルトが false です。デフォルトから変えないと、NFSマウントで作成したファイルが emptyDir のボリュームに作られてしまい、永続化が保証されないので注意が必要です。(この辺の話は本記事の最後の方にも詳しく書いています)

storageClass: "openebs-hostpath"

本例では上のように書くことで、永続化ボリュームとして openebs-hostpath のStorageClassリソースを使う設定にしていますが、ここは環境に応じて適宜変更ください。StorageClassではなくて、直接PVCを永続化先として指定する場合は以下のように書きます。

  values:
  - persistence:
      enabled: true
      existingClaim: "export-pvc" # あくまで一例

Helmチャートのインストール

helmfileコマンドで、nfs-server-provisionerのHelmチャートをインストールします。

helmfile apply -f nfs-server-provisioner.yaml

リソースが作成されたのを確認します。

$ kubectl get all -n nfs-server-provisioner
NAME                           READY   STATUS    RESTARTS   AGE
pod/nfs-server-provisioner-0   1/1     Running   0          12s

NAME                             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                                                                     AGE
service/nfs-server-provisioner   ClusterIP   10.106.177.244   <none>        2049/TCP,2049/UDP,32803/TCP,32803/UDP,20048/TCP,20048/UDP,875/TCP,875/UDP,111/TCP,111/UDP,662/TCP,662/UDP   12s

NAME                                      READY   AGE
statefulset.apps/nfs-server-provisioner   1/1     12s

NFSを利用可能なStorageClassも以下のように作られます。

$ kubectl get storageclass -l app=nfs-server-provisioner
NAME   PROVISIONER                            RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs    cluster.local/nfs-server-provisioner   Delete          Immediate           true                   33s

これで、Kubernetesクラスター内でNFSのボリュームが使える状態になりました。

NFSのボリュームをマウントしてみる

実際にNFSボリュームが作れるのかを試してみます。

PVCの作成

まずはボリューム作成のためのPVCを以下のマニフェストで作ってみます。

nfs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
  namespace: default
spec:
  storageClassName: nfs # ここがポイント
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

実行コマンドは以下

kubectl apply -f nfs-pvc.yaml

PVCが作られて、ダイナミックプロビジョニングでPVも作られていることを確認できます。

$ kubectl get pvc -n default
NAME      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
nfs-pvc   Bound    pvc-c0b29986-7679-4f90-b49d-d1f7a2da99ab   1Gi        RWX            nfs            <unset>                 7s

Podにマウントしてみる

続いて、上記のボリュームをマウントするDeploymentリソースをレプリカ数3で作ります。以下がマニフェストです。

mnt-nfs-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mnt-nfs
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mnt-nfs
  template:
    metadata:
      labels:
        app: mnt-nfs
    spec:
      containers:
      - name: mnt-nfs
        image: busybox
        command: ["tail", "-f", "/dev/null"]
        volumeMounts:
        - name: nfs-volume
          mountPath: /data
      volumes:
      - name: nfs-volume
        persistentVolumeClaim:
          claimName: nfs-pvc

実行コマンドは以下

kubectl apply -f mnt-nfs-deploy.yaml

各PodでNFSボリュームがマウントされて、以下のように起動するのを確認します。

$ kubectl get pod -n default
NAME                       READY   STATUS    RESTARTS   AGE
mnt-nfs-65f866fc8b-8kfg2   1/1     Running   0          18s
mnt-nfs-65f866fc8b-cs99m   1/1     Running   0          18s
mnt-nfs-65f866fc8b-msm6h   1/1     Running   0          18s

確かに、/dataディレクトリがNFSでマウントされていることがわかります。

$ kubectl exec -n default -it deploy/mnt-nfs -- mount | grep /data
10.107.158.61:/export/pvc-c0b29986-7679-4f90-b49d-d1f7a2da99ab on /data type nfs (rw,relatime,vers=3,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=30,retrans=2,sec=sys,mountaddr=10.107.158.61,mountvers=3,mountport=20048,mountproto=udp,local_lock=none,addr=10.107.158.61)

NFSボリュームのマウント元の確認

上記の/dataディレクトリの実体は、最初に作った nfs-server-provisioner-0 というPod(以下)の /export ディレクトリ配下に存在します。

$ kubectl get pod -n nfs-server-provisioner
NAME                       READY   STATUS    RESTARTS   AGE
nfs-server-provisioner-0   1/1     Running   0          120m

それを確認すべく、試しにマウント先で適当なファイルを作ってみます。

kubectl exec -n default -it deploy/mnt-nfs -- \
sh -c 'echo hello >> /data/tmp.txt'

これと同じファイルを、nfs-server-provisioner-0 の以下のディレクトリで確認できます。

$ kubectl exec -n nfs-server-provisioner -it nfs-server-provisioner-0 -- \
ls -l /export/pvc-c0b29986-7679-4f90-b49d-d1f7a2da99ab
(以下が実行結果)
total 4
-rw-r--r-- 1 root root 6 Oct 27 06:08 tmp.txt

さらに言うと、/export ディレクトリも特定のボリュームをマウントして作られているので、本当の実体はさらに別のところにあります。この部分の設定は、最初に nfs-server-provisioner のHelmチャートを作った時の value の中で、persistence 配下の設定によって決まります。

何だか文章だけではわけがわからなくなってきたので、今の状況を図にしてみました。

nfs-server-provisioner.drawio.png

この図を見ると、マウントポイントとマウント元が多重になっているのがわかります。
また障害耐性やパフォーマンスの観点では、一番右の「nfs-server-provisionerの設定に依存」の部分が、実際のデータが格納されている箇所でかなり重要であることがわかります。

繰り返しになりますが、nfs-server-provisionerのHelmチャートをデプロイする際に全てデフォルトのvalueを使うと、/export ディレクトリのボリュームがKubernetesのemptyDirと呼ばれる場所に作られます。この場合、もしnfs-server-provisioner-0のPodが何らかの原因で消えてしまうと、NFSのマウントポイントで作成したデータも全て消えてしまいます。
したがってnfs-server-provisionerのHelmチャートをデプロイする際は、何かしらの永続的なストレージリソースを persistence 配下のvalueで指定しておくことを強くお勧めします。

上手くいかなかった点

nfs-server-provisionerのStatefulSetのレプリカ数がデフォルト1なので、冗長性を上げるために2にしてみましたが、NFSをマウントするPodがRunningにならずに上手く動作しませんでした。

ドキュメントの Persistence の部分に次のように書かれているので、動かなかったのも一応納得です。

If this chart is deployed with more than 1 replica, storageClass.defaultClass=true and persistence.storageClass, then the 2nd+ replica will end up using the 1st replica to provision storage - which is likely never a desired outcome.

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?