はじめに
30代未経験からエンジニアを目指して勉強中のYNと申します。
今回はNginxコンテナをFargate
でhttps対応させるCloudFormation
テンプレートをご紹介します。
CloudFormation
を使えばインフラの作成も削除も一瞬なので積極的に使えるようになりたいですよね。
完成像は下記を含みます。
- Route53 + ACM + ALBによるHTTPS設定
- 2つのAZへの負荷分散
- 1つのECS service(Fargate)
- FargateのAuto Scaling設定
前提条件
- AWS-CLIを設定済み
- ドメインを取得後、ネームサーバーの設定済(参照)
1) IAMロールの作成
create_IAM_roles.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: create IAM roles for ECS usage upfront
Parameters:
EnvironmentName:
Type: String
Default: ecs-course
Description: "A name that will be used for namespacing all cluster resources."
Resources:
AutoscalingRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [application-autoscaling.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
Policies:
- PolicyName: service-autoscaling
PolicyDocument:
Statement:
- Effect: Allow
Action:
- 'application-autoscaling:*'
- 'cloudwatch:DescribeAlarms'
- 'cloudwatch:PutMetricAlarm'
- 'ecs:DescribeServices'
- 'ecs:UpdateService'
Resource: '*'
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
Policies:
- PolicyName: AmazonECSTaskExecutionRolePolicy
PolicyDocument:
Statement:
- Effect: Allow
Action:
# Allow the ECS Tasks to download images from ECR
- 'ecr:GetAuthorizationToken'
- 'ecr:BatchCheckLayerAvailability'
- 'ecr:GetDownloadUrlForLayer'
- 'ecr:BatchGetImage'
# Allow the ECS tasks to upload logs to CloudWatch
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: '*'
Outputs:
AutoscalingRole:
Description: The ARN of the role used for autoscaling
Value: !GetAtt 'AutoscalingRole.Arn'
Export:
Name: !Sub ${EnvironmentName}:AutoscalingRole
ECSTaskExecutionRole:
Description: The ARN of the ECS role
Value: !GetAtt 'ECSTaskExecutionRole.Arn'
Export:
Name: !Sub ${EnvironmentName}:ECSTaskExecutionRole
aws cloudformation create-stack --stack-name create-IAM-roles --capabilities CAPABILITY_IAM --template-body file://./create_IAM_roles.yml
2) VPCの設定
vpc_setup.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: VPC and subnets as base for an ECS cluster
Parameters:
EnvironmentName:
Type: String
Default: ecs-course
Mappings:
SubnetConfig:
VPC:
CIDR: '172.16.0.0/16'
PublicOne:
CIDR: '172.16.0.0/24'
PublicTwo:
CIDR: '172.16.1.0/24'
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
EnableDnsSupport: true
EnableDnsHostnames: true
CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR']
PublicSubnetOne:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: { Ref: 'AWS::Region' }
VpcId: !Ref 'VPC'
CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR']
MapPublicIpOnLaunch: true
PublicSubnetTwo:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: { Ref: 'AWS::Region' }
VpcId: !Ref 'VPC'
CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR']
MapPublicIpOnLaunch: true
InternetGateway:
Type: AWS::EC2::InternetGateway
GatewayAttachement:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref 'VPC'
InternetGatewayId: !Ref 'InternetGateway'
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref 'VPC'
PublicRoute:
Type: AWS::EC2::Route
DependsOn: GatewayAttachement
Properties:
RouteTableId: !Ref 'PublicRouteTable'
DestinationCidrBlock: '0.0.0.0/0'
GatewayId: !Ref 'InternetGateway'
PublicSubnetOneRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetOne
RouteTableId: !Ref PublicRouteTable
PublicSubnetTwoRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetTwo
RouteTableId: !Ref PublicRouteTable
Outputs:
DefaultRegion:
Description: The default region setting on aws-cli
Value: { Ref: 'AWS::Region' }
Export:
Name: !Sub ${EnvironmentName}:Region
VpcId:
Description: The ID of the VPC that this stack is deployed in
Value: !Ref 'VPC'
Export:
Name: !Sub ${EnvironmentName}:VpcId
PublicSubnetOne:
Description: Public subnet one
Value: !Ref 'PublicSubnetOne'
Export:
Name: !Sub ${EnvironmentName}:PublicSubnetOne
PublicSubnetTwo:
Description: Public subnet two
Value: !Ref 'PublicSubnetTwo'
Export:
Name: !Sub ${EnvironmentName}:PublicSubnetTwo
aws cloudformation create-stack --stack-name vpc-setup --capabilities CAPABILITY_IAM --template-body file://./vpc_setup.yml
3) ECSクラスターの設定
ecs_cluster_setup.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: ECS cluster launchtype Fargate.
Parameters:
EnvironmentName:
Type: String
Default: ecs-course
Description: 'A name that will be used for namespacing our cluster resources.'
ClusterName:
Type: String
Default: ecs-course-fargate
Description: 'A name that will be used for the ecs cluster.'
Resources:
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Ref 'ClusterName'
ContainerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the ECS hosts that run containers
VpcId:
Fn::ImportValue: !Sub ${EnvironmentName}:VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Outputs:
ECSCluster:
Description: The name of the ECS cluster
Value: !Ref 'ECSCluster'
Export:
Name: !Sub ${EnvironmentName}:ECSCluster
ClusterName:
Description: The name of the ECS cluster
Value: !Ref 'ClusterName'
Export:
Name: !Sub ${EnvironmentName}:ClusterName
ContainerSecurityGroup:
Description: The container sg of the ECS cluster
Value: !Ref 'ContainerSecurityGroup'
Export:
Name: !Sub ${EnvironmentName}:ContainerSecurityGroup
aws cloudformation create-stack --stack-name ecs-cluster-setup --capabilities CAPABILITY_IAM --template-body file://./ecs_cluster_setup.yml
4) Route53 + ACM + ALB + ECS(Fargate)の設定
https_nginx_setup.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: External, public facing load balancer, for forwarding public traffic to containers
Parameters:
DomainName:
Description: FQDN of the HostZone
Type: String
Default: 'example.com' #取得済のドメイン名
SubDomain:
Description: FQDN of the certificate
Type: String
Default: 'test.example.com' #サブドメインを指定
HostZoneId:
Description: FQDN of the hosted zone
Type: String
Default: 'Z05XXXXXXXXXXXXXX' # Route53のホストゾーンIDを記入
EnvironmentName:
Type: String
Default: ecs-course
ServiceName:
Type: String
Default: nginx-service
Description: A name for the service
ImageUrl:
Type: String
Default: nginx:1.17.7 #ECRのイメージも設定できる。(ECSTaskExecutionRoleにてECRが許可されている)
Description:
The url of a docker image that contains the application process that
will handle the traffic for this service
ContainerPort:
Type: Number
Default: 80
Description: What port number the application inside the docker container is binding to
ContainerCpu:
Type: Number
Default: 256
Description: How much CPU to give the container. 1024 is 1 CPU
ContainerMemory:
Type: Number
Default: 512
Description: How much memory in megabytes to give the container
Priority:
Type: Number
Default: 1
Description: The priority for the routing rule added to the load balancer.
This only applies if your have multiple services which have been
assigned to different paths on the load balancer.
DesiredCount:
Type: Number
Default: 1
Description: How many copies of the service task to run
Role:
Type: String
Default: ''
Description:
(Optional) An IAM role to give the service's containers if the code within needs to
access other AWS resources like S3 buckets, DynamoDB tables, etc
Conditions:
HasCustomRole: !Not [!Equals [!Ref 'Role', '']]
Resources:
# A log group for storing the stdout logs from this service's containers
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName}
# The task definition. This is a simple metadata description of what
# container to run, and what resource requirements it has.
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Ref 'ServiceName'
Cpu: !Ref 'ContainerCpu'
Memory: !Ref 'ContainerMemory'
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSTaskExecutionRole
TaskRoleArn:
Fn::If: #三項演算子。1行目がtrueなら2行目が参照され、falseなら3行目が参照される
- 'HasCustomRole'
- !Ref 'Role'
- !Ref 'AWS::NoValue'
ContainerDefinitions:
- Name: !Ref 'ServiceName'
Cpu: !Ref 'ContainerCpu'
Memory: !Ref 'ContainerMemory'
Image: !Ref 'ImageUrl'
PortMappings:
- ContainerPort: !Ref 'ContainerPort'
LogConfiguration:
LogDriver: 'awslogs'
Options:
awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName}
awslogs-region: !Ref 'AWS::Region'
awslogs-stream-prefix: !Ref 'ServiceName'
# The service. The service is a resource which allows you to run multiple
# copies of a type of task, and gather up their logs and metrics, as well
# as monitor the number of running tasks and replace any that have crashed
Service:
Type: AWS::ECS::Service
DependsOn: HTTPSLoadBalancerListener
Properties:
ServiceName: !Ref 'ServiceName'
Cluster:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSCluster
LaunchType: FARGATE
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 75
DesiredCount: !Ref 'DesiredCount'
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup
Subnets:
- Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne
- Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo
TaskDefinition: !Ref 'TaskDefinition'
LoadBalancers:
- ContainerName: !Ref 'ServiceName'
ContainerPort: !Ref 'ContainerPort'
TargetGroupArn: !Ref 'TargetGroup'
ApiDomain:
Type: AWS::Route53::RecordSet
DependsOn: PublicLoadBalancer
Properties:
HostedZoneId: !Sub '${HostZoneId}'
Name: !Sub '${SubDomain}'
Type: CNAME
TTL: 60
ResourceRecords:
- !GetAtt PublicLoadBalancer.DNSName
ACMCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Sub '${SubDomain}'
DomainValidationOptions:
- DomainName: !Sub '${SubDomain}'
HostedZoneId: !Sub '${HostZoneId}'
ValidationMethod: DNS
PublicLoadBalancerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the public facing load balancer
VpcId:
Fn::ImportValue: !Sub ${EnvironmentName}:VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
PublicLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
LoadBalancerAttributes:
- Key: idle_timeout.timeout_seconds
Value: '30'
Subnets:
- Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne
- Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo
SecurityGroups: [!Ref 'PublicLoadBalancerSG']
HTTPLoadBalancerListener: # 80=>443に転送する
Type: AWS::ElasticLoadBalancingV2::Listener
DependsOn:
- PublicLoadBalancer
Properties:
DefaultActions:
- Type: 'redirect'
RedirectConfig:
Protocol: 'HTTPS'
Port: 443
Host: '#{host}'
Path: '/#{path}'
Query: '#{query}'
StatusCode: 'HTTP_301'
LoadBalancerArn: !Ref 'PublicLoadBalancer'
Port: 80
Protocol: HTTP
HTTPSLoadBalancerListener: # SSL設定してTargetGroupに転送
Type: AWS::ElasticLoadBalancingV2::Listener
DependsOn: PublicLoadBalancer
Properties:
LoadBalancerArn: !Ref 'PublicLoadBalancer'
Port: 443
Protocol: HTTPS
DefaultActions:
- Type: 'forward'
TargetGroupArn: !Ref 'TargetGroup'
SslPolicy: 'ELBSecurityPolicy-2016-08'
Certificates:
- CertificateArn: !Ref 'ACMCertificate'
TargetGroup: # Serviceを参照させる
Type: AWS::ElasticLoadBalancingV2::TargetGroup
DependsOn: PublicLoadBalancer
Properties:
HealthCheckIntervalSeconds: 6
HealthCheckPath: /
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
TargetType: ip #fargateなのでVPC内のipをtargetにする。EC2であればinstanceがtargetになる
Name: !Ref 'ServiceName'
Port: !Ref 'ContainerPort'
Protocol: HTTP
UnhealthyThresholdCount: 2
VpcId:
Fn::ImportValue: !Sub ${EnvironmentName}:VpcId
Outputs:
ExternalUrl:
Description: The url of the external load balancer
Value: !Sub https://${PublicLoadBalancer.DNSName}
Export:
Name: !Sub ${EnvironmentName}:ExternalUrl
ServiceName:
Description: The name of ecs service
Value: !Ref 'ServiceName'
Export:
Name: !Sub ${EnvironmentName}:ServiceName
aws cloudformation create-stack --stack-name https-nginx-setup --capabilities CAPABILITY_IAM --template-body file://./https_nginx_setup.yml
5) Auto Scaling Groupの設定
CPU使用率をモニタリングし、一定時間の平均値が連続で閾値を超えるとスケールアウト、閾値を下回るとスケールインします。
auto-scaling-setup.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: AutoScaling setup for fargate service
Parameters:
EnvironmentName:
Type: String
Default: ecs-course
Description: 'A name that will be used for namespacing our cluster resources.'
TaskMinContainerCount:
Type: Number
Description: Minimum number of containers to run for the service
Default: 1
MinValue: 1
ConstraintDescription: Value must be at least one
TaskMaxContainerCount:
Type: Number
Description: Maximum number of containers to run for the service when auto scaling out
Default: 2
MinValue: 1
ConstraintDescription: Value must be at least one
ServiceScaleEvaluationPeriods:
Description: The number of periods over which data is compared to the specified threshold
Type: Number
Default: 2
MinValue: 2
ServiceCpuScaleOutThreshold:
Type: Number
Description: Average CPU value to trigger auto scaling out
Default: 50
MinValue: 0
MaxValue: 100
ConstraintDescription: Value must be between 0 and 100
ServiceCpuScaleInThreshold:
Type: Number
Description: Average CPU value to trigger auto scaling in
Default: 25
MinValue: 0
MaxValue: 100
ConstraintDescription: Value must be between 0 and 100
Resources:
ServiceScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MinCapacity: !Ref TaskMinContainerCount
MaxCapacity: !Ref TaskMaxContainerCount
ResourceId:
!Sub #この書き方により、コンテキスト内でのみ有効な変数を定義できる
- service/${ECSClusterName}/${ECSServiceName} #auto scalingを適用するECSservieをこのフォーマットで指定。
- ECSClusterName:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSCluster
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
RoleARN:
Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
ServiceScaleOutPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub
- ${EnvironmentName}-${ECSServiceName}-ScaleOutPolicy
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
PolicyType: StepScaling
ScalingTargetId: !Ref ServiceScalingTarget
StepScalingPolicyConfiguration:
AdjustmentType: ChangeInCapacity
Cooldown: 60
MetricAggregationType: Average
StepAdjustments:
- ScalingAdjustment: 1
MetricIntervalLowerBound: 0
DependsOn: ServiceScalingTarget
ServiceScaleInPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub
- ${EnvironmentName}-${ECSServiceName}-ScaleInPolicy
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
PolicyType: StepScaling
ScalingTargetId: !Ref ServiceScalingTarget
StepScalingPolicyConfiguration:
AdjustmentType: ChangeInCapacity
Cooldown: 60
MetricAggregationType: Average
StepAdjustments:
- ScalingAdjustment: -1
MetricIntervalUpperBound: 0
DependsOn: ServiceScalingTarget
ServiceScaleOutAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub
- ${EnvironmentName}-${ECSServiceName}-ScaleOutAlarm
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
Statistic: Average
TreatMissingData: notBreaching
Threshold: !Ref ServiceCpuScaleOutThreshold
AlarmDescription: Alarm to add capacity if CPU is high
Period: 60
AlarmActions:
- !Ref ServiceScaleOutPolicy
Namespace: AWS/ECS
Dimensions:
- Name: ClusterName
Value:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSCluster
- Name: ServiceName
Value: !Sub
- ${EnvironmentName}-${ECSServiceName}
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
ComparisonOperator: GreaterThanThreshold
MetricName: CPUUtilization
DependsOn:
- ServiceScaleOutPolicy
ServiceScaleInAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub
- ${EnvironmentName}-${ECSServiceName}-ScaleInAlarm
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
Statistic: Average
TreatMissingData: notBreaching
Threshold: !Ref ServiceCpuScaleInThreshold
AlarmDescription: Alarm to reduce capacity if container CPU is low
Period: 300
AlarmActions:
- !Ref ServiceScaleInPolicy
Namespace: AWS/ECS
Dimensions:
- Name: ClusterName
Value:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSCluster
- Name: ServiceName
Value: !Sub
- ${EnvironmentName}-${ECSServiceName}
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
ComparisonOperator: LessThanThreshold
MetricName: CPUUtilization
DependsOn:
- ServiceScaleInPolicy
aws cloudformation create-stack --stack-name auto-scaling-setup --capabilities CAPABILITY_IAM --template-body file://./auto-scaling-setup.yml