2022/04/07

GKEでNFS

gkek8s

概要

k8sの複数のpod間で共有できる ReadWriteMany なボリュームの必要ができきたので、GKEでのNFSの構築を行いました。
今回はその備忘録です。

具体的には、ファイルアップロードを行うWebアプリケーションで、大きいファイルだった場合複数のchunkに分けてアップロードするような処理を書いたのですが・・
このアップロード先はk8s上の複数のpodに分散される可能性があり、全てのpodで共有できるvolumeがあると便利だなと思い、NFSを構築しました。

注意事項

※ このvolumeはchunkの格納に一時的に使うためだけのもので、特段可用性などは持たせていません。
もし可用性を持たせる場合は、Rook/Cephなんかをうまく使うのが良いのかなと思います。
chunkが全てアップロードされたら、chunkを一つに繋げてファイルを復元し、GCSなどの可用性の高いファイルストレージに格納する感じです。

参考

ほぼほぼmappedinn/kubernetes-nfs-volume-on-gke: kubernetes-nfs-volume-on-gkeを参考にしています。

実装

GCE Diskを作成します。

your-gce-disk-name という名前で、200GBのものを作成したものとします。

注意点

nfs serverを動かす、nodeは単一のzone(今回はasia-northeast2-a)で行う必要があり、(k8sのnodeを作るときにちょっと工夫が必要です。)そのzoneと同じ場所にdiskも作成する必要があります。

terraformの定義だと以下のような感じ

resource "google_compute_disk" "your-gce-disk-name" {
  name = "your-gce-disk-name"
  size = 200
  type = "pd-ssd"
  zone = "asia-northeast2-a"
}

NFS server statefulset

NFS serverのStatefulsetをdeployします。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nfs-server
  namespace: your-namespace
spec:
  serviceName: nfs-server
  replicas: 1
  selector:
    matchLabels:
      app: nfs-server
  template:
    metadata:
      labels:
        app: nfs-server
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                - key: "topology.kubernetes.io/zone"
                  operator: In
                  values:
                    - "asia-northeast2-a"
      containers:
        - name: nfs-server
          image: gcr.io/google_containers/volume-nfs:0.8
          ports:
            - name: nfs
              containerPort: 2049
            - name: mountd
              containerPort: 20048
            - name: rpcbind
              containerPort: 111
          securityContext:
            privileged: true
          volumeMounts:
            - mountPath: /exports
              name: data
      volumes:
        - name: data
          gcePersistentDisk:
            pdName: your-gce-disk-name
            fsType: ext4

NFS server service

次に、statefulsetの各種ポートに対応したサービスをdeployします。

apiVersion: v1
kind: Service
metadata:
  name: nfs-server
  namespace: your-namespace
spec:
  ports:
    - name: nfs
      port: 2049
      targetPort: nfs
    - name: mountd
      port: 20048
      targetPort: mountd
    - name: rpcbind
      port: 111
      targetPort: rpcbind
  selector:
    app: nfs-server

PVとPVC

最後に、persistent volumeとpersistent volume claimを定義します。

これを、Webアプリケーション側で指定する感じで利用します。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs
spec:
  storageClassName: nfs
  capacity:
    storage: 200Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: nfs-server.your-namespace.svc.cluster.local  # 先程deployしたserviceのcluster内でのhost名を指定します。
    path: "/"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs
  namespace: your-namespace
spec:
  storageClassName: nfs
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 200Gi

Webアプリケーションでの利用

apiVersion: apps/v1
kind: Deployment
metadata:
  name: server
  namespace: your-namespace
  labels:
    app: server
spec:
  replicas: 2
  selector:
    matchLabels:
      app: server
  template:
    metadata:
      labels:
        app: server
    spec:
      containers:
        - name: server
          image: your-app-container-image
      # ここから↓の部分
          volumeMounts:
            - name: nfs
              mountPath: "/path-to-mount-nfs-directory"
      volumes:
        - name: nfs
          persistentVolumeClaim:
            claimName: nfs

こちらで、動作確認が取れました。
以上です。