本日(2022年10月8日)、JAWS Days 2022が行われた。この際、コンテナサービスのハンズオンに参加していたのだが、必要なリソース群をAWS CloudFormationでデプロイしようとして、とても時間を食ってしまっていたが、無事にハンズオンの範囲まで作成が完了したので報告する。
なお、元の資料はここに講師が出しているので、参照せよ。
ECS用のロールの作成
ハンズオンの通り。ただし、別にecs.amazonaws.comサービスが引き受け可能な、AmazonEC2ContainerServiceRoleポリシーをアタッチしたロールも作成しておくこと。これをやらないと、コンテナのマネージができないようだ。
VPCテンプレート
今回は、3AZである前提で作成している。4AZ以上だったり、2AZしかない場合は、適宜テンプレートを修正せよ。
AWSTemplateFormatVersion: "2010-09-09"
Description: VPC for ECS Hands-on
Parameters:
VpcCIDR:
Type: String
Description: VPC CIDR address
Default: 10.0.0.0/16
EnableInterfaceEndpoint:
Type: String
Description: Enable Interface endpoint or not
AllowedValues:
- true
- false
Default: false
TagName:
Type: String
Description: Tag name
Default: ''
Conditions:
HasTag:
Fn::Not:
- !Equals
- !Ref TagName
- ''
EnabledInterfaceEndpoint: !Equals
- !Ref EnableInterfaceEndpoint
- true
Resources:
Vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCIDR
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-Vpc"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
IPv6Block:
Type: AWS::EC2::VPCCidrBlock
Properties:
VpcId: !Ref Vpc
AmazonProvidedIpv6CidrBlock: true
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-InternetGateway"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref Vpc
InternetGatewayId: !Ref InternetGateway
EgressOnlyGateway:
Type: AWS::EC2::EgressOnlyInternetGateway
Properties:
VpcId: !Ref Vpc
PublicSubnet1:
Type: AWS::EC2::Subnet
DependsOn: IPv6Block
Properties:
VpcId: !Ref Vpc
AvailabilityZone:
Fn::Select:
- 0
- !GetAZs ''
CidrBlock:
Fn::Select:
- 0
-
Fn::Cidr:
- !GetAtt Vpc.CidrBlock
- 6
- 13
Ipv6CidrBlock:
Fn::Select:
- 0
-
Fn::Cidr:
-
Fn::Select:
- 0
- !GetAtt Vpc.Ipv6CidrBlocks
- 6
- 64
MapPublicIpOnLaunch: true
PrivateDnsNameOptionsOnLaunch:
EnableResourceNameDnsAAAARecord : true
EnableResourceNameDnsARecord: true
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-PublicSubnet1"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
-
Key: SubnetType
Value: Public
PrivateSubnet1:
Type: AWS::EC2::Subnet
DependsOn: IPv6Block
Properties:
VpcId: !Ref Vpc
AvailabilityZone:
Fn::Select:
- 0
- !GetAZs ''
CidrBlock:
Fn::Select:
- 1
-
Fn::Cidr:
- !GetAtt Vpc.CidrBlock
- 6
- 13
Ipv6CidrBlock:
Fn::Select:
- 1
-
Fn::Cidr:
-
Fn::Select:
- 0
- !GetAtt Vpc.Ipv6CidrBlocks
- 6
- 64
AssignIpv6AddressOnCreation: true
PrivateDnsNameOptionsOnLaunch:
EnableResourceNameDnsAAAARecord : true
EnableResourceNameDnsARecord: true
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-PrivateSubnet1"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
-
Key: SubnetType
Value: Private
PublicSubnet2:
Type: AWS::EC2::Subnet
DependsOn: IPv6Block
Properties:
VpcId: !Ref Vpc
AvailabilityZone:
Fn::Select:
- 1
- !GetAZs ''
CidrBlock:
Fn::Select:
- 2
-
Fn::Cidr:
- !GetAtt Vpc.CidrBlock
- 6
- 13
Ipv6CidrBlock:
Fn::Select:
- 2
-
Fn::Cidr:
-
Fn::Select:
- 0
- !GetAtt Vpc.Ipv6CidrBlocks
- 6
- 64
MapPublicIpOnLaunch: true
PrivateDnsNameOptionsOnLaunch:
EnableResourceNameDnsAAAARecord : true
EnableResourceNameDnsARecord: true
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-PublicSubnet2"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
-
Key: SubnetType
Value: Public
PrivateSubnet2:
Type: AWS::EC2::Subnet
DependsOn: IPv6Block
Properties:
VpcId: !Ref Vpc
AvailabilityZone:
Fn::Select:
- 1
- !GetAZs ''
CidrBlock:
Fn::Select:
- 3
-
Fn::Cidr:
- !GetAtt Vpc.CidrBlock
- 6
- 13
Ipv6CidrBlock:
Fn::Select:
- 3
-
Fn::Cidr:
-
Fn::Select:
- 0
- !GetAtt Vpc.Ipv6CidrBlocks
- 6
- 64
AssignIpv6AddressOnCreation: true
PrivateDnsNameOptionsOnLaunch:
EnableResourceNameDnsAAAARecord : true
EnableResourceNameDnsARecord: true
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-PrivateSubnet2"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
-
Key: SubnetType
Value: Private
PublicSubnet3:
Type: AWS::EC2::Subnet
DependsOn: IPv6Block
Properties:
VpcId: !Ref Vpc
AvailabilityZone:
Fn::Select:
- 2
- !GetAZs ''
CidrBlock:
Fn::Select:
- 4
-
Fn::Cidr:
- !GetAtt Vpc.CidrBlock
- 6
- 13
Ipv6CidrBlock:
Fn::Select:
- 4
-
Fn::Cidr:
-
Fn::Select:
- 0
- !GetAtt Vpc.Ipv6CidrBlocks
- 6
- 64
MapPublicIpOnLaunch: true
PrivateDnsNameOptionsOnLaunch:
EnableResourceNameDnsAAAARecord : true
EnableResourceNameDnsARecord: true
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-PublicSubnet3"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
-
Key: SubnetType
Value: Public
PrivateSubnet3:
Type: AWS::EC2::Subnet
DependsOn: IPv6Block
Properties:
VpcId: !Ref Vpc
AvailabilityZone:
Fn::Select:
- 2
- !GetAZs ''
CidrBlock:
Fn::Select:
- 5
-
Fn::Cidr:
- !GetAtt Vpc.CidrBlock
- 6
- 13
Ipv6CidrBlock:
Fn::Select:
- 5
-
Fn::Cidr:
-
Fn::Select:
- 0
- !GetAtt Vpc.Ipv6CidrBlocks
- 6
- 64
AssignIpv6AddressOnCreation: true
PrivateDnsNameOptionsOnLaunch:
EnableResourceNameDnsAAAARecord : true
EnableResourceNameDnsARecord: true
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-PrivateSubnet3"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
-
Key: SubnetType
Value: Private
PublicRouteTable:
Type: AWS::EC2::RouteTable
DependsOn: IPv6Block
Properties:
VpcId: !Ref Vpc
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-PublicRouteTable"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
PublicRouteV4:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicRouteV6:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationIpv6CidrBlock: '::/0'
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet1
PublicSubnet2RouteTableAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet2
PublicSubnet3RouteTableAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet3
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-PrivateRouteTable"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
PrivateEgressRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationIpv6CidrBlock: '::/0'
EgressOnlyInternetGatewayId: !Ref EgressOnlyGateway
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
VpcId: !Ref Vpc
VpcEndpointType: Gateway
RouteTableIds:
- !Ref PrivateRouteTable
PrivateSubnet1RouteTableAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref PrivateSubnet1
PrivateSubnet2RouteTableAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref PrivateSubnet2
PrivateSubnet3RouteTableAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref PrivateSubnet3
FrontSecGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Front security group
VpcId: !Ref Vpc
SecurityGroupIngress:
-
Description: Ingress IPv4 HTTP
IpProtocol: tcp
CidrIp: '0.0.0.0/0'
FromPort: 80
ToPort: 80
-
Description: Ingress IPv6 HTTP
IpProtocol: tcp
CidrIpv6: '::/0'
FromPort: 80
ToPort: 80
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-FrontSecGroup"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
BackSecGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Backend security group
VpcId: !Ref Vpc
SecurityGroupIngress:
-
Description: Ingress IPv4 HTTP
IpProtocol: tcp
SourceSecurityGroupId: !Ref FrontSecGroup
FromPort: 80
ToPort: 80
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-BackSecGroup"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
VpcEndpointSecGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: VPC endpoint security group
VpcId: !Ref Vpc
SecurityGroupIngress:
-
Description: Ingress IPv4 HTTPS
IpProtocol: tcp
CidrIp: '0.0.0.0/0'
FromPort: 443
ToPort: 443
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-VpcEndpointSecGroup"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
DkrVpcEndpoint:
Type: AWS::EC2::VPCEndpoint
Condition: EnabledInterfaceEndpoint
Properties:
ServiceName: com.amazonaws.ap-northeast-1.ecr.dkr
VpcId: !Ref Vpc
VpcEndpointType: Interface
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref VpcEndpointSecGroup
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
- !Ref PrivateSubnet3
EcrApiVpcEndpoint:
Type: AWS::EC2::VPCEndpoint
Condition: EnabledInterfaceEndpoint
Properties:
ServiceName: com.amazonaws.ap-northeast-1.ecr.api
VpcId: !Ref Vpc
VpcEndpointType: Interface
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref VpcEndpointSecGroup
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
- !Ref PrivateSubnet3
LogsVpcEndpoint:
Type: AWS::EC2::VPCEndpoint
Condition: EnabledInterfaceEndpoint
Properties:
ServiceName: com.amazonaws.ap-northeast-1.logs
VpcId: !Ref Vpc
VpcEndpointType: Interface
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref VpcEndpointSecGroup
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
- !Ref PrivateSubnet3
パラメータは、以下の3つをとる。
- VpcCIDR - VPCのCIDR。デフォルトは10.0.0.0/16で、ハンズオンの通りである
- EnableInterfaceEndpoint - インタフェースVPCエンドポイントを有効にするか。デフォルトはfalseで、デプロイしない。インタフェースVPCエンドポイントは有料なので、それだけシャットダウンするための仕組み。サービス稼働時にはtrueにすること
- TagName - コストタグ名。デフォルトではスタック名をそのまま用いる。VPCエンドポイントにはタグをつけられないので、後から手作業でつけること(こちらのIssueも参照せよ)
ハンズオンとの相違点としては、
- 2AZではなく3AZでデプロイしている
- インタフェースVPCエンドポイントに関しては専用のセキュリティグループに切り分け、バックエンドセキュリティグループに不要な443番ポートへのアクセスを除外している
ECRテンプレート
AWSTemplateFormatVersion: "2010-09-09"
Description: Amazon ECR
Parameters:
TagName:
Type: String
Description: Tag name
Default: ''
Conditions:
HasTag:
Fn::Not:
- !Equals
- !Ref TagName
- ''
Resources:
Repository:
Type: AWS::ECR::Repository
Properties:
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-Repository"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
パラメータは、TagNameの1つのみをとる。説明はVPCスタックと同様。
URLはデプロイ後に確認すること。
コンテナのpush
ハンズオンの通りだが、URLが異なるのでその点に注意せよ。
ECSテンプレート
AWSTemplateFormatVersion: "2010-09-09"
Description: ECR Hands On Main
Parameters:
ImageName:
Type: String
Description: Image name
Vpc:
Type: AWS::EC2::VPC::Id
Description: VPC ID
PublicSubnets:
Type: List<AWS::EC2::Subnet::Id>
Description: Public subnet IDs
FrontSecGroup:
Type: AWS::EC2::SecurityGroup::Id
Description: Front security group ID
PrivateSubnets:
Type: List<AWS::EC2::Subnet::Id>
Description: Private subnet IDs
BackSecGroup:
Type: AWS::EC2::SecurityGroup::Id
Description: Back security group ID
TagName:
Type: String
Description: Tag name
Default: ''
Conditions:
HasTag:
Fn::Not:
- !Equals
- !Ref TagName
- ''
Resources:
Alb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
IpAddressType: dualstack
Subnets: !Ref PublicSubnets
SecurityGroups:
- !Ref FrontSecGroup
Type: application
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-Alb"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckPath: /index.php
HealthCheckProtocol: HTTP
HealthCheckPort: traffic-port
Protocol: HTTP
Port: 80
TargetType: ip
VpcId: !Ref Vpc
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-TargetGroup"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
AlbListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
-
Type: forward
TargetGroupArn: !Ref TargetGroup
LoadBalancerArn: !Ref Alb
Port: 80
Protocol: HTTP
EcsCluster:
Type: AWS::ECS::Cluster
Properties:
CapacityProviders:
- FARGATE
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-EcsCluster"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays: 14
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-LogGroup"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
EcsTaskDef:
Type: AWS::ECS::TaskDefinition
Properties:
ExecutionRoleArn:
!Sub "arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole"
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
RuntimePlatform:
CpuArchitecture: X86_64
OperatingSystemFamily: LINUX
Cpu: '256'
Memory: '512'
ContainerDefinitions:
-
Name: Web
Image: !Ref ImageName
PortMappings:
-
ContainerPort: 80
HostPort: 80
Protocol: tcp
Essential: true
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-create-group: 'true'
awslogs-group: !Ref LogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: hands-on
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-EcsTaskDef"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
EcsService:
Type: AWS::ECS::Service
DependsOn: AlbListener
Properties:
Cluster: !Ref EcsCluster
TaskDefinition: !Ref EcsTaskDef
DeploymentController:
Type: CODE_DEPLOY
DesiredCount: 2
LaunchType: FARGATE
PlatformVersion: LATEST
NetworkConfiguration:
AwsvpcConfiguration:
SecurityGroups:
- !Ref BackSecGroup
Subnets: !Ref PrivateSubnets
LoadBalancers:
-
ContainerName: Web
ContainerPort: 80
TargetGroupArn: !Ref TargetGroup
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-EcsService"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
パラメータは、以下の7つをとる。
- ImageName - 必須。イメージの名前。さっきpushしたDockerイメージのURLを指定する
- Vpc - VPCのID。最初に作ったVPCのIDを指定する
- PublicSubnets - 必須。パブリックサブネットのID群を指定する。CLIで指定する場合はカンマ区切りで指定する
- FrontSecGroup - 必須。フロントのセキュリティグループID(ハンズオンのパブリックサブネットのセキュリティグループ)
- PrivateSubnets - 必須。プライベートサブネットのID群を指定する。CLIで指定する場合はカンマ区切りで指定する
- BackSecGroup - 必須。バックエンドのセキュリティグループID(ハンズオンのプライベートサブネットのセキュリティグループ)
- TagName - コストタグ名。デフォルトではスタック名をそのまま用いる
デプロイの順序は、VPCスタック→ECRスタック→ECRイメージ→ECSスタックの順である。ただし、VPCスタックとECRスタック→ECRイメージは順不同である。
解体は、ECSスタック削除→ECRイメージ削除→VPCスタックのVPCエンドポイント無効化で、費用発生を止めることができる。
CodeCommitレポジトリの作成
ここからは、資料で説明されている後半戦。
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CodeCommit
Parameters:
RepositoryName:
Type: String
Description: Repository name
TagName:
Type: String
Description: Tag name
Default: ''
Conditions:
HasTag:
Fn::Not:
- !Equals
- !Ref TagName
- ''
Resources:
Repository:
Type: AWS::CodeCommit::Repository
Properties:
RepositoryName: !Ref RepositoryName
RepositoryDescription: ECS Hands-on repository
Tags:
-
Key: Name
Value: !Sub "${AWS::StackName}-Repository"
-
Key: CostTag
Value:
Fn::If:
- HasTag
- !Ref TagName
- !Ref AWS::StackName
パラメータは2つ。
- RepositoryName - 必須。レポジトリの名前を指定する。ハンズオンの通りにするなら、ContainerHandsOnと指定する
- TagName - コストタグ名。デフォルトではスタック名をそのまま用いる
CodeCommitを触るのにGitクレデンシャルを用意せずにアクセスする方法
方法は以下の通り。
-
pip install git-remote-codecommit
する。場合によってはpip3になったり、sudoが必要だったり、管理者権限が必要になるかもしれない -
git clone codecommit://[レポジトリ名]
する。プロファイルを名前付きにする場合はcodecommit://[プロファイル名]@[レポジトリ名]
する。リージョンが異なる場合、スキームがcodecommit::[リージョン名]
になる
以上の情報はAWS公式が出している。