ECSのFargateからEFSをマウントするための最小限サンプル設定

FargateからEFSを利用するための動きを確認したい場合に使える、最小限の構成設定を準備したので記載します。

Dockerイメージの準備

以下のようなシンプルなコンテナを作成します。

ECS上でFargateで動かして、時刻とホスト名を2カ所に追記で吐き出します。

/mounttarget ではEFSを直接マウント、/accesspointアクセスポイント経由でマウントします。

FROM debian:stable-slim

RUN mkdir /accesspoint
RUN mkdir /mounttarget
CMD echo "$(date) $(hostname)" | tee -a /accesspoint/log.txt /mounttarget/log.txt

ECRに efs-test という名前でPushしておきます。

f:id:yomon8:20210502090317p:plain

Cloudformation適用

EFSの定義と、EFSを利用するECSクラスタとタスク定義を一気にデプロイします。

---
AWSTemplateFormatVersion: "2010-09-09"
Description: Fargate Sample Tasks using EFS 

Parameters:
  ImageName:
    Type: String
  VpcId:
    Type: AWS::EC2::VPC::Id
  SubnetId:
    Type: AWS::EC2::Subnet::Id
  ClusterName:
    Type: String
    Default: MyEfsSampleCluster

Resources: 
#---------------------------------
# EFS
#---------------------------------
  MyEFSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: EFS Allowed Ports
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 2049
          ToPort: 2049
          CidrIp: 0.0.0.0/0
      Tags: 
        - Key: Name
          Value: MyEFSSecurityGroup

  MyEFSFileSystem:
    Type: AWS::EFS::FileSystem
    Properties:
      FileSystemTags:
        - Key: Name
          Value: my-efs-fs
      BackupPolicy:  
        Status: ENABLED
      Encrypted: true
      LifecyclePolicies:
        - TransitionToIA: AFTER_30_DAYS
      PerformanceMode: generalPurpose

  MyEFSMountTarget:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref MyEFSFileSystem
      SecurityGroups:
        - !Ref MyEFSSecurityGroup
      SubnetId: !Ref SubnetId

  MyEFSAccessPoint:
    Type: AWS::EFS::AccessPoint
    Properties:
      AccessPointTags:  
        - Key: Name
          Value: my-accesspoint
      FileSystemId: !Ref MyEFSFileSystem
      PosixUser: 
        Gid: "1000"
        Uid: "1000"
      RootDirectory: 
        CreationInfo: 
          OwnerGid: "1000"
          OwnerUid: "1000"
          Permissions: "0755"
        Path: /accesspoint

#---------------------------------
# ECS 
#---------------------------------
  MyECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Ref ClusterName
      CapacityProviders:
        - FARGATE
      DefaultCapacityProviderStrategy:
        - CapacityProvider: FARGATE
          Base: 1
          Weight: 1

  MyECSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: EFS Allowed Ports
      VpcId: !Ref VpcId
      Tags: 
        - Key: Name
          Value: ECSSecurityGroup

  MyECSTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "ECSTaskExecutionRole-${ClusterName}"
      Path: /
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

  MyECSTaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "ECSTaskRole-${ClusterName}"
      Path: /
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole

  MyECSTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: efs-sample
      Cpu: 256
      Memory: 512
      NetworkMode: awsvpc
      ExecutionRoleArn: !Ref MyECSTaskExecutionRole
      TaskRoleArn: !Ref MyECSTaskRole
      RequiresCompatibilities:
        - FARGATE
      Volumes:
        - Name: efs-mount-target-vol
          EFSVolumeConfiguration:
            AuthorizationConfig:
              IAM: ENABLED
            FileSystemId: !Ref MyEFSFileSystem
            RootDirectory: /
            TransitEncryption: ENABLED
        - Name: efs-access-point-vol
          EFSVolumeConfiguration:
            AuthorizationConfig:
              AccessPointId: !Ref MyEFSAccessPoint
              IAM: ENABLED
            FileSystemId: !Ref MyEFSFileSystem
            TransitEncryption: ENABLED
      ContainerDefinitions:
        - Name: efs-sample
          Image: !Ref ImageName
          Essential: true
          User: 0
          MountPoints:
            - ContainerPath: /mounttarget
              ReadOnly: False
              SourceVolume: efs-mount-target-vol
            - ContainerPath: /accesspoint
              ReadOnly: False
              SourceVolume: efs-access-point-vol
    DependsOn: MyEFSFileSystem

  ECSService:
    Type: AWS::ECS::Service
    Properties:
      DesiredCount: 0
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups: 
            - !Ref MyECSSecurityGroup
          Subnets: 
            - !Ref SubnetId
      Cluster: !Ref MyECSCluster
      ServiceName: my-efs-sample-service
      TaskDefinition: !Ref MyECSTaskDefinition

