はじめに
30代未経験からエンジニアを目指して勉強中のYNと申します。
自分の学習記録としてまとめます。
全体像
ECSクラスタ内では、service discoveryを使って複数のservice間で通信を行うことができます。
今回は、下記のように簡単な2つのserviceを連携させます。
-
https://subdomain.domain.com
=> Frontend ECS serviceからHello from FRONTEND-server!
が返ってくる -
https://subdomain.domain.com/backend
=> Frontend ECS serviceからBackend ECS serviceを介してHello from BACKEND-server!
が返ってくる
※FargateとNat gatewayは時間課金となりますのでご注意ください。
前提条件
- AWS-CLIを設定済み
- ドメインを取得後、ネームサーバーの設定済(参照)
1) フロントエンドアプリの作成
ビルドしてECRやDockerhubなど適当なレジストリにpushしてください。
const express = require('express');
const app = express();
const port = 3000;
const axios = require('axios');
const url = 'http://node-service-backend.example-namespace.local:8000';
// backendサービスに繋ぐためには、後ほど作成するservice discoveryの設定が必要
// Route53でクラスター内のnamespaceを作り、そこにbackendサービスの場所を問い合わせる必要がある。
// エンドポイントは、ecsService.namespace:portとなる
// 今回は、backend ECS service名がnode-service-backendで、namespace名がexample-namespace.localとなる
app.get('/', (req, res) => {
res.send('Hello from FRONTEND-server!');
});
app.get('/backend', async (req, res) => {
const axios_res = await axios.get(url);
res.send(axios_res.data);
});
app.listen(port, () => {
console.log(`Example app listening at port:${port}`);
});
FROM node:12
WORKDIR /usr/src/app
COPY package.json ./
RUN npm install
COPY frontend-app.js .
EXPOSE 3000
CMD [ "node", "frontend-app.js" ]
2) バックエンドアプリの作成
ビルドしてECRやDockerhubなど適当なレジストリにpushしてください。
const express = require('express');
const app = express();
const port = 8000;
app.get('/', (req, res) => {
res.send('Hello from BACKEND-server!');
});
app.listen(port, () => {
console.log(`Example app listening at port:${port}`);
});
FROM node:12
WORKDIR /usr/src/app
COPY package.json ./
RUN npm install
COPY backend-app.js .
EXPOSE 8000
CMD [ "node", "backend-app.js" ]
3) IAMロールの作成
下記2つのロールを作成します
- ECSタスクを実行するためのロール
- オートスケールを実行するためのロール
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:
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
# 下記ManagedPolicyArnsとPoliciesは同義。ただし下のように実際に書き出した方がわかりやすい
# ManagedPolicyArns:
# - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
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: '*'
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: '*'
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 ecs-iam-role --capabilities CAPABILITY_IAM --template-body file://./iam_role.yml
4) VPCの設定
VPCの設定をします。
2つのAZにそれぞれpublic/privateサブネットを1つづつ配置し、publicサブネットに配置したNatに対してprivateサブネットからルーティングします。Nat gateway
は時間課金なので注意しましょう。
Nat(もしくは今回やらないPrivate link)を配置しないと、privateサブネットからS3やECR/Dockerhubなどにアクセスすることができません。
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'
PrivateOne:
CIDR: '172.16.2.0/24'
PrivateTwo:
CIDR: '172.16.3.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
PrivateSubnetOne:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: { Ref: 'AWS::Region' }
VpcId: !Ref 'VPC'
CidrBlock: !FindInMap ['SubnetConfig', 'PrivateOne', 'CIDR']
MapPublicIpOnLaunch: true
PrivateSubnetTwo:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: { Ref: 'AWS::Region' }
VpcId: !Ref 'VPC'
CidrBlock: !FindInMap ['SubnetConfig', 'PrivateTwo', 'CIDR']
MapPublicIpOnLaunch: true
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Ref EnvironmentName
InternetGatewayAttachment:
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: InternetGatewayAttachment
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
NatGateway1EIP:
Type: AWS::EC2::EIP
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
NatGateway2EIP:
Type: AWS::EC2::EIP
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
NatGateway1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateway1EIP.AllocationId
SubnetId: !Ref PublicSubnetOne
NatGateway2:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateway2EIP.AllocationId
SubnetId: !Ref PublicSubnetTwo
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName} Private Routes (AZ1)
DefaultPrivateRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway1
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnetOne
PrivateRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName} Private Routes (AZ2)
DefaultPrivateRoute2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable2
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway2
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable2
SubnetId: !Ref PrivateSubnetTwo
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
PrivateSubnetOne:
Description: Private subnet one
Value: !Ref 'PrivateSubnetOne'
Export:
Name: !Sub ${EnvironmentName}:PrivateSubnetOne
PrivateSubnetTwo:
Description: Private subnet two
Value: !Ref 'PrivateSubnetTwo'
Export:
Name: !Sub ${EnvironmentName}:PrivateSubnetTwo
aws cloudformation create-stack --stack-name VPC --capabilities CAPABILITY_IAM --template-body file://./vpc.yml
5) ECSクラスターの設定
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'
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
aws cloudformation create-stack --stack-name ecs-cluster --capabilities CAPABILITY_IAM --template-body file://./ecs_cluster.yml
6) フロントエンドアプリをfrontend serviceとしてデプロイする
publicサブネットにALBを配置してACMと連携させてHTTPS対応します。
ALBからprivateサブネットに配置したフロントエンドアプリにrequestを転送します。
事前に、(お名前.comなどで用意した)ドメイン名をRoute53にホストしておいてください。
詳細はこちら
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: mydomain.com'
SubDomain:
Description: FQDN of the certificate
Type: String
Default: 'subdomain.mydomain.com'
HostZoneId:
Description: FQDN of the hosted zone
Type: String
Default: 'Z0XXXXXXXXXXXXX' # Route53のホストゾーン名
EnvironmentName:
Type: String
Default: ecs-course
ServiceName:
Type: String
Default: node-service
Description: A name for the service
ImageUrl:
Type: String
Default: <your-image-url> # フロントアプリのDockerイメージのurl
Description:
The url of a docker image that contains the application process that
will handle the traffic for this service
ContainerPort:
Type: Number
Default: 3000
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'
Essential: true
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'
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: !Ref 'ContainerPort'
CidrIp: 172.16.0.0/16 #VPC内からの通信のみ許可
# 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 # これによりECRにアクセスできる
SecurityGroups:
- !Ref ContainerSecurityGroup
Subnets:
- Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetOne
- Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetTwo
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 frontend-service --capabilities CAPABILITY_IAM --template-body file://./frontend_service.yml
7) frontend serviceのオートスケーリング設定
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 --capabilities CAPABILITY_IAM --template-body file://./auto_scaling.yml
8) バックエンドアプリをbackend serviceとしてデプロイして、frontend serviceとつなげる
frontend serviceからbackend serviceに接続するためには、service discoveryが必要となります。
AWSTemplateFormatVersion: '2010-09-09'
Description: External, public facing load balancer, for forwarding public traffic to containers
Parameters:
EnvironmentName:
Type: String
Default: ecs-course
NameSpace:
Type: String
Default: example-namespace.local
Description: A name for the namespace
ServiceName:
Type: String
Default: node-service-backend
Description: A name for the service
ImageUrl:
Type: String
Default: <your-image-url> # バックエンドアプリのDockerイメージのurl
Description:
The url of a docker image that contains the application process that
will handle the traffic for this service
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
ContainerPort:
Type: Number
Default: 8000
Description: What port number the application inside the docker container is binding to
DesiredCount:
Type: Number
Default: 1
Description: How many copies of the service task to run
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:
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: !Ref 'ContainerPort'
CidrIp: 172.16.0.0/16 #VPC内からの通信のみ許可
PrivateNamespace: #route53でnamespaceを作成
Type: AWS::ServiceDiscovery::PrivateDnsNamespace
Properties:
Name: !Ref NameSpace
Vpc:
Fn::ImportValue: !Sub ${EnvironmentName}:VpcId
DiscoveryService: #namespaceとserviceを紐付ける
Type: AWS::ServiceDiscovery::Service
Properties:
Description: Discovery Service for the Demo Application
DnsConfig:
RoutingPolicy: MULTIVALUE
DnsRecords:
- TTL: 60
Type: A
- TTL: 60
Type: SRV
HealthCheckCustomConfig:
FailureThreshold: 1
Name: !Ref ServiceName
NamespaceId: !Ref PrivateNamespace
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName}
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: !Ref 'AWS::NoValue' #とりあえず。本来はDBへの接続権限とか
ContainerDefinitions:
- Name: !Ref 'ServiceName'
Cpu: !Ref 'ContainerCpu'
Memory: !Ref 'ContainerMemory'
Essential: true
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'
Service:
Type: AWS::ECS::Service
DependsOn: TaskDefinition
Properties:
ServiceName: !Ref 'ServiceName'
Cluster:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSCluster
LaunchType: FARGATE
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 75
DesiredCount: !Ref 'DesiredCount'
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED # これによりECRにアクセスできる
SecurityGroups:
- !Ref ContainerSecurityGroup
Subnets:
- Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetOne
- Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetTwo
TaskDefinition: !Ref 'TaskDefinition'
ServiceRegistries:
- RegistryArn: !GetAtt DiscoveryService.Arn
Port: !Ref 'ContainerPort'
ServiceScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
DependsOn: Service
Properties:
MinCapacity: !Ref TaskMinContainerCount
MaxCapacity: !Ref TaskMaxContainerCount
ResourceId:
!Sub #この書き方により、コンテキスト内でのみ有効な変数を定義できる
- service/${ECSClusterName}/${ECSServiceName} #auto scalingを適用するECSservieをこのフォーマットで指定。
- ECSClusterName:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSCluster
ECSServiceName: !Ref 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: !Ref 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: !Ref 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: !Ref 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: !Ref ServiceName
ComparisonOperator: GreaterThanThreshold
MetricName: CPUUtilization
DependsOn:
- ServiceScaleOutPolicy
ServiceScaleInAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub
- ${EnvironmentName}-${ECSServiceName}-ScaleInAlarm
- EnvironmentName: !Ref EnvironmentName
ECSServiceName: !Ref 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: !Ref ServiceName
ComparisonOperator: LessThanThreshold
MetricName: CPUUtilization
DependsOn:
- ServiceScaleInPolicy
aws cloudformation create-stack --stack-name backend-service --capabilities CAPABILITY_IAM --template-body file://./backend_service.yml