EKSでEFSファイルシステムを永続ボリュームとして利用する

## はじめに

ここ2ヶ月ほどEKS/Kubernetesを触っています。

アプリケーションをコンテナ化する以上、コンテナは状態をなるべく持たず状態はデータベースやオブジェクトストレージに保存するようにするのがベストプラクティスですが、どうしてもファイルシステムにファイルを保存したいという場合はあるのではないでしょうか。

そういった場合、KubernetesのPersistent Volumeの仕組みを使うとPodに非揮発性のファイルシステムをマウントすることができます。
ただ、マウント対象のファイルシステムは自分で用意しないといけません。

マウント対象としてはいくつか選べるのですが、複数のPodから同時に読めて状態を共有できるようにするにはNFSファイルシステムをマウントするのがよいと思われます。

そこで、マネージドNFSファイルシステムであるAmazon EFSのファイルシステムをEKSクラスタで利用しようとしました。
EKSもEFSもAWSのマネージドサービスだし楽勝だろ、と思っていたら意外とハマったので、手順を共有して苦しむ人が減ればと思います。

### EFSファイルシステムを作成する

AWSコンソールからEFSファイルシステムを作成します。

このとき、EKSのワーカーノードが存在するサブネットすべてをマウントターゲットに追加し、またそれぞれセキュリティグループをEKSが勝手に作成するセキュリティグループを設定しておきます。

https://aws.amazon.com/jp/about-aws/whats-new/2019/02/deploy-a-kubernetes-cluster-using-amazon-eks-with-new-quick-start/

こちらのクイックスタートをつかっていれば、`xxx-NodeSecurityGroup-yyy`みたいな名前のセキュリティグループができているかと思いますが、それです。

<img width="1109" alt="sc-2019-09-18 21.55.42.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133157/3660bca6-372a-5736-b24c-539a4a2d28dd.jpeg">


### ワーカーノードのIAMロール

EKSワーカーノードのロールに1で作ったEFSボリュームに関するポリシーを追加します。こちらもクイックスタートを使っていると、`xxx-NodeInstanceRole-yyy`という名前になっているかと。

こちらのロールに、さきほど作成したEFSファイルシステムの「リスト・読み込み・書き込み」の権限が付与されていればOKです。

![50bac8bd-88f9-478b-a0d5-b302239b7f99.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133157/3e2acdb9-8122-258f-389e-a4e54f284945.png)

### efs-provisioner の設定

EFSをKubernetesの永続ボリュームとして使うには、`efs-provisioner`を使う必要があります。

https://github.com/kubernetes-incubator/external-storage/tree/master/aws/efs

しかし、これがREADMEの通りに手順をたどっても動かせないという罠があります。
リポジトリのサンプルマニフェストの記述が間違っているからです。。😡

こちらのIssueに解決策が載っていました。

https://github.com/kubernetes-incubator/external-storage/issues/1209

ただマウントしたいだけなら、

https://github.com/kubernetes-incubator/external-storage/blob/master/aws/efs/deploy/rbac.yaml

と
https://github.com/kubernetes-incubator/external-storage/blob/master/aws/efs/deploy/manifest.yaml

の２つをapplyする必要がありますが、両方とも修正する必要があります。

まずrbac.yamlですが、よくわからないRoleとClusterRoleの使い分けがありますが、ClusterRoleに一本化しましょう。また、サービスロールの記述も追加しないといけません。

```rbac.yaml
---

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: efs-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"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]

---

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-efs-provisioner
subjects:
  - kind: ServiceAccount
    name: efs-provisioner
    namespace: development # ClusterRoleBindingの指定はネームスペースごと
roleRef:
  kind: ClusterRole
  name: efs-provisioner-runner
  apiGroup: rbac.authorization.k8s.io

---

apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: development # ServiceAccountの指定もネームスペースごと
  name: efs-provisioner
```

そして、manifest.yamlは、Deploymentのところにネームスペースとサービスアカウントを追記します。

```manifest.yaml
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  namespace: development # この記述もネームスペースごと
  name: efs-provisioner
  spec:
    serviceAccount: efs-provisioner
    containers:
      - name: efs-provisioner
        image: quay.io/external_storage/efs-provisioner:latest
```

### ディレクトリ分割

前の手順までで、Podの永続ボリュームとしてEFSファイルシステムを利用できるようになります。

適当なEC2インスタンスをたててEFSファイルシステムをマウントしてみるとわかるのですが、manifest.yamlの`PersistentVolumeClaim`ごとにディレクトリが切られて、各Podからはその中しか見られないようになっています。
なので、１つのEFSファイルシステムを複数のPodで相乗り利用する場合は、共通の`StorageClass`を利用する`PersistentVolumeClaim`を複数定義すればよいです。

## おわりに

これでEKSクラスタ上で動くPodの永続ボリュームとしてEFSファイルシステムを利用できるようになりました。
EKSもEFSもAWSの製品なのでありがちなユースケースなのではないかと思うのですが、意外とドンピシャな情報がなかったので、参考になればと思います。