[アップデート] Fargate for EKSがEFSを正式サポートしました

EKSファンのみなさん、お待たせしました!ECSでは既に提供されている「FargateからのEFS利用」が、ついにEKSでも使えるようになりました!
2020.08.19

みなさん、こんにちは!
AWS事業本部の青柳@福岡オフィスです。

Amazon EKS (Amazon Elastic Kubernetes Service) から利用できるAWSのストレージサービスとしては、以下のものがあります。

  • Amazon EBS (Elastic Block Store)
  • Amazon EFS (Elastic File System)
  • Amazon FSx for Lustre (※FSx for Lustre用のドライバーは現在ベータ版です)

これらのうち EFS は、これまではEC2ワーカーノード上のポッドからのみ利用可能だったのですが、今回のアップデートで Fargate上のポッドから利用可能になりました!!

AWS Fargate 上の Amazon EKS が Amazon EFS ファイルシステムのサポートを開始

以下のサイトで設定手順・利用方法が公開されていますので、これらを参考に実際に試してみました。

AWSブログ:
新機能 – AWS Fargate for Amazon EKS が新たに Amazon EFS をサポート | Amazon Web Services ブログ

AWSドキュメント:
Amazon EFS CSI driver - Amazon EKS
(※ 日本語版ドキュメントはまだ更新されていませんので、英語版を参照してください)

GitHubリポジトリ:
https://github.com/kubernetes-sigs/aws-efs-csi-driver

これらの中で特に AWSブログの記事 は「EKSとEFSを組み合わせることの利点」や「Kubernetesのストレージ利用におけるPV、PVC、StoregeClassの役割」などについて非常に分かり易く解説されていると思いました。
是非、一読されることをお勧めします。

前提条件

今回のアップデートを試すにあたって、特にツール類のバージョンは指定されていません。
とは言え、念のために最新バージョンにアップデートしておきましょう。

今回は、以下の各ツールのバージョンを使用しました。

$ eksctl version
0.25.0

$ kubectl version --client --short
Client Version: v1.18.8

$ aws --version
aws-cli/2.0.40 Python/3.7.3 Linux/4.19.104-microsoft-standard exe/x86_64.ubuntu.18

準備

EKSクラスターの作成

EKSクラスターを作成します。
作成手段は問われませんが、今回は「eksctl」コマンドを使ってサクッと作成しました。

$ eksctl create cluster \
    --name eks-example \
    --fargate

--fargateオプションを付けることで、EC2ワーカーノードは作成されず、Fargate専用のEKSクラスターが作成されます。

セキュリティグループの作成

EFSファイルシステムを作成する前に、EKSの「ポッド」からEFSファイルシステムに対するアクセスを許可するためのセキュリティグループを作成します。

eksctlコマンドでEKSクラスターを作成した場合、3つのアベイラビリティゾーンに「Public Subnet」「Private Subnet」が各1個ずつ、合計6個のサブネットが作成されます。

EKSでFargateを使用する場合はプライベートサブネットに展開されますので、Fargate上で起動するポッドは3個のプライベートサブネットのCIDR範囲のIPアドレスを持つことになります。

したがって、下図のように3個のプライベートサブネットからの「NFSプロトコル (TCP:2049)」のインバウンドアクセスを許可するセキュリティグループを作成します。

(ドキュメントでは「EKSクラスターが展開されるVPCのCIDR範囲を指定」となっていますが、パブリックサブネットからのアクセスが無いことが判っていますので、今回はプライベートサブネットのみに範囲を絞って指定しました)

EFSファイルシステムの作成

EKSクラスターからアクセスするEFSファイルシステムは、以下のいずれかのVPCに作成する必要があります。

  • EKSクラスターと同じVPC上に作成する
  • EKSクラスターとは別のVPCに作成して、EKSクラスターのVPCとのピアリング接続を行う

今回は前者の「EKSクラスターと同じVPC上に作成する」を採用することにします。