パラメータを入れてCloudformationを適用します。

項目 説明
スタックの名前 任意
ClusterName 理由なければ変更しないで大丈夫です
ImageName ECRにPushしたイメージの名称を入れて下さい 例: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/efs-test
VpcId FargateとEFSの動くVPC
SubnetId FargateとEFSのサブネット

f:id:yomon8:20210502093218p:plain:w500

この際に以下のエラーが発生する場合があります。

Resource handler returned message: "Invalid request provided: CreateCluster Invalid Request: Unable to assume the service linked role. Please verify that the ECS service linked role exists. (Service: AmazonECS; Status Code: 400; Error Code:

その場合はこちらのコマンドでロールを作成してから再実行してください。

aws iam create-service-linked-role --aws-service-name ecs.amazonaws.com

⇒参考URL

動かしてみる

ECSクラスタの初期設定はタスク数を0としているので何も動きません。以下のようにタスク数を増やすとEFSにログを出力し始めます。

aws ecs update-service --cluster MyEfsSampleCluster \
  --service my-efs-sample-service \
  --desired-count 2

EFSの中身を見てみる

別のEC2(Linux)から中身を覗いてみます。

EFSのマウント

EFSの以下のボタンをクリックするとEFSのマウントコマンドが表示されます。

f:id:yomon8:20210502093811p:plain

例えばヘルパーを使わない場合は以下のようなコマンドが表示されます。

sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport ${YOUR_EFS_ID}.efs.ap-northeast-1.amazonaws.com:/ efs

ヘルパーをインストールしておけば、シンプルなコマンドでマウントも可能です。

sudo yum install amazon-efs-utils
sudo mount -t efs -o tls fs-0e20972e:/ efs

EFSの中身確認

今回はコンテナの最後に以下のコマンドを打ってログを出力していました。

echo "$(date) $(hostname)" | tee -a /accesspoint/log.txt /mounttarget/log.txt

${mount_dir}/log.txt${mount_dir}/accesspoint/log.txt にそれぞれ出力されているはずです。

中身見ると両方のログに同じ内容が出力されているのが確認できます。

$ head -n5 efs/log.txt
Sun May  2 00:18:37 UTC 2021 ip-10-0-1-95.ap-northeast-1.compute.internal
Sun May  2 00:18:40 UTC 2021 ip-10-0-1-172.ap-northeast-1.compute.internal
Sun May  2 00:19:52 UTC 2021 ip-10-0-1-230.ap-northeast-1.compute.internal
Sun May  2 00:20:01 UTC 2021 ip-10-0-1-215.ap-northeast-1.compute.internal
Sun May  2 00:20:40 UTC 2021 ip-10-0-1-221.ap-northeast-1.compute.internal
$ head -n5 efs/accesspoint/log.txt
Sun May  2 00:18:37 UTC 2021 ip-10-0-1-95.ap-northeast-1.compute.internal
Sun May  2 00:18:40 UTC 2021 ip-10-0-1-172.ap-northeast-1.compute.internal
Sun May  2 00:19:52 UTC 2021 ip-10-0-1-230.ap-northeast-1.compute.internal
Sun May  2 00:20:01 UTC 2021 ip-10-0-1-215.ap-northeast-1.compute.internal
Sun May  2 00:20:40 UTC 2021 ip-10-0-1-221.ap-northeast-1.compute.internal

コンテナユーザをrootのままにしているので、MountTargetに出力された log.txt も所有者IDが0(root)になっています。

$ ls -ln efs
total 8
drwxr-xr-x 2 1000 1000 6144 May  1 09:48 accesspoint
-rw-r--r-- 1    0    0  299 May  1 09:49 log.txt

AccessPoint経由の出力ではCloudformationで以下のように設定してあります。

  MyEFSAccessPoint:
    Type: AWS::EFS::AccessPoint
    Properties:
      AccessPointTags:  
        - Key: Name
          Value: my-accesspoint
      FileSystemId: !Ref MyEFSFileSystem
      PosixUser: 
        Gid: "1000"
        Uid: "1000"
      RootDirectory: 
        CreationInfo: 
          OwnerGid: "1000"
          OwnerUid: "1000"
          Permissions: "0755"
        Path: /accesspoint

これが反映されたPermissionが設定されていることが確認できます。

$ ls -ln efs/accesspoint/
total 4
-rw-r--r-- 1 1000 1000 299 May  1 09:49 log.txt

掃除

Cloudformaitonのスタックの削除と、ECRからのイメージの削除をすれば完了です。