この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、コカコーラ大好きカジです。
以前、VPC構築済みの環境や、同じVPC内に複数のFargateを複数構築するときに使えるCloudFormationテンプレートを作成し公開しました。
上記のCloudFormationテンプレートで構築した環境をBlue/Green Deploymentへ変更しようとした際に、現在マネージメントコンソールから変更できず、解決した方法を記載しておきます。2020年3月時点、上記ブログのCloudFormationから作成したECSのサービス更新から、Blue/Green Deploymentの選択が表示されません。今後変更されると推測しています。(マネージメントコンソールから手動でFargateを構築した場合は影響を受けません。)
CloudFormationのテンプレートを一部追記することで、マネージメントコンソールのECS Serviceの更新からBlue/Green Deploymentの選択できるようになりましたが、全体の手順もまとめてご紹介します。
目次
前提条件
- 上記CloudFormationテンプレートで構築できる準備が整っている必要があります。
- 前回のStackからのUpdate Stackでは名前が重複しエラーとなり、更新できません。そのためStackを一度削除し再作成か、すべて別名での構築となります。
Blue/Green Deploymentとは
Blue/Green Deploymentはデプロイの一つのパターンです。
本番環境内に2つの環境を用意し、トラフィックを片方だけに向けておきます。新しいバージョンのデプロイは本番のトラフィックが流れていない方の環境に行い、その環境上で正しく動作していることを確認したら、トラフィックをその環境に切り替えることで新しいバージョンをリリースします。
今回のFargateに対してのCodeDeployでのBlue/Green Deploymentでは2つのターゲットグループを作成し、ALBからのトラフィックを流すターゲットグループを切り替えることでBlue/Green Deploymentするための変更を行います。
詳細は以下
前回のブログからの変更点(前回のブログを見てくれている人向け)
一番重要なポイントは、ECS Fargateは、以下の2行を追記となります。
# ------------------------------------------------------------#
# ECS Service
# ------------------------------------------------------------#
ECSService:
Type: AWS::ECS::Service
DependsOn: ALBListener
Properties:
Cluster: !Ref ECSCluster
DesiredCount: !Ref ECSTaskDesiredCount
DeploymentController: # 追記
Type: CODE_DEPLOY # 追記
その他変更点
- ALBのターゲットグループとリスナーをそれぞれ1つ追加
- Blue/Green DeploymentのCodeDeployと接続する際に、CodeDeploy用のRoleの指定が必要となるため一緒にCloudFormationで構築
- Blue/Green DeploymentのCodeDeployをAWS CLIで構築 (現時点で、CloudFormation非対応のため)
- マネージメントコンソールのECS Serviceの更新からBlue/Green Deploymentの選択し設定 (現時点で、CloudFormation非対応のため)
作業手順
文末のCloudFormationテンプレートで、ALBとFargateを構築します。(構築方法は前回のブログと同じ)
構築したCloudFormationのリソース情報(赤い四角部分)から文末のJSONファイルを一部修正します。
文末のAWS CLIのスクリプト実行でCodeDeployのBlue/Green Deploymentを構築します。
上記まで完了したら、マネージメントコンソールからECS>作成したServiceの更新から、Blue/Green Deploymentの選択します。
新しいデプロイの強制にチェックを入れて、「次のステップ」をクリック
デプロイメントの設定で、AWS CLIで構築したCodeDeployのアプリケーション名と、デプロイメントグループが選択されていることを確認し「次のステップ」をクリック
次以降は、しばらく「次のステップ」を連打となります。
変更内容を確認し、サービスの更新を押します。
正常に行えると以下のようになり、Blue/Green Deploymentが設定されました。
Blue/Green Deploymentに必要なCode Deploy作成用スクリプトサンプル
AWS CLIがインストール済みの環境で行なってください。
CloudFromationで構築後にCodeDeploy GroupのJSONファイルを修正してから、スクリプトを実行します。
詳細は以下のブログが参考になります。
sample-fargate-deploy-group.json
Create Stackで作成後のリソースを確認し以下のJSONを修正します。ALBのターゲットグループ、リスナーや、Cluster/Service名、CodeDeployのRoleを修正します。
{
"applicationName": "sample-fargate-app",
"autoRollbackConfiguration": {
"enabled": true,
"events": [
"DEPLOYMENT_FAILURE"
]
},
"blueGreenDeploymentConfiguration": {
"deploymentReadyOption": {
"actionOnTimeout": "CONTINUE_DEPLOYMENT",
"waitTimeInMinutes": 0
},
"terminateBlueInstancesOnDeploymentSuccess": {
"action": "TERMINATE",
"terminationWaitTimeInMinutes": 5
}
},
"deploymentGroupName": "sample-fargate-deploygroup",
"deploymentStyle": {
"deploymentOption": "WITH_TRAFFIC_CONTROL",
"deploymentType": "BLUE_GREEN"
},
"loadBalancerInfo": {
"targetGroupPairInfoList": [
{
"targetGroups": [
{
"name": "sample-fargate-tg1"
},
{
"name": "sample-fargate-tg2"
}
],
"prodTrafficRoute": {
"listenerArns": [
"arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:listener/app/sample-fargate-alb/f064a3708a0a154d/95624f75b7087fb2"
]
},
"testTrafficRoute": {
"listenerArns": [
"arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:listener/app/sample-fargate-alb/f064a3708a0a154d/64defa76d77a59a5"
]
}
}
]
},
"serviceRoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/sample-fargate-CodeDeployRole",
"ecsServices": [
{
"serviceName": "sample-fargate-service",
"clusterName": "sample-fargate-cluster"
}
]
}
create-sample-codedeploy.sh
上記JSONファイルを修正してから実行します。
#!/bin/bash
aws deploy create-application \
--application-name sample-fargate-app \
--compute-platform ECS \
--region ap-northeast-1
aws deploy create-deployment-group \
--cli-input-json file://sample-fargate-deploy-group.json \
--region ap-northeast-1
スクリプト成功時のログ
$ ./create-sample-codedeploy.sh
{
"applicationId": "ebbd58ba-a9e2-4ec4-94dc-xxxxxxxxxxxx"
}
{
"deploymentGroupId": "c5df86a3-75dd-4926-9ba3-xxxxxxxxxxxx"
}
CloudFormationテンプレート
構築方法は前回のブログと変わりません。
AWSTemplateFormatVersion: "2010-09-09"
Description: Fargate and ALB Create
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "Project Name Prefix"
Parameters:
- ProjectName
- Label:
default: "InternetALB Configuration"
Parameters:
- InternetALBName
- TargetGroupName1
- TargetGroupName2
- Label:
default: "Fargate for ECS Configuration"
Parameters:
- ECSClusterName
- ECSTaskName
- ECSTaskCPUUnit
- ECSTaskMemory
- ECSContainerName
- ECSImageName
- ECSServiceName
- ECSTaskDesiredCount
- Label:
default: "Netowork Configuration"
Parameters:
- VpcId
- ALBSecurityGroupId
- ALBSubnetId1
- ALBSubnetId2
- ECSSecurityGroupId
- ECSSubnetId1
- ECSSubnetId2
- Label:
default: "Scaling Configuration"
Parameters:
- ServiceScaleEvaluationPeriods
- ServiceCpuScaleOutThreshold
- ServiceCpuScaleInThreshold
- TaskMinContainerCount
- TaskMaxContainerCount
ParameterLabels:
InternetALBName:
default: "InternetALBName"
TargetGroupName1:
default: "TargetGroupName1"
TargetGroupName2:
default: "TargetGroupName2"
ECSClusterName:
default: "ECSClusterName"
ECSTaskName:
default: "ECSTaskName"
ECSTaskCPUUnit:
default: "ECSTaskCPUUnit"
ECSTaskMemory:
default: "ECSTaskMemory"
ECSContainerName:
default: "ECSContainerName"
ECSImageName:
default: "ECSImageName"
ECSServiceName:
default: "ECSServiceName"
ECSTaskDesiredCount:
default: "ECSTaskDesiredCount"
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
ProjectName:
Default: sample-fargate
Type: String
#VPCID
VpcId:
Description: "VPC ID"
Type: AWS::EC2::VPC::Id
#ALBSecurity Group
ALBSecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
#ALBSubnet1
ALBSubnetId1:
Description: "ALB Subnet 1st"
Type: AWS::EC2::Subnet::Id
#ALBSubnet2
ALBSubnetId2:
Description: "ALB Subnet 2st"
Type: AWS::EC2::Subnet::Id
#ECSSecurity Group
ECSSecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
#ECSSubnet1
ECSSubnetId1:
Description: "ECS Subnet 1st"
Type: AWS::EC2::Subnet::Id
#ECSSubnet2
ECSSubnetId2:
Description: "ECS Subnet 2st"
Type: AWS::EC2::Subnet::Id
#InternetALB
InternetALBName:
Type: String
Default: "alb"
#TargetGroupName1
TargetGroupName1:
Type: String
Default: "tg1"
#TargetGroupName2
TargetGroupName2:
Type: String
Default: "tg2"
#ECSClusterName
ECSClusterName:
Type: String
Default: "cluster"
#ECSTaskName
ECSTaskName:
Type: String
Default: "task"
#ECSTaskCPUUnit
ECSTaskCPUUnit:
AllowedValues: [256, 512, 1024, 2048, 4096]
Type: String
Default: "256"
#ECSTaskMemory
ECSTaskMemory:
AllowedValues: [256, 512, 1024, 2048, 4096]
Type: String
Default: "512"
#ECSContainerName
ECSContainerName:
Type: String
Default: "container"
#ECSImageName
ECSImageName:
Type: String
Default: "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/kaji-test-ecr:latest"
#ECSServiceName
ECSServiceName:
Type: String
Default: "service"
#ECSTaskDesiredCount
ECSTaskDesiredCount:
Type: Number
Default: 1
# Scaling params
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
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
Resources:
# ------------------------------------------------------------#
# Target Group
# ------------------------------------------------------------#
TargetGroup1:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
VpcId: !Ref VpcId
Name: !Sub "${ProjectName}-${TargetGroupName1}"
Protocol: HTTP
Port: 80
TargetType: ip
TargetGroup2:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
VpcId: !Ref VpcId
Name: !Sub "${ProjectName}-${TargetGroupName2}"
Protocol: HTTP
Port: 80
TargetType: ip
# ------------------------------------------------------------#
# Internet ALB
# ------------------------------------------------------------#
InternetALB:
Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
Properties:
Name: !Sub "${ProjectName}-${InternetALBName}"
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${InternetALBName}"
Scheme: "internet-facing"
LoadBalancerAttributes:
- Key: "deletion_protection.enabled"
Value: false
- Key: "idle_timeout.timeout_seconds"
Value: 60
- Key: "access_logs.s3.enabled"
Value: true
- Key: "access_logs.s3.bucket"
Value: !Sub "alb-log-${AWS::AccountId}"
SecurityGroups:
- !Ref ALBSecurityGroupId
Subnets:
- !Ref ALBSubnetId1
- !Ref ALBSubnetId2
ALBListener1:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup1
Type: forward
LoadBalancerArn: !Ref InternetALB
Port: 80
Protocol: HTTP
ALBListener2:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup2
Type: forward
LoadBalancerArn: !Ref InternetALB
Port: 8080
Protocol: HTTP
# ------------------------------------------------------------#
# ECS Cluster
# ------------------------------------------------------------#
ECSCluster:
Type: "AWS::ECS::Cluster"
Properties:
ClusterName: !Sub "${ProjectName}-${ECSClusterName}"
# ------------------------------------------------------------#
# ECS LogGroup
# ------------------------------------------------------------#
ECSLogGroup:
Type: "AWS::Logs::LogGroup"
Properties:
LogGroupName: !Sub "/ecs/logs/${ProjectName}-ecs-group"
# ------------------------------------------------------------#
# ECS Task Execution Role
# ------------------------------------------------------------#
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${ProjectName}-ECSTaskExecutionRolePolicy"
Path: /
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
# ------------------------------------------------------------#
# ECS TaskDefinition
# ------------------------------------------------------------#
ECSTaskDefinition:
Type: "AWS::ECS::TaskDefinition"
Properties:
Cpu: !Ref ECSTaskCPUUnit
ExecutionRoleArn: !Ref ECSTaskExecutionRole
Family: !Sub "${ProjectName}-${ECSTaskName}"
Memory: !Ref ECSTaskMemory
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
#ContainerDefinitions
ContainerDefinitions:
- Name: !Sub "${ProjectName}-${ECSContainerName}"
Image: !Ref ECSImageName
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref ECSLogGroup
awslogs-region: !Ref "AWS::Region"
awslogs-stream-prefix: !Ref ProjectName
MemoryReservation: 128
PortMappings:
- HostPort: 80
Protocol: tcp
ContainerPort: 80
# ------------------------------------------------------------#
# ECS Service
# ------------------------------------------------------------#
ECSService:
Type: AWS::ECS::Service
DependsOn: ALBListener1
Properties:
Cluster: !Ref ECSCluster
DesiredCount: !Ref ECSTaskDesiredCount
DeploymentController:
Type: CODE_DEPLOY
LaunchType: FARGATE
LoadBalancers:
- TargetGroupArn: !Ref TargetGroup1
ContainerPort: 80
ContainerName: !Sub "${ProjectName}-${ECSContainerName}"
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- !Ref ECSSecurityGroupId
Subnets:
- !Ref ECSSubnetId1
- !Ref ECSSubnetId2
ServiceName: !Sub "${ProjectName}-${ECSServiceName}"
TaskDefinition: !Ref ECSTaskDefinition
# ------------------------------------------------------------#
# Auto Scaling Service
# ------------------------------------------------------------#
ServiceAutoScalingRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: application-autoscaling.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: !Sub "${ProjectName}-${ECSContainerName}-autoscaling"
PolicyDocument:
Statement:
- Effect: Allow
Action:
- application-autoscaling:*
- cloudwatch:DescribeAlarms
- cloudwatch:PutMetricAlarm
- ecs:DescribeServices
- ecs:UpdateService
Resource: "*"
ServiceScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MinCapacity: !Ref TaskMinContainerCount
MaxCapacity: !Ref TaskMaxContainerCount
ResourceId: !Sub
- service/${EcsClusterName}/${EcsDefaultServiceName}
- EcsClusterName: !Ref ECSCluster
EcsDefaultServiceName: !Sub "${ProjectName}-${ECSServiceName}"
RoleARN: !GetAtt ServiceAutoScalingRole.Arn
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
DependsOn:
- ECSService
- ServiceAutoScalingRole
ServiceScaleOutPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub "${ProjectName}-${ECSServiceName}-ScaleOutPolicy"
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 "${ProjectName}-${ECSServiceName}-ScaleInPolicy"
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 "${ProjectName}-${ECSServiceName}-ScaleOutAlarm"
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: !Ref ECSCluster
- Name: ServiceName
Value: !Sub "${ProjectName}-${ECSServiceName}"
ComparisonOperator: GreaterThanThreshold
MetricName: CPUUtilization
DependsOn:
- ECSService
- ServiceScaleOutPolicy
ServiceScaleInAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${ProjectName}-${ECSServiceName}-ScaleInAlarm"
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: !Ref ECSCluster
- Name: ServiceName
Value: !Sub "${ProjectName}-${ECSServiceName}"
ComparisonOperator: LessThanThreshold
MetricName: CPUUtilization
DependsOn:
- ECSService
- ServiceScaleInPolicy
# ------------------------------------------------------------#
# BlueGreen CodeDeploy Role
# ------------------------------------------------------------#
CodeDeployRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${ProjectName}-CodeDeployRole"
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: codedeploy.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS
感想
自分でハマった点を解決したメモを公開しました。どなたかのお役に立てれば光栄です。 また、今後、CloudFormationでFargateとCodeDeployのBlue/Green Deploymentもまとめて構築できるようになり、サンプルテンプレートが公開されることを期待しております。