はじめに
2020年4月にAWS Fargateのプラットフォームバージョン1.4.0がリリースされ、それに伴い、ECS FargateからAmazon Elastic Filesystem(以下、EFS)が利用できるようになりました。今回これを必要とする要件があり、EFSを初めて利用することになりました。そのため、基本的な概念を整理するために記事にしました。
今回の進め方として、CloudFormation(以下、CFn)で設定が必要な要素を確認しつつ、不明点があればドキュメントを読んで理解していったので、本記事でもCFnの定義をみながらEFSの要素についてみていきたいと思います。
最終的な構成
これから順を追って各コンポーネントについてみていきますが、今回の最終的な構成としては以下のようになります。
サブネットは二つで、それぞれのサブネットにECS Fargate上でタスクが稼働しています。そして、それぞれのタスクからEFSをマウントするという構成です。
CFnの定義
1. ECSの作成
まずはECS FargateをCloudFormationで定義していきます。構成図でいうと以下の赤枠の部分になります。
CloudFormationでは以下のようになります。この段階ではEFSに絡む定義はまだ出てきません。
尚、今回直接関連しないパラメータやサブネットなどの一部のリソースについては省略させていただいております。
ECSService:
Type: 'AWS::ECS::Service'
Properties:
Cluster: !Ref ECSCluster
DesiredCount: 2
LaunchType: 'FARGATE'
LoadBalancers:
- ContainerName: 'api'
ContainerPort: 80
TargetGroupArn: !Ref ElasticLoadBalancingV2TargetGroupExternal
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: 'DISABLED'
SecurityGroups:
- !Ref EC2SecurityGroupAPI
Subnets:
- !Ref EC2SubnetPrivateAppAZ1
- !Ref EC2SubnetPrivateAppAZ2
PlatformVersion: '1.4.0'
TaskDefinition: !Ref ECSTaskDefinition
ECSTaskDefinition:
Type: 'AWS::ECS::TaskDefinition'
Properties:
Family: efs-test
RequiresCompatibilities:
- 'FARGATE'
Cpu: 1024
Memory: 2048
NetworkMode: 'awsvpc'
ExecutionRoleArn: !GetAtt IAMRoleECSTaskExecution.Arn
TaskRoleArn: !GetAtt IAMRoleAPI.Arn
ContainerDefinitions:
- Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepositoryAPI}:latest
Name: 'api'
Cpu: 512
MemoryReservation: 1024
- ContainerPort: 8080
HostPort: 80
Protocol: 'tcp'
LogConfiguration:
LogDriver: 'awslogs'
Options:
awslogs-group: '/ecs/efs-test'
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: 'ecs'
awslogs-create-group: true
Essential: true
IAMRoleAPI:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
Service: 'ecs-tasks.amazonaws.com'
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/AWSOpsWorksCloudWatchLogs'
2. EFS Filesystemの作成
次にEFSファイルシステムを作成していきます。構成図では以下の箇所になります。
ここではバックアップや暗号化の有無、パフォーマンスモード、スループットモードを設定していきます。
EFSFileSystem:
Type: 'AWS::EFS::FileSystem'
Properties:
BackupPolicy:
Status: 'ENABLED'
Encrypted: true
FileSystemTags:
- Key: 'Name'
Value: !Sub 'efs-filesystem'
FileSystemPolicy:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Action:
- 'elasticfilesystem:ClientWrite'
- 'elasticfilesystem:ClientMount'
Principal:
AWS: !GetAtt IAMRoleAPI.Arn
LifecyclePolicies:
- TransitionToIA: AFTER_30_DAYS
PerformanceMode: 'generalPurpose'
ThroughputMode: 'bursting'
注意点としては、パフォーマンスモードはgeneralPurpose、maxIOから選択可能なのですが、ファイルシステムの作成後は変更できない点です。一般的なファイルサービスとして利用する場合はgeneralPurpose、ビッグデータ解析などのワークロードの場合にmaxIOを選択すると良いと思います。
スループットモードはバーストかプロビジョニングを選択できます。バーストモードは短期間で高いスループットが必要になり、残りの時間は低いスループットで良いワークロードが想定されています。
バーストモードはベースラインとクレジットという概念を抑えておく必要があります。EFSのスループットにはベースラインが設定されており、これを超えるスループットを必要とする場合、クレジットを消費してスループットをバーストすることができます。クレジットを使い切った場合は当然バーストすることはできないため、ベースラインでは不足している場合でもベースラインのスループットのままとなります。クレジットは時間と共に蓄積されていきます。ベースラインのスループットはディスク容量を増やすことであげることができます。
バーストモードではクレジットが常に枯渇するようなワークロードの場合はプロビジョニングを検討します。プロビジョニングは必要なスループットを指定することで、そのスループットを維持できます。ただし、ベースラインのスループットを超えてプロビジョニングされたスループット分に別途課金が発生します。ここで発生する課金は安くないため注意が必要です。
EFSのパフォーマンスについてはこちらのドキュメントにまとめられています。
また、ここで重要な設定としてFileSystemPolicyがあります。これはこのファイルシステムに対してリソースベースのアクセスを制御することが可能になります。今回はECS FargateのIAMRoleからマウント、読み込み、書き込みのアクセスを許可しています。
3. EFS MountTargetの作成
次にEFS マウントターゲットを作成していきます。構成図では以下の箇所になります。
マウントターゲットはEFSにアクセスためのリソースになります。これは耐障害性の観点からAZに一つずつ作成することが推奨されています。AZごとに作成できるマウントターゲットは一つだけのため、一つのAZに複数のサブネットが含まれる場合もマウントターゲットを作成するサブネットを一つ選択して作成します。他のサブネットからは同じAZ内のサブネットに作られたマウントターゲットを利用できます。
また、マウントターゲットにはセキュリティグループをアタッチできるため、EFSのファイアウォールとしての役割も担います。セキュリティグループはport 2049からのアクセスを許可することでアクセス可能になります。
EFSMountTargetPrivateAppAZ1:
Type: 'AWS::EFS::MountTarget'
Properties:
FileSystemId: !Ref EFSFileSystem
SubnetId: !Ref EC2SubnetPrivateAppAZ1
SecurityGroups:
- !Ref EC2SecurityGroupEFS
EFSMountTargetPrivateAppAZ2:
Type: 'AWS::EFS::MountTarget'
Properties:
FileSystemId: !Ref EFSFileSystem
SubnetId: !Ref EC2SubnetPrivateAppAZ2
SecurityGroups:
- !Ref EC2SecurityGroupEFS
EC2SecurityGroupEFS:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: 'efs-filesystem securitygroup'
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 2049
ToPort: 2049
SourceSecurityGroupId: !Ref EC2SecurityGroupAPI
Tags:
- Key: 'Name'
Value: 'efs-filesystem'
VpcId: !Ref EC2VPC
4. EFS AccessPointの作成
次にアクセスポイントを作成していきます。構成図では以下の箇所になります。
CloudFormationの定義としては以下のようになります。
EFSAccessPoint:
Type: 'AWS::EFS::AccessPoint'
Properties:
FileSystemId: !Ref EFSFileSystem
PosixUser:
Uid: "1001"
Gid: "1001"
RootDirectory:
CreationInfo:
OwnerGid: "1001"
OwnerUid: "1001"
Permissions: "0755"
Path: "/mnt/efs"
私はこのアクセスポイントを間違って理解してしまい、少しハマりました。ドキュメントには以下のように記載されています。
アクセスポイントを使用すると、アクセスポイントを介したすべてのファイルシステム要求に対してユーザー ID (ユーザーの POSIX グループなど) を適用できます。
ここでいう「適用」が指す意味を「適用するために上書き」してくれるものと勘違いし、それに沿った設定をしたところ、EFSファイルシステムにアクセスする際に Permission Deny
が発生しました。挙動を確認したところ、おそらくここは「適用するために指定されたUID、GID以外を弾く」ことを指すようです。
そのため、NFSクライアント側でUID、GIDを設定する必要があります。ECSのタスク定義 or Dockerで特にUID、GIDを指定しない場合はデフォルトでrootユーザーでのアクセスとなるため、同じくPermission Deny
が発生します。回避策としてはファイルシステムポリシーに「elasticfilesystem:ClientRootAccess」を設定する方法があります。ただしこれはno_root_squashを設定するのと同等のリスクが生まれるため、リスクを考慮して設定するか判断する必要があります。
すみません、ここはPosixUserで指定したUID、GIDに上書きしてくれる理解であっていました。改めて検証したところ、ECSのタスク定義やDockerfile内でUserを指定することなく、正常にファイル書き込みされることを確認しました。
5. ECS FargateからEFSをマウントする設定
ここまででEFS側の設定は完了しているので、「 1. ECSの作成」で作成したECSのタスク定義を編集し、EFSをマウントしていきます。
設定箇所としては2箇所になります。
- Volumeを定義してECSからEFSを認識させる設定
- ボジュームをタスクからマウントする設定
ECSTaskDefinition:
Type: 'AWS::ECS::TaskDefinition'
Properties:
Family: efs-test
RequiresCompatibilities:
- 'FARGATE'
Cpu: 1024
Memory: 2048
NetworkMode: 'awsvpc'
ExecutionRoleArn: !GetAtt IAMRoleECSTaskExecution.Arn
TaskRoleArn: !GetAtt IAMRoleAPI.Arn
#### ここを追加する。1.に該当する。
Volumes:
- Name: 'efs-filesystem'
EFSVolumeConfiguration:
AuthorizationConfig:
AccessPointId: !Ref EFSAccessPoint
IAM: 'ENABLED'
FilesystemId: !Ref EFSFileSystem
TransitEncryption: 'ENABLED'
#### ここを追加する。1.に該当する。
ContainerDefinitions:
- Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepositoryAPI}:latest
Name: 'api'
Cpu: 512
MemoryReservation: 1024
- ContainerPort: 8080
HostPort: 80
Protocol: 'tcp'
LogConfiguration:
LogDriver: 'awslogs'
Options:
awslogs-group: '/ecs/efs-test'
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: 'ecs'
awslogs-create-group: true
#### ここを追加する。2.に該当する。
MountPoints:
-
SourceVolume: 'efs-filesystem'
ContainerPath: '/working'
#### ここを追加する。2.に該当する。
Essential: true
以上の設定により、ECS FargateからEFSを利用可能になります。
最後に
EFSを初めて使ってみて、アクセス制御の選択肢が豊富だなという感想を抱きました。
EFSをECSにマウントするCloudFormationの定義は意外と落ちていなかったりするので、参考になれば幸いです。