EFSファイルシステムを作成する際に「カスタマイズ」を選択して、「ステップ2 ネットワークアクセス」の設定画面で以下のように設定します。

  • VPC: EKSクラスターと同じVPCを指定
  • マウントターゲット
    • 各アベイラビリティゾーン毎に、プライベートサブネットを選択
    • セキュリティグループは、初期設定されている「default」を削除し、前の手順で作成したものを選択

その他の設定は全てデフォルトのままで、ファイルシステムを作成します。

EKSクラスターへのCSIドライバーの組み込み

ここからは、EKSクラスター上での設定を行っていきます。

EKSクラスターがストレージをマウントしてアクセスできるようにするために、「CSI (Container Storage Interface)」という仕組みを使います。
(かつては「in-tree plugin」という形で提供されてきましたが、徐々にCSIへ移行しつつあります)

今回使用する「Amazon EFS CSI Driver」をEKSクラスターへ組み込みます。
以下のYAMLファイルを作成して、kubectl applyコマンドを実行します。

csidriver.yaml

apiVersion: storage.k8s.io/v1beta1
kind: CSIDriver
metadata:
  name: efs.csi.aws.com
spec:
  attachRequired: false
$ kubectl apply -f csidriver.yaml
csidriver.storage.k8s.io/efs.csi.aws.com created

組み込まれたCSIドライバーを確認します。

$ kubectl get csidrivers
NAME              CREATED AT
efs.csi.aws.com   2020-08-19T10:10:28Z

次に、「ストレージクラス」の定義を行います。

「ストレージクラス」とは、Kubernetes上でストレージを利用する方法について「性能」「冗長性」などのパラメーターの組み合わせを「Standard」「Premium」などの名前を付けて事前定義しておき、利用時に選択できるようにしておく仕組みです。

例えば、EBSの場合には「Type (io1/gp2/sc1/st1)」や「IOPS (io1の場合)」などのパラメーターを指定することができます。
しかし、EFSでは指定できるパラメーターが無いため、パラメーター無しのストレージクラスを一つだけ定義します。

storageclass.yaml

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com
$ kubectl apply -f storageclass.yaml
storageclass.storage.k8s.io/efs-sc created

これで、EKSクラスターからEFSファイルシステムを利用する準備ができました。

Persistent Volume (PV) および Persistent Volume Claim (PVC) の作成

ここからは、EFSファイルシステムをマウント・利用する都度、設定する必要がある手順となります。

まず、作成したEFSファイルシステムをマウント可能にするための定義として「Persistent Volume (PV)」を作成します。

pv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: efs-pv
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: efs-sc
  csi:
    driver: efs.csi.aws.com
    volumeHandle: fs-XXXXXXXX

代表的なパラメーターを説明します:

  • spec.capacity.storage: 用意するボリュームのサイズを指定します。ただし、EFSは完全にエラスティックなファイルシステムであり事前にサイズを確保する必要が無いため、ここで指定した値は意味がなく、無視されます。(Kubernetesの仕様上、ダミー値として指定する必要があります)
  • spec.accessModes: マウントしたボリュームに対するアクセス方法を以下から選択します。
    • ReadWriteOnce: 単一のノードから「読み取り/書き込み可能」としてマウント可能
    • ReadOnlyMany : 複数のノードから「読み取り専用」としてマウント可能
    • ReadWriteMany: 複数のノードから「読み取り/書き込み可能」としてマウント可能
  • spec.persistentVolumeReclaimPolicy: ボリュームがマウントから解放された時の振る舞いを以下から選択します。
    • Delete: ボリュームの中身は削除される
    • Retain: ボリュームの中身は削除されず、次回にマウントされた時に再利用される
  • spec.storageClassName: 定義したストレージクラスから利用するものを指定します。
  • spec.csi.volumeHandle: EFSファイルシステムの「ファイルシステムID」を指定します。(ご自身の環境に合わせて書き換えてください)

