CloudFormationでECS Fargateを構築する機会があったのでブログに残します。
やること
タイトルの通りCloudFormationを使用してECS Fargateを動かせる環境を作成します。
ECS Fargateで使用するコンテナイメージはCloudShellで作成してECRへpushします。
ECRへのアクセスなどはVPCエンドポイントを使用する構成とします。
簡単にはなりますが構成図は以下となります。
AWSリソースの作成
まずはECSを動かすネットワーク周りのリソースとECRを作成します。
作成は以下のCloudFormationテンプレートで行いました。
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
VPCCIDR:
Default: 10.0.0.0/16
Type: String
PublicSubnet01CIDR:
Default: 10.0.0.0/24
Type: String
PublicSubnet02CIDR:
Default: 10.0.1.0/24
Type: String
PrivateSubnet01CIDR:
Default: 10.0.2.0/24
Type: String
PrivateSubnet02CIDR:
Default: 10.0.3.0/24
Type: String
Resources:
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------#
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: ecs-test-vpc
# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------#
PublicSubnet01:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1a
CidrBlock: !Ref PublicSubnet01CIDR
Tags:
- Key: Name
Value: ecs-test-public-01
VpcId: !Ref VPC
PublicSubnet02:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1c
CidrBlock: !Ref PublicSubnet02CIDR
Tags:
- Key: Name
Value: ecs-test-public-02
VpcId: !Ref VPC
PrivateSubnet01:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1a
CidrBlock: !Ref PrivateSubnet01CIDR
Tags:
- Key: Name
Value: ecs-test-private-01
VpcId: !Ref VPC
PrivateSubnet02:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1c
CidrBlock: !Ref PrivateSubnet02CIDR
Tags:
- Key: Name
Value: ecs-test-private-02
VpcId: !Ref VPC
# ------------------------------------------------------------#
# InternetGateway
# ------------------------------------------------------------#
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ecs-test-igw
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------#
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: ecs-test-public-rtb
PublicRouteTableRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
RouteTableId: !Ref PublicRouteTable
PublicRtAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet01
PublicRtAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet02
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: ecs-test-private-rtb
PrivateRtAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref PrivateSubnet01
PrivateRtAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref PrivateSubnet02
# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------#
ALBSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: for alb
GroupName: ecs-test-sg-alb
SecurityGroupIngress:
- FromPort: 80
IpProtocol: tcp
CidrIp: 0.0.0.0/0
ToPort: 80
Tags:
- Key: Name
Value: ecs-test-sg-alb
VpcId: !Ref VPC
ECSSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: for ecs
GroupName: test-sg-ecs
SecurityGroupIngress:
- FromPort: 80
IpProtocol: tcp
SourceSecurityGroupId: !Ref ALBSG
ToPort: 80
Tags:
- Key: Name
Value: ecs-test-sg-ecs
VpcId: !Ref VPC
VPCEndpointSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: for VPC Endpoint
GroupName: ecs-test-vpc-endpoint-sg
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
FromPort: -1
IpProtocol: -1
ToPort: -1
SecurityGroupIngress:
- SourceSecurityGroupId: !Ref ECSSG
FromPort: 443
IpProtocol: tcp
ToPort: 443
Tags:
- Key: Name
Value: ecs-test-vpc-endpoint-sg
VpcId: !Ref VPC
# ------------------------------------------------------------#
# VPC Endpoint
# ------------------------------------------------------------#
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- !Ref PrivateRouteTable
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
VpcEndpointType: Gateway
VpcId: !Ref VPC
ECRdkrEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.dkr
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet01
SecurityGroupIds:
- !Ref VPCEndpointSG
ECRapiEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.api
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet01
SecurityGroupIds:
- !Ref VPCEndpointSG
LogsEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
ServiceName: !Sub com.amazonaws.${AWS::Region}.logs
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet01
SecurityGroupIds:
- !Ref VPCEndpointSG
SsmMessagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet01
SecurityGroupIds:
- !Ref VPCEndpointSG
# ------------------------------------------------------------#
# ALB
# ------------------------------------------------------------#
ALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
IpAddressType: ipv4
Name: alb
Scheme: internet-facing
SecurityGroups:
- !Ref ALBSG
Subnets:
- !Ref PublicSubnet01
- !Ref PublicSubnet02
Tags:
- Key: Name
Value: ecs-test-alb
Type: application
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckEnabled: true
HealthCheckIntervalSeconds: 30
HealthCheckPath: /
HealthCheckPort: 80
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 5
IpAddressType: ipv4
Matcher:
HttpCode: 200
Name: ecs-test-tg
Port: 80
Protocol: HTTP
ProtocolVersion: HTTP1
Tags:
- Key: Name
Value: ecs-test-tg
TargetType: ip
UnhealthyThresholdCount: 2
VpcId: !Ref VPC
ALBHTTPListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup
Type: forward
LoadBalancerArn: !Ref ALB
Port: 80
Protocol: HTTP
# ------------------------------------------------------------#
# ECR
# ------------------------------------------------------------#
ECR:
Type: AWS::ECR::Repository
Properties:
EmptyOnDelete: true
EncryptionConfiguration:
EncryptionType: AES256
RepositoryName: ecs-test-ecr
VPCやサブネット周りの詳細な説明は省かせていただきますが、31行目から201行目でVPC、サブネット、インターネットゲートウェイ、ルートテーブル、セキュリティグループを作成しています。
206行目~249行目でVPCエンドポイントを作成しています。
作成しているのはECRのVPCエンドポイント、S3のVPCエンドポイント、CloudWatch LogsのVPCエンドポイント、SystemsManagerのVPCエンドポイントとなります。
ECRにアクセスするのに最低限必要なVPCエンドポイントは以下のドキュメントに記載されています。
Amazon ECR インターフェイス VPC エンドポイント (AWS PrivateLink)
254行目~302行目でタスクを紐づけるALBを作成しています。
307行目~313行目でECRの作成を行っています。
デプロイは以下のコマンドを実行します。
VPCやサブネットのCIDRを変更したい場合は「--parameters」オプションでパラメータを指定してください。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名
デプロイが完了したらCloudShellへアクセスします。
アクセス方法は以下のドキュメントをご確認ください。
AWS CloudShell の使用を開始するには?
アクセスができたら、ECS Fargateで動かすコンテナイメージを作成していきます。
まずは以下のDockerfileを作成してください。
内容はシンプルでApacheのコンテナイメージを作成するものとなっています。
FROM httpd:2.4
RUN echo "ecs-test" > /usr/local/apache2/htdocs/index.html
Dockerfileを作成したら以下のコマンドでコンテナイメージを作成します。
「docker images」を実行すると作成されたイメージが確認できます。
docker build -t ecs-test .
docker images
イメージが作成できたら以下のコマンドでタグを付けます。
docker tag ecs-test:latest AWSアカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-test-ecr:latest
タグの設定後、以下のコマンドでECRに対して認証を行いコンテナイメージをpushします。
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin AWSアカウントID.dkr.ecr.ap-northeast-1.amazonaws.com
docker push AWSアカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-test-ecr:latest
pushが成功するとECRの画面から以下のようにイメージが確認できます。
ECRへのpushまで完了したらECSを作成していきます。
ECSは以下のCloudFormationテンプレートで作成しました。
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
TargetGroupArn:
Type: String
SecurityGroupID:
Type: String
PrivateSubnet01ID:
Type: String
PrivateSubnet02ID:
Type: String
Resources:
# ------------------------------------------------------------#
# CloudWatch Logs
# ------------------------------------------------------------#
ECSLogGroup:
Type: "AWS::Logs::LogGroup"
Properties:
LogGroupName: !Sub "/ecs/logs/ecs-test-log"
# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------#
TaskExecutionRole:
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/service-role/AmazonECSTaskExecutionRolePolicy
RoleName: ecs-test-tast-execution-role
TaskRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ecs-tasks.amazonaws.com
Action:
- sts:AssumeRole
RoleName: ecs-test-tast-role
TaskRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: ecs-test-task-role-policy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "ssmmessages:CreateControlChannel"
- "ssmmessages:CreateDataChannel"
- "ssmmessages:OpenControlChannel"
- "ssmmessages:OpenDataChannel"
Resource: '*'
Roles:
- !Ref TaskRole
# ------------------------------------------------------------#
# ECS
# ------------------------------------------------------------#
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
CapacityProviders:
- FARGATE
ClusterName: ecs-test-cluster
DefaultCapacityProviderStrategy:
- CapacityProvider: FARGATE
Weight: 1
ECSTaskDef:
Type: AWS::ECS::TaskDefinition
Properties:
ContainerDefinitions:
- Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/ecs-test-ecr:latest
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: "/ecs/logs/ecs-test-log"
awslogs-region: !Ref "AWS::Region"
awslogs-stream-prefix: "ecs-test-log"
Name: ecs-test-task
PortMappings:
- ContainerPort: 80
HostPort: 80
Cpu: 256
ExecutionRoleArn: !Ref TaskExecutionRole
Family: ecs-test-task-def
Memory: 512
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
TaskRoleArn: !Ref TaskRole
ECSService:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref ECSCluster
DesiredCount: 1
EnableExecuteCommand: true
LoadBalancers:
- ContainerName: ecs-test-task
ContainerPort: 80
TargetGroupArn: !Ref TargetGroupArn
NetworkConfiguration:
AwsvpcConfiguration:
SecurityGroups:
- !Ref SecurityGroupID
Subnets:
- !Ref PrivateSubnet01ID
- !Ref PrivateSubnet02ID
ServiceName: ecs-test-service
TaskDefinition: !Ref ECSTaskDef
上記のCloudFormationテンプレートではCloudWatch LogsロググループやECSの使用するタスクロール、タスク実行ロールの作成とECS周りのリソース作成を行っています。
47行目~76行目でECSのタスクロールを作成しています。
このタスクロールではECS Execでコンテナ接続に必要なIAMポリシーを設定するようにしています。
ECS Execは120行目の「EnableExecuteCommand」というもので有効化しています。
今回はタスクが起動できるところまでを確認したかったのでApplication AutoScalingの設定は入れていません。
サービスのオートスケーリング
デプロイは以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=TargetGroupArn,ParameterValue=ECSタスクを紐づけるターゲットグループのARN ParameterKey=SecurityGroupID,ParameterValue=ECSタスクが使用するセキュリティグループのID ParameterKey=PrivateSubnet01ID,ParameterValue=1つ目のプライベートサブネットのID ParameterKey=PrivateSubnet02ID,ParameterValue=2つ目のプライベートサブネットのID --capabilities CAPABILITY_NAMED_IAM
デプロイ完了後、ALBのDNS名にブラウザからアクセスすると「ecs-test」という文字列が表示されることを確認できます。
また、ECSクラスターの画面からタスクが1つ起動していることが確認できます。
タスクにECS Execで接続するには以下のコマンドを実行します。
aws ecs execute-command --cluster ecs-test-cluster --task タスクのID --container ecs-test-task --interactive --command "/bin/sh"
さいごに
今回はApplication AutoScalingの設定までは入れませんでしたが必要であれば以下のドキュメントのリソースを使用して作成することが可能です。
Application Auto Scaling resource type reference