シンプルなECS on Fargateの構成をCloudFrontから作るのに少々苦労したので、テンプレート的にまとめようと思います。
構成図
前提
- aws-cliのインストール済
- AWSアカウント作成済
- aws-cliのconfigureをセットアップ済
ディレクトリ構成
.
├── architecture.drawio
├── ecr
│ ├── Dockerfile
│ ├── deploy.ecr.sh
│ ├── parameter.ecr.json
│ ├── push.ecr.sh
│ └── template.ecr.yaml
├── ecs
│ ├── deploy.ecs.sh
│ ├── parameter.ecs.json
│ └── template.ecs.yaml
└── vpc
├── deploy.vpc.sh
├── parameter.vpc.json
└── template.vpc.yaml
なお、コードはこちらをご覧ください。
各ディレクトリ内の parameter.<リソース>.json
および ecr/push.ecr.sh
を書き換えることでご自身の環境でもご利用できます。
各種ポイント
コードは詰まったポイントのみ抜粋します。
詳細はこちらをご覧ください。
ECS -> ECRの通信
ECSがECRからimageをpullする際、以下の2つの方法が存在します。
上記からケースにもよりますが、基本的には各種エンドポイントを使用する方がセキュアかつ安価に構築することが可能です。
そのため、今回は各種エンドポイントを利用しています。
なお、必要となるエンドポイントと用途は以下のとおりです。
エンドポイント | 用途 |
---|---|
s3(Gateway) | ECRからのimage pull |
ecr.dkr | ECRからのiamge pull |
ecr.api | ECRからのiamge pull |
logs (必須ではない) |
ECSサービスのログ取得 |
logsに関しては必須ではありませんが、これがなければサービスのログを取得することができなくなるので、特別な理由がなければ作成することを推奨します。
S3エンドポイントのみGateway型であるため、VPCのstackで記述しており、それ以外のエンドポイントはECSのstackで記述しています。
S3Endpoint:
Type: "AWS::EC2::VPCEndpoint"
Properties:
....
DkrEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
....
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref Endpointsg
....
ApiEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
....
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref Endpointsg
....
LogsEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
....
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref Endpointsg
....
ここで注意してほしい点が、ecsで記述しているエンドポイントに PrivateDnsEnabled
を有効化していることです。
PrivateDnsEnabled
パラメータはインターフェースVPCエンドポイントがプライベートDNSを使用するかどうかを制御します。
つまり、有効化するとVPC内のみで通信が解決し、無効化すると通信がインターネットを経由することになります。
今回は、VPC内のみで通信が解決してほしいので有効化しています。
また、それぞれのインターフェースVPCエンドポイントにはセキュリティグループを付与できます。
全て以下に従うセキュリティグループを付与します。
- インバウンドルール:
- ポート範囲: 443 (HTTPS)
- プロトコル: TCP
- 送信元: Fargateタスクが存在するサブネットのセキュリティグループ
- アウトバウンドルール:
- 基本的にすべてのトラフィックを許可(0.0.0.0/0)(必要に応じて特定の範囲を許可)
Endpointsg:
Type: AWS::EC2::SecurityGroup
Properties:
....
SecurityGroupIngress:
- SourceSecurityGroupId: !GetAtt ECSsg.GroupId
Description: from ecs
IpProtocol: tcp
FromPort: 443
ToPort: 443
....
ECSのExecutionRole
ECSにはTaskを実行するためのExecutionRoleを設定する必要があります。
必要に応じて権限を付与することになりますが、最小の権限は arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
に全て用意されています。
そのため、以下のようにIAM Roleを作成してあげます。
ECSRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "ecs-tasks.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
こちらで作成したIAM RoleをTaskDefinitionで指定すればOKです。
ECSとALBの紐付け
ALBでTargetGroupを設定する際、一般的には以下のようにTargetsパラメータでバランシング対象を設定すると思います。
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
....
Targets:
- Id: !Ref EC2Instance
しかしECSでは異なり、ECSサービスからALBのTargetGroupを参照しにいきます。
そのため、以下のようにTargetGroupでは特に指定せず、ECSServiceのLoadBalancers.TargetGroupArnで紐づけるようにします。
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
VpcId:
"Fn::ImportValue": { "Fn::Sub": "${ProjectName}-VpcId" }
Name: !Sub ${ProjectName}-alb-tg
Protocol: HTTP
Port: 80
TargetType: ip
ECSService:
Type: AWS::ECS::Service
DependsOn: ALBListener
Properties:
....
LoadBalancers:
- ContainerName: !Sub ${ProjectName}-container
ContainerPort: 80
TargetGroupArn: !Ref TargetGroup
....