以下のコマンドで Persistent Volume を作成します。

$ kubectl apply -f pv.yaml
persistentvolume/efs-pv created

次に、ポッドがEFSファイルシステムのマウントを要求する際に指定する「Persistent Volume Claim (PVC)」を作成します。

claim.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 5Gi

パラメーターは基本的に Persistent Volume に合わせて記述します。

以下のコマンドで Persistent Volume Claim を作成します。

$ kubectl apply -f claim.yaml
persistentvolumeclaim/efs-claim created

ポッドからEFSファイルシステムをマウントしてみる

では、いよいよポッドからEFSファイルシステムをマウントしてみましょう。

ポッドのマニフェストを以下のように記述します。

pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: sample
spec:
  containers:
  - name: sample
    image: busybox
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: efs-claim

spec.volumesには、ポッドが利用しようとするボリュームについての記述を行います。
具体的には、作成した「Persistent Volume Claim」の名前を指定して、「Persistent Volume」で定義されているボリュームへのマウントを要求します。

そして、spec.containers.volumeMountsには、コンテナの設定情報の一つとして「ボリュームのマウント情報」を指定します。
マウント元はnameで示したボリューム定義、マウント先はmountPathで指定したコンテナ上のパスです。

準備ができましたら、ポッドをデプロイします。

$ kubectl apply -f pod.yaml
pod/sample created

Fargateが起動するのに1~2分程度「Pending」の状態になり、問題なく起動してボリュームへのマウントが成功すると「Running」になります。

$ kubectl get pods
NAME     READY   STATUS    RESTARTS   AGE
sample   0/1     Pending   0          32s

$ kubectl get pods
NAME     READY   STATUS    RESTARTS   AGE
sample   1/1     Running   0          57s

上手くいった場合: EFSファイルシステムへ書き込まれたファイルを確認してみましょう

ポッドの起動に成功したら、以下のコマンドでポッド内のコンテナにログインします。

$ kubectl exec -it sample -- /bin/sh

マウントされたEFSファイルシステムのボリュームを確認します。

# ls -l /data
total 4
-rw-r--r--    1 root     root          1590 Aug 19 12:23 out.txt

ポッドによってテキストファイルが書き込まれていますね。
中身をダンプしてみます。

# cat /data/out.txt
Wed Aug 19 12:22:32 UTC 2020
Wed Aug 19 12:22:37 UTC 2020
Wed Aug 19 12:22:42 UTC 2020
Wed Aug 19 12:22:47 UTC 2020
Wed Aug 19 12:22:52 UTC 2020
Wed Aug 19 12:22:57 UTC 2020
Wed Aug 19 12:23:02 UTC 2020
Wed Aug 19 12:23:07 UTC 2020
Wed Aug 19 12:23:12 UTC 2020
Wed Aug 19 12:23:17 UTC 2020

ちゃんとポッドからテキストファイルに対して書き込まれていることが確認できました。

上手くいかない場合: ポッドの状態を確認してみましょう

もし、「Running」とならずに「ContainerCreating」の状態のままになってしまう場合は、まずはポッドのイベントを確認してみましょう。

$ kubectl describe pod/sample
(中略)
Events:
  Type     Reason       Age               From                                                                 Message
  ----     ------       ----              ----                                                                 -------
  Normal   Scheduled    <unknown>         fargate-scheduler                                                    Successfully assigned default/sample to fargate-ip-192-168-184-116.ap-northeast-1.compute.internal
  Warning  FailedMount  1s (x2 over 18s)  kubelet, fargate-ip-192-168-184-116.ap-northeast-1.compute.internal  MountVolume.SetUp failed for volume "efs-pv" : kubernetes.io/csi: mounter.SetupAt failed: rpc error: code = Internal desc = Could not mount "fs-b6485e97:/" at "/var/lib/kubelet/pods/341c9fda-1ffa-4a01-a04d-a7e2bb69dbe0/volumes/kubernetes.io~csi/efs-pv/mount": mount failed: exit status 32
Mounting command: mount
Mounting arguments: -t efs -o tls fs-b6485e97:/ /var/lib/kubelet/pods/341c9fda-1ffa-4a01-a04d-a7e2bb69dbe0/volumes/kubernetes.io~csi/efs-pv/mount
Output: Could not start amazon-efs-mount-watchdog, unrecognized init system "supervisord"
mount.nfs4: Connection reset by peer

このように、EFSへのマウントに関して「Failed」と出力されている場合は、ここまでの設定でどこかが間違っている可能性が高いです。
例えば、以下のような点を確認すると良いでしょう。

  • セキュリティグループの設定は正しいか?
  • 手順の漏れは無いか? (例えば「ストレージクラスを定義していない」など)
  • PV、PVCの名前の対応は間違っていないか?

上記とは異なる出力 (例えば「コンテナイメージのpullに失敗した」) がされている場合は、内容に応じて問題個所を見直してみてください。

複数のポッドからの書き込みを試してみる

EFSは「共有ファイルシステム」ですので、複数のポッドからの読み込みや書き込みにも当然対応しています。

Deploymentを定義して、複数のポッドからEFSへの書き込みを試してみましょう。

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample2
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sample2
  template:
    metadata:
      labels:
        app: sample2
    spec:
      containers:
      - name: sample2
        image: busybox
        command: ["/bin/sh"]
        args: ["-c", "while true; do echo $(date -u) >> /data/$(uname -n).txt; sleep 5; done"]
        volumeMounts:
        - name: persistent-storage
          mountPath: /data
      volumes:
      - name: persistent-storage
        persistentVolumeClaim:
          claimName: efs-claim

先ほどの「ポッド単体」の時と、定義内容はほとんど変わりません。

spec.replicas: 3でポッドを3つデプロイするようにしています。
また、EFSファイルシステムへの書き込みは、同一のディレクトリに対してコンテナ自身のホスト名をファイル名として書き込むようにします。

準備ができましたら、デプロイします。

$ kubectl apply -f deployment.yaml
deployment.apps/sample2 created

しばらく待って、ポッドが3つ起動することを確認します。

$ kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
sample2-6bdcb7dd85-dk4k6   1/1     Running   0          100s
sample2-6bdcb7dd85-njb5q   1/1     Running   0          100s
sample2-6bdcb7dd85-pf6k9   1/1     Running   0          100s

起動したポッドの一つにログインして、/dataディレクトリを確認します。

$ kubectl exec -it sample2-6bdcb7dd85-dk4k6 -- /bin/sh
# ls -l /data
total 20
-rw-r--r--    1 root     root          4548 Aug 19 12:31 out.txt
-rw-r--r--    1 root     root           319 Aug 19 12:34 sample2-6bdcb7dd85-dk4k6.txt
-rw-r--r--    1 root     root           551 Aug 19 12:34 sample2-6bdcb7dd85-njb5q.txt
-rw-r--r--    1 root     root           580 Aug 19 12:34 sample2-6bdcb7dd85-pf6k9.txt

各コンテナからファイルに書き込まれていることが確認できました。
(もちろん、中身もちゃんと書き込まれていることも確認しましょう!)

おわりに

FargateからのEFS利用については、既にECSでは今年4月にリリースされていました。

EKSでの設定方法はECSとは全く異なりますが、Kubernetesにおけるストレージ利用方法について理解を進めつつ試してみると、そこまで難しくはないのではないかと思いました。(使いこなそうとすると、やっぱり難しいかもしれませんけどね・・・)

今回、ECSに加えてEKSにおいても「FargateからのEFS利用」が実現したことで、AWSのコンテナオーケストレーションにおけるストレージ利用の選択肢が広がったのではないかと思います。
みなさんも是非試してみてください。