この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは。AWS事業本部トクヤマシュンです。
ECS Fargateのデプロイ方法には、RollingアップデートとBlue/Greenデプロイメントの2種類があります。
それぞれの違いはドキュメントに記載がありますが、実際に触ってみないと中々理解しづらいものです。
そこで今回はCloudFormationを使って両方のデプロイ環境をサクっと構築するためのテンプレートをご紹介します。
こちらは第二弾、Blue Greenデプロイメントです。
興味のある方は、是非一度ご自身の環境で試してみてください。
第一弾のRollingアップデートは次のエントリからどうぞ。
構築する環境
今回の構成は次の図の通りです。
Blue/Greenデプロイメントはフックを使う方法とプレースホルダーを使う方法がありますが、
今回はプレースホルダーを使って構築します。
CFnテンプレートなどのソースコード
構築用のCFnテンプレートやパイプラインソースとして利用するDockerファイルを下記GitHubに格納していますので、構築の際はご参考ください。
構築の際はCLIを使うことをオススメします。
parameters/
配下に変数ファイルを準備していますので、各自の環境に合わせて適宜設定してください。
リソース名はプレフィックスをSystemName-Environmentで統一しています。
デフォルトでは、SystemName=system, Environment=devとしています。
次章からはCFnテンプレートを確認します。
CFnテンプレートは折りたたんで記載してありますので、ご注意ください
VPC構築
VPCスタック作成
次のCFnテンプレートを使ってスタックを作成し、VPCを構築します。
(VPCはRollingアップデート、Blue/Greenデプロイメント共通です)
vpc.yaml(クリックで展開)
vpc.yaml
AWSTemplateFormatVersion: 2010-09-09
Description:
VPC Create
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "System Configuration"
Parameters:
- Environment
- SystemtName
- Label:
default: "Netowork Configuration"
Parameters:
- VPCCIDR
- AvailabilityZone1
- AvailabilityZone2
- PublicSubnet1CIDR
- PublicSubnet2CIDR
- PrivateSubnet1CIDR
- PrivateSubnet2CIDR
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
Environment:
Default: dev
Type: String
SystemName:
Default: system
Type: String
VPCCIDR:
Type: String
Default: "10.10.0.0/16"
AvailabilityZone1:
Type: String
Default: "ap-northeast-1a"
AvailabilityZone2:
Type: String
Default: "ap-northeast-1c"
PublicSubnet1CIDR:
Type: String
Default: "10.10.0.0/24"
PublicSubnet2CIDR:
Type: String
Default: "10.10.1.0/24"
PrivateSubnet1CIDR:
Type: String
Default: "10.10.2.0/24"
PrivateSubnet2CIDR:
Type: String
Default: "10.10.3.0/24"
Resources:
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------#
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-vpc"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# Internet Gateway
# ------------------------------------------------------------#
IGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-igw"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref IGW
# ------------------------------------------------------------#
# NAT Gateway
# ------------------------------------------------------------#
NGW1:
Type: "AWS::EC2::NatGateway"
Properties:
AllocationId: !GetAtt NGW1EIP.AllocationId
SubnetId: !Ref PublicSubnet1
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-ngw-1"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
NGW1EIP:
Type: "AWS::EC2::EIP"
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-nat-eip-1"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
NGW2:
Type: "AWS::EC2::NatGateway"
Properties:
AllocationId: !GetAtt NGW2EIP.AllocationId
SubnetId: !Ref PublicSubnet2
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-ngw-2"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
NGW2EIP:
Type: "AWS::EC2::EIP"
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-nat-eip-2"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# PublicSubnet
# ------------------------------------------------------------#
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PublicSubnet1CIDR
AvailabilityZone: !Ref AvailabilityZone1
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-public-subnet-1"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PublicSubnet2CIDR
AvailabilityZone: !Ref AvailabilityZone2
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-public-subnet-2"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# Public RouteTable
# ------------------------------------------------------------#
PublicRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-public-rt-1"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
PublicRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-public-rt-2"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# Public Routing
# ------------------------------------------------------------#
PublicRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable1
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref IGW
PublicRoute2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable2
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref IGW
# ------------------------------------------------------------#
# Public RouteTable Association
# ------------------------------------------------------------#
PublicSubnetAttach1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable1
SubnetId: !Ref PublicSubnet1
PublicSubnetAttach2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable2
SubnetId: !Ref PublicSubnet2
# ------------------------------------------------------------#
# PrivateSubnet
# ------------------------------------------------------------#
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PrivateSubnet1CIDR
AvailabilityZone: !Ref AvailabilityZone1
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-private-subnet-1"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PrivateSubnet2CIDR
AvailabilityZone: !Ref AvailabilityZone2
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-private-subnet-2"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# Private RouteTable
# ------------------------------------------------------------#
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-private-rt-1"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
PrivateRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-private-rt-2"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# Private Routing
# ------------------------------------------------------------#
PrivateRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable1
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NGW1
PrivateRoute2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable2
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NGW2
# ------------------------------------------------------------#
# Private RouteTable Association
# ------------------------------------------------------------#
PrivateSubnetAttach1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnet1
PrivateSubnetAttach2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable2
SubnetId: !Ref PrivateSubnet2
VPC構築のポイント
- private subnetからはNAT Gatewayを通じてインターネット経由でECS・ECRにアクセスする想定
- ビルドやECSタスクの更新を頻繁に行う環境であれば、NAT Gatewayの転送料金を防ぐためにVPCエンドポイント経由とするなどの対策が必要
- 参考:そのトラフィック、NATゲートウェイを通す必要ありますか?適切な経路で不要なデータ処理料金は削減しましょう
ECR構築
Blue/Greenデプロイメント用ECRスタック作成
次のCFnテンプレートを使ってスタックを作成し、Blue/Greenデプロイメント用のECRを構築します。
ecr-blue-green.yaml(クリックで展開)
ecr-blue-green.yaml
AWSTemplateFormatVersion: 2010-09-09
Description:
ECR Create
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "System Configuration"
Parameters:
- Environment
- SystemtName
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
Environment:
Default: dev
Type: String
SystemName:
Default: system
Type: String
Resources:
# ------------------------------------------------------------#
# ECR
# ------------------------------------------------------------#
BlueGreenECR:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Sub "${SystemName}-${Environment}-blgr-repo"
EncryptionConfiguration:
EncryptionType: "AES256"
ImageScanningConfiguration:
ScanOnPush: true
ImageTagMutability: IMMUTABLE
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-repo"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
ECR構築のポイント
- リポジトリはSSE-S3(AES256)によるサーバー側暗号化を実施
- push時にイメージスキャンを行う
- タグはIMMUTABLEとする
ECS構築
Blue/Greenアップデート用ECS
次のCFnテンプレートを使ってスタックを作成し、Blue/Greenアップデート用のECSを構築します。
ecs-blue-green.yaml(クリックで展開)
ecs-blue-green.yaml
AWSTemplateFormatVersion: 2010-09-09
Description:
ECS and ALB Create
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "System Configuration"
Parameters:
- Environment
- SystemtName
- Label:
default: "Netowork Configuration"
Parameters:
- VpcId
- ALBSubnetId1
- ALBSubnetId2
- ECSSubnetId1
- ECSSubnetId2
- ALBAllowInboundIP
- Label:
default: "Fargate Configuration"
Parameters:
- ECSImage
- ECSTaskCPUUnit
- ECSTaskMemory
- ECSTaskDesiredCount
- Label:
default: "Scaling Configuration"
Parameters:
- ServiceScaleCpuTarget
- ServiceScaleInCooldown
- ServiceScaleOutCooldown
- TaskMinContainerCount
- TaskMaxContainerCount
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
Environment:
Default: dev
Type: String
SystemName:
Default: system
Type: String
#VPCID
VpcId:
Description : "VPC ID"
Type: AWS::EC2::VPC::Id
Default: "vpc-"
#ALBSubnet1
ALBSubnetId1:
Description : "ALB Subnet 1st"
Type : AWS::EC2::Subnet::Id
Default: "subnet-public1Id"
#ALBSubnet2
ALBSubnetId2:
Description : "ALB Subnet 2nd"
Type : AWS::EC2::Subnet::Id
Default: "subnet-public2Id"
#ALBAllowInboundIpAddress
ALBAllowInboundIP:
Description : "ALB Subnet 2nd"
Type: String
Default: "xxx.xxx.xxx.xxx/32"
#ECSSubnet1
ECSSubnetId1:
Description : "ECS Subnet 1st"
Type : AWS::EC2::Subnet::Id
Default: "subnet-private1Id"
#ECSSubnet2
ECSSubnetId2:
Description : "ECS Subnet 2nd"
Type : AWS::EC2::Subnet::Id
Default: "subnet-private2Id"
#ECSTaskCPUUnit
ECSTaskCPUUnit:
Type: String
Default: "256"
#ECSTaskMemory
ECSTaskMemory:
Type: String
Default: "512"
#ECSImage
ECSImage:
Type: String
Default: "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/system-dev-blgr-repo:latest"
#ECSTaskDesiredCount
ECSTaskDesiredCount:
Type: Number
Default: 1
# Scaling params
ServiceScaleCpuTarget:
Description: Target Tracking Scaling CPU Target
Type: Number
Default: 70
ServiceScaleInCooldown:
Type: Number
Description: Target Tracking Scale In Cooldown seconds
Default: 180
ServiceScaleOutCooldown:
Type: Number
Description: Target Tracking Scale Out Cooldown seconds
Default: 60
TaskMinContainerCount:
Type: Number
Description: Minimum number of containers to run for the service
Default: 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
ConstraintDescription: Value must be at least one
Resources:
# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------#
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ALB Security Group
GroupName: !Sub "${SystemName}-${Environment}-blgr-alb-sg"
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: !Ref ALBAllowInboundIP
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
CidrIp: !Ref ALBAllowInboundIP
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-alb-sg"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
TaskSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Task Security Group
GroupName: !Sub "${SystemName}-${Environment}-blgr-task-sg"
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ALBSecurityGroup
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-task-sg"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# ALB
# ------------------------------------------------------------#
ALB:
Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
Properties:
Name: !Sub "${SystemName}-${Environment}-blgr-alb"
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 "${SystemName}-${Environment}-blgr-alb-log-bucket-${AWS::AccountId}"
Scheme: "internet-facing"
SecurityGroups:
- !Ref ALBSecurityGroup
Subnets:
- !Ref ALBSubnetId1
- !Ref ALBSubnetId2
Type: "application"
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-alb"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
ALBListenerBlue:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroupBlue
Type: forward
LoadBalancerArn: !Ref ALB
Port: 80
Protocol: HTTP
ALBListenerGreen:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroupGreen
Type: forward
LoadBalancerArn: !Ref ALB
Port: 8080
Protocol: HTTP
TargetGroupBlue:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
HealthCheckPath: /
VpcId: !Ref VpcId
Name: !Sub "${SystemName}-${Environment}-blgr-tg-blue"
Protocol: HTTP
Port: 80
TargetType: ip
TargetGroupGreen:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
HealthCheckPath: /
VpcId: !Ref VpcId
Name: !Sub "${SystemName}-${Environment}-blgr-tg-green"
Protocol: HTTP
Port: 80
TargetType: ip
# ------------------------------------------------------------#
# ALB Log S3 Bucket
# ------------------------------------------------------------#
ALBLogBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${SystemName}-${Environment}-blgr-alb-log-bucket-${AWS::AccountId}"
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: "AES256"
BucketKeyEnabled: true
PublicAccessBlockConfiguration:
BlockPublicAcls: TRUE
BlockPublicPolicy: TRUE
IgnorePublicAcls: TRUE
RestrictPublicBuckets: TRUE
LifecycleConfiguration:
Rules:
- Id: !Sub "${SystemName}-${Environment}-blgr-alb-log-lifecycle"
Status: Enabled
ExpirationInDays: 400
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-alb-log-bucket-${AWS::AccountId}"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
logsBucketPolicy:
Type: AWS::S3::BucketPolicy
DependsOn: ALBLogBucket
Properties:
Bucket: !Sub "${SystemName}-${Environment}-blgr-alb-log-bucket-${AWS::AccountId}"
PolicyDocument:
Statement:
- Action:
- 's3:PutObject'
Effect: 'Allow'
Resource:
- Fn::Join:
- ''
- - "arn:aws:s3:::"
- !Sub "${SystemName}-${Environment}-blgr-alb-log-bucket-${AWS::AccountId}"
- "/*"
Principal:
AWS: '582318560864'
- Action:
- 's3:GetBucketAcl'
Effect: 'Allow'
Resource:
Fn::Join:
- ''
- - "arn:aws:s3:::"
- !Sub "${SystemName}-${Environment}-blgr-alb-log-bucket-${AWS::AccountId}"
Principal:
Service: 'logdelivery.elb.amazonaws.com'
# ------------------------------------------------------------#
# ECS Cluster
# ------------------------------------------------------------#
ECSCluster:
Type: "AWS::ECS::Cluster"
Properties:
ClusterName: !Sub "${SystemName}-${Environment}-blgr-cluster"
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-cluster"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# ECS LogGroup
# ------------------------------------------------------------#
ECSLogGroup:
Type: "AWS::Logs::LogGroup"
Properties:
LogGroupName: !Sub "${SystemName}-${Environment}-blgr-cluster-log"
# ------------------------------------------------------------#
# ECS Task Execution Role
# ------------------------------------------------------------#
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${SystemName}-${Environment}-blgr-task-execution-role"
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
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-task-execution-role"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# ECS TaskDefinition
# ------------------------------------------------------------#
ECSTaskDefinition:
Type: "AWS::ECS::TaskDefinition"
Properties:
Cpu: !Ref ECSTaskCPUUnit
ExecutionRoleArn: !Ref ECSTaskExecutionRole
Family: !Sub "${SystemName}-${Environment}-blgr-task-definition"
Memory: !Ref ECSTaskMemory
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ContainerDefinitions:
- Name: app-container
Image: !Ref ECSImage
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref ECSLogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: !Ref SystemName
MemoryReservation: 128
PortMappings:
- HostPort: 80
Protocol: tcp
ContainerPort: 80
ReadonlyRootFilesystem: false
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-task-definition"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# ECS Service
# ------------------------------------------------------------#
ECSService:
Type: AWS::ECS::Service
DependsOn: ALBListenerBlue
Properties:
Cluster: !Ref ECSCluster
DesiredCount: !Ref ECSTaskDesiredCount
DeploymentController:
Type: CODE_DEPLOY
LaunchType: FARGATE
LoadBalancers:
-
TargetGroupArn: !Ref TargetGroupBlue
ContainerPort: 80
ContainerName: !Sub "app-container"
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: DISABLED
SecurityGroups:
- !Ref TaskSecurityGroup
Subnets:
- !Ref ECSSubnetId1
- !Ref ECSSubnetId2
PlatformVersion: 1.4.0
ServiceName: !Sub "${SystemName}-${Environment}-blgr-service"
TaskDefinition: !Ref ECSTaskDefinition
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-service"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# Auto Scaling Service
# ------------------------------------------------------------#
ServiceAutoScalingRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${SystemName}-${Environment}-blgr-autoscaling-role"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: application-autoscaling.amazonaws.com
Action: sts:AssumeRole
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-autoscaling-role"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
ServiceAutoScalingPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub "${SystemName}-${Environment}-blgr-autoscaling-policy"
PolicyDocument:
Statement:
- Effect: Allow
Action:
- application-autoscaling:*
- cloudwatch:DescribeAlarms
- cloudwatch:PutMetricAlarm
- ecs:DescribeServices
- ecs:UpdateService
Resource: '*'
Roles:
- !Ref ServiceAutoScalingRole
ServiceScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MinCapacity: !Ref TaskMinContainerCount
MaxCapacity: !Ref TaskMaxContainerCount
ResourceId:
!Join
- '/'
- - 'service'
- !Ref ECSCluster
- !GetAtt ECSService.Name
RoleARN: !GetAtt ServiceAutoScalingRole.Arn
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
DependsOn:
- ECSService
- ServiceAutoScalingRole
ServiceScalingPolicyCPU:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub "${SystemName}-${Environment}-blgr-target-tracking-scaling-cpu"
PolicyType: TargetTrackingScaling
ScalingTargetId: !Ref ServiceScalingTarget
TargetTrackingScalingPolicyConfiguration:
TargetValue: !Ref ServiceScaleCpuTarget
ScaleInCooldown: !Ref ServiceScaleInCooldown
ScaleOutCooldown: !Ref ServiceScaleOutCooldown
PredefinedMetricSpecification:
PredefinedMetricType: ECSServiceAverageCPUUtilization
DependsOn: ServiceScalingTarget
ECS構築のポイント
- ALBは特定のソースIPからのみアクセス可能な想定
- 変数ALBAllowInboundIPでセキュリティグループのインバウンドルールで許可するIPアドレスを指定
- ALBのアクセスログはS3に出力
- SSE-S3(AES256)で暗号化し、400日のライフサイクルを設定
- Blue/Greenデプロイメントではテストポートに8080を設定
- ECSサービスのAutoScalingはCPUの追跡ポリシーを設定
- 閾値は変数で設定可能。変数値のデフォルトには70%を設定
- Blue/Greenデプロイメントでは状況によってデプロイ失敗となるので注意
- 参考:CodeDeployによるBlue/Greenデプロイ
CI/CD環境構築
Blue/Greenデプロイメント用CI/CDスタック作成
次のCFnテンプレートを使ってスタックを作成し、Blue/Greenアップデート用のCI/CD環境を構築します。
cicd-blue-green.yaml(クリックで展開)
cicd-blue-green.yaml
AWSTemplateFormatVersion: 2010-09-09
Description:
Blue Green Update For ECS Fargate with GitHub
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "System Configuration"
Parameters:
- Environment
- SystemtName
- Label:
default: "VPC Configuration"
Parameters:
- VpcId
- CodeBuildSubnetId1
- CodeBuildSubnetId2
- Label:
default: "ECR・ECS Configuration"
Parameters:
- ECSClusterName
- ECSServiceName
- ECSTaskContainerName
- ECRName
- Label:
default: "GitHub Configuration"
Parameters:
- GitHubOrganizationName
- GitHubRepositoryName
- GitHubBranchName
- Label:
default: "Blue/Green Configuration"
Parameters:
- TargetGroupBlueName
- TargetGroupGreenName
- ListenerBlueArn
- ListenerGreenArn
Parameters:
Environment:
Default: dev
Type: String
SystemName:
Default: system
Type: String
ECSClusterName:
Default: system-dev-blgr-cluster
Description : "CI/CD Deploy ECS Cluster"
Type: String
ECSServiceName:
Default: system-dev-blgr-service
Description : "CI/CD Deploy ECS Service"
Type: String
ECSTaskContainerName:
Default: app-container
Description : "CI/CD Deploy ECS Task Container"
Type: String
ECRName:
Default: system-dev-blgr-repo
Description : "ECR Repository Name"
Type: String
GitHubOrganizationName:
Default: GitHubOrganizationName
Description : "CI/CD GitHub Organization"
Type: String
GitHubRepositoryName:
Default: GitHubRepositoryName
Description : "CI/CD GitHub Repository"
Type: String
GitHubBranchName:
Default: GitHubBranchName
Description : "CI/CD GitHub Branch"
Type: String
VpcId:
Default: vpc-
Description : "VPC ID"
Type: AWS::EC2::VPC::Id
CodeBuildSubnetId1:
Default: subnet-private1Id
Description : "Private Subnet 1st"
Type: AWS::EC2::Subnet::Id
CodeBuildSubnetId2:
Default: subnet-private2Id
Description : "Private Subnet 2nd"
Type: AWS::EC2::Subnet::Id
TargetGroupBlueName:
Default: system-dev-blgr-tg-blue
Description : "Target Group Blue Name"
Type: String
TargetGroupGreenName:
Default: system-dev-blgr-tg-green
Description : "Target Group Green Name"
Type: String
ListenerBlueArn:
Default: ListenerBlueArn
Description : "Listener Blue Arn"
Type: String
ListenerGreenArn:
Default: ListenerGreenArn
Description : "Listener Green Arn"
Type: String
Resources:
# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------#
CodeBuildSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Blue Green Deploy CodeBuild Security Group
GroupName: !Sub "${SystemName}-${Environment}-blgr-code-build-sg"
VpcId: !Ref VpcId
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-code-build-sg"
- Key: Systemname
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# CICD Role
# ------------------------------------------------------------#
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${SystemName}-${Environment}-blgr-cicd-build-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-cicd-build-role"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
CodeBuildServicePolicy:
DependsOn: CodePipelineServiceRole
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub "${SystemName}-${Environment}-blgr-cicd-build-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Resource: "*"
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- Resource: !Sub arn:aws:s3:::${BLGRArtifactBucket}/*
Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:GetObjectVersion
- s3:GetBucketAcl
- s3:GetBucketLocation
- Resource: "*"
Effect: Allow
Action:
- ecr:GetAuthorizationToken
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:GetRepositoryPolicy
- ecr:DescribeRepositories
- ecr:ListImages
- ecr:DescribeImages
- ecr:BatchGetImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
- ecr:PutImage
- Resource: "*"
Effect: Allow
Action:
- ec2:CreateNetworkInterface
- ec2:DescribeDhcpOptions
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
- ec2:DescribeSubnets
- ec2:DescribeSecurityGroups
- ec2:DescribeVpcs
- Resource: "*"
Effect: Allow
Action:
- ec2:CreateNetworkInterface
- ec2:DescribeDhcpOptions
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
- ec2:DescribeSubnets
- ec2:DescribeSecurityGroups
- ec2:DescribeVpcs
- ec2:CreateNetworkInterfacePermission
- Resource: "*"
Effect: Allow
Action:
- codestar-connections:UseConnection
Roles:
- !Ref CodeBuildServiceRole
CodeDeployServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${SystemName}-${Environment}-blgr-cicd-deploy-role"
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: codedeploy.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${SystemName}-${Environment}-blgr-cicd-pipeline-role"
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-cicd-build-role"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
CodePipelineServicePolicy:
Type: AWS::IAM::Policy
DependsOn: CodePipelineServiceRole
Properties:
PolicyName: !Sub "${SystemName}-${Environment}-blgr-cicd-pipeline-policy"
PolicyDocument:
Version: 2012-10-17
Statement:
- Resource:
- !Sub arn:aws:s3:::${BLGRArtifactBucket}/*
Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketVersioning
- Resource: "*"
Effect: Allow
Action:
- codecommit:GetRepository
- codecommit:ListBranches
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
- codecommit:CancelUploadArchive
- codedeploy:CreateDeployment
- codedeploy:GetApplication
- codedeploy:GetApplicationRevision
- codedeploy:GetDeployment
- codedeploy:GetDeploymentConfig
- codedeploy:RegisterApplicationRevision
- codebuild:StartBuild
- codebuild:StopBuild
- codebuild:BatchGet*
- codebuild:Get*
- codebuild:List*
- codecommit:GetBranch
- codecommit:GetCommit
- s3:*
- ecs:*
- elasticloadbalancing:*
- autoscaling:*
- iam:PassRole
- codestar-connections:UseConnection
Roles:
- !Ref CodePipelineServiceRole
# ------------------------------------------------------------#
# IAMRole For CustomResource Lambda
# ------------------------------------------------------------#
LambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${SystemName}-${Environment}-blgr-deploy-groupe-lambda-role"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/AWSCodeDeployFullAccess
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-deploy-groupe-lambda-role"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
LambdaPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub "${SystemName}-${Environment}-blgr-cicd-build-policy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:*
- logs:*
Resource: '*'
- Effect: Allow
Resource: '*'
Action:
- iam:PassRole
Condition:
StringEqualsIfExists:
iam:PassedToService:
- codedeploy.amazonaws.com
Roles:
- !Ref LambdaRole
# ------------------------------------------------------------#
# Blue Green Artifact S3 Bucket
# ------------------------------------------------------------#
BLGRArtifactBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${SystemName}-${Environment}-blgr-cicd-artifact-${AWS::AccountId}"
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
BucketKeyEnabled: true
PublicAccessBlockConfiguration:
BlockPublicAcls: TRUE
BlockPublicPolicy: TRUE
IgnorePublicAcls: TRUE
RestrictPublicBuckets: TRUE
LifecycleConfiguration:
Rules:
- Id: !Sub "${SystemName}-${Environment}-blgr-cicd-build-artifact-lifecycle"
Status: Enabled
ExpirationInDays: 1827
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-cicd-artifact-${AWS::AccountId}"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# GitHub Connection
# ------------------------------------------------------------#
GitHubConnection:
Type: AWS::CodeStarConnections::Connection
Properties:
ConnectionName: !Sub "${SystemName}-${Environment}-blgr-cicd-github"
ProviderType: GitHub
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-cicd-github"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# Code Build LogGroup
# ------------------------------------------------------------#
CodeBuildLogGroup:
Type: "AWS::Logs::LogGroup"
Properties:
LogGroupName: !Sub "${SystemName}-${Environment}-blgr-cicd-code-build"
# ------------------------------------------------------------#
# Code Build
# ------------------------------------------------------------#
CodeBuildProject:
Type: AWS::CodeBuild::Project
DependsOn: CodeBuildServicePolicy
Properties:
Name: !Sub "${SystemName}-${Environment}-blgr-cicd-code-build"
Environment:
PrivilegedMode: true
ComputeType: BUILD_GENERAL1_SMALL
Image: "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
Type: LINUX_CONTAINER
EnvironmentVariables:
- Name: AWS_REGION
Value: !Ref AWS::Region
- Name: REPOSITORY_URI
Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRName}
- Name: ECSTaskContainerName
Value: !Ref ECSTaskContainerName
Artifacts:
Type: CODEPIPELINE
Source:
Type: CODEPIPELINE
BuildSpec: |
version: 0.2
env:
variables:
DOCKER_BUILDKIT: "1"
phases:
install:
runtime-versions:
docker: 19
pre_build:
commands:
- AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
- echo Logging in to ECR
- aws ecr --region ${AWS_REGION} get-login-password | docker login --username AWS --password-stdin https://${REPOSITORY_URI}
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- IMAGE_TAG=${COMMIT_HASH:=latest}
build:
commands:
- echo Build started on `date`
- docker image build -t ${REPOSITORY_URI}:${COMMIT_HASH} .
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker images
- docker image push ${REPOSITORY_URI}:${COMMIT_HASH}
- printf '{"ImageURI":"%s"}' ${REPOSITORY_URI}:${COMMIT_HASH} > imageDetail.json
artifacts:
files: imageDetail.json
ServiceRole: !Ref CodeBuildServiceRole
Cache:
Modes:
- "LOCAL_DOCKER_LAYER_CACHE"
- "LOCAL_SOURCE_CACHE"
Type: "LOCAL"
LogsConfig:
CloudWatchLogs:
Status: "ENABLED"
GroupName: !Ref CodeBuildLogGroup
Visibility: "PRIVATE"
TimeoutInMinutes: 60
QueuedTimeoutInMinutes: 480
VpcConfig:
SecurityGroupIds:
- !Ref CodeBuildSecurityGroup
Subnets:
- !Ref CodeBuildSubnetId1
- !Ref CodeBuildSubnetId2
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-cicd-build"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
# ------------------------------------------------------------#
# Lambda for Create Blue Green Deployment Group
# ------------------------------------------------------------#
LambdaFunction:
Type: 'AWS::Lambda::Function'
DeletionPolicy: 'Delete'
Properties:
Code:
ZipFile: |
import boto3
import json
import logging
import cfnresponse
from botocore.exceptions import ClientError
logger = logging.getLogger()
logger.setLevel(logging.INFO)
client = boto3.client('codedeploy')
def lambda_handler(event, context):
appName = event['ResourceProperties']['appName']
deploymentGroup = event['ResourceProperties']['deploymentGroup']
clusterName = event['ResourceProperties']['ECSClusterName']
serviceName = event['ResourceProperties']['ECSServiceName']
print('REQUEST RECEIVED:\n' + json.dumps(event))
responseData = {}
try:
res = client.create_application(
applicationName=appName,
computePlatform='ECS'
)
logger.info(res)
logger.info("SUCCESS: CodeDeploy Application created.")
res = client.create_deployment_group(
applicationName=appName,
deploymentGroupName=deploymentGroup,
deploymentConfigName='CodeDeployDefault.ECSAllAtOnce',
serviceRoleArn=event['ResourceProperties']['CodeDeployServiceRoleArn'],
autoRollbackConfiguration={
'enabled': True,
'events': [
'DEPLOYMENT_FAILURE',
]
},
deploymentStyle={
'deploymentType': 'BLUE_GREEN',
'deploymentOption': 'WITH_TRAFFIC_CONTROL'
},
blueGreenDeploymentConfiguration={
'terminateBlueInstancesOnDeploymentSuccess': {
'action': 'TERMINATE',
'terminationWaitTimeInMinutes': 5
},
'deploymentReadyOption': {
'actionOnTimeout': 'STOP_DEPLOYMENT',
'waitTimeInMinutes': 5
}
},
loadBalancerInfo={
'targetGroupPairInfoList': [
{
'targetGroups': [
{
'name': event['ResourceProperties']['TargetGroup1']
},
{
'name': event['ResourceProperties']['TargetGroup2']
},
],
'prodTrafficRoute': {
'listenerArns': [
event['ResourceProperties']['ALBListener1'],
]
},
'testTrafficRoute': {
'listenerArns': [
event['ResourceProperties']['ALBListener2'],
]
}
},
]
},
ecsServices=[
{
'serviceName': event['ResourceProperties']['ECSServiceName'],
'clusterName': event['ResourceProperties']['ECSClusterName']
},
]
)
except ClientError as e:
logger.error("ERROR: Something error!")
logger.error(e)
responseData = {'error': str(e)}
cfnresponse.send(event, context, cfnresponse.FAILED, responseData)
else:
logger.info(res)
logger.info(
"SUCCESS: CodeDeploy Application and DeploymentGroup created.")
return cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
Handler: index.lambda_handler
Role: !GetAtt LambdaRole.Arn
Runtime: python3.8
Timeout: 10
# ------------------------------------------------------------#
# Custom Resource
# ------------------------------------------------------------#
CreateCodeDeploy:
Type: Custom::CreateCodeDeploy
DependsOn:
- CodeDeployServiceRole
Properties:
ServiceToken: !GetAtt LambdaFunction.Arn
Region: !Ref AWS::Region
ECSClusterName: !Ref ECSClusterName
ECSServiceName: !Ref ECSServiceName
CodeDeployServiceRoleArn: !GetAtt CodeDeployServiceRole.Arn
TargetGroup1: !Ref TargetGroupBlueName
TargetGroup2: !Ref TargetGroupGreenName
ALBListener1: !Ref ListenerBlueArn
ALBListener2: !Ref ListenerGreenArn
appName: !Sub "${SystemName}-${Environment}-blgr-app"
deploymentGroup: !Sub "${SystemName}-${Environment}-blgr-deployment-group"
# ------------------------------------------------------------#
# Code Pipeline
# ------------------------------------------------------------#
Pipeline:
Type: AWS::CodePipeline::Pipeline
DependsOn: CodePipelineServicePolicy
Properties:
RoleArn: !GetAtt CodePipelineServiceRole.Arn
Name: !Sub "${SystemName}-${Environment}-blgr-cicd-pipeline"
ArtifactStore:
Type: S3
Location: !Ref BLGRArtifactBucket
Stages:
- Name: Source
Actions:
- Name: Source
ActionTypeId:
Category: Source
Owner: AWS
Version: 1
Provider: CodeStarSourceConnection
Configuration:
FullRepositoryId: !Sub
- ${GitHubOrganizationName}/${GitHubRepositoryName}
- GitHubOrganizationName: !Ref GitHubOrganizationName
GitHubRepositoryName: !Ref GitHubRepositoryName
ConnectionArn: !Ref GitHubConnection
BranchName: !Ref GitHubBranchName
RunOrder: 1
OutputArtifacts:
- Name: SourceArtifact
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
Configuration:
ProjectName: !Ref CodeBuildProject
RunOrder: 1
InputArtifacts:
- Name: SourceArtifact
OutputArtifacts:
- Name: BuildArtifact
- Name: Deploy
Actions:
- Name: Deploy
ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: CodeDeployToECS
Configuration:
AppSpecTemplateArtifact: SourceArtifact
AppSpecTemplatePath: appspec.yaml
TaskDefinitionTemplateArtifact: SourceArtifact
TaskDefinitionTemplatePath: !Sub "${SystemName}-${Environment}-taskdef.json"
ApplicationName: !Sub "${SystemName}-${Environment}-blgr-app"
DeploymentGroupName: !Sub "${SystemName}-${Environment}-blgr-deployment-group"
Image1ArtifactName: BuildArtifact
Image1ContainerName: IMAGE1_NAME
RunOrder: 1
InputArtifacts:
- Name: SourceArtifact
- Name: BuildArtifact
Region: !Ref AWS::Region
Tags:
- Key: Name
Value: !Sub "${SystemName}-${Environment}-blgr-cicd-pipeline"
- Key: SystemName
Value: !Sub "${SystemName}"
- Key: Environment
Value: !Sub "${Environment}"
CI/CD環境構築のポイント
- ソースアクションにはGitHubバージョン2を指定
- このため、初回のパイプラインは必ず失敗
- コンソール画面での接続設定後、ソースアクションの実行が可能になる
- アーティファクトはS3に出力
- SSE-S3(AES256)で暗号化し、400日のライフサイクルを設定
- CodeDeploy ECS Blue/Greenデプロイメントグループの作成はLambdaカスタムリソースで実施
- 現時点(2022/8/31)ではCloudFormationで直接作成することはできないため
- 参考:CloudFormation 一撃で Fargate の Blue/Green Deployment 環境を構築する
- CodeDeproyのパラメーターは下記を設定
- デプロイメントコンフィグ:ECSAllAtOnce
- デプロイ失敗時:ロールバック
- トラフィックの再ルーティングはルーティング準備ができてから5分以内に実施
- 再ルーティング後、5分後に以前のタスクは終了
動作確認
それでは構築の動作確認をします。
CFn構築
下記の順番に実行します。
- vpc.yaml
- ecr-blue-green.yaml
- 構築後、latestタグを付与したイメージをpushすること
- ecs-blue-green.yaml
- vpcの値に合わせてパラメータファイルを更新すること
- cicd-blue-green.yaml
- vpc・ecsの値に合わせてパラメータファイルを更新すること
AWS CLIで下記のコマンドを実行することで、スタックを構築します。
aws cloudformation create-stack \
--stack-name スタック名 \
--template-body file://./テンプレート名 \
--capabilities CAPABILITY_NAMED_IAM \
--parameters file://./parameters/パラメータ名 \
--tags Key=Environment,Value=<Environmentに設定する変数値> \
--tags Key=SystemName,Value=<SystemNameに設定する変数値>
GitHubのリポジトリ構築
BlueGreenデプロイ用のリポジトリを作成し、サンプルソースのGitHub/
フォルダの内容をアップロードします。
なお、アップロードの際は下記に注意してください。
- ソースとするGitHubのルートディレクトリに下記ファイルが格納されていること。
- appspec.yaml
- SystemName-Encironment-taskdef.json
- タスクの実行roleを構築したものに変更すること
- サンプルソースではsystem-dev-taskdef.jsonを用意
AWSとGitHubとの接続
作成したCI/CDパイプラインはGitHubとの接続ができずに初回実行は失敗しています。
コンソール画面からGitHubとの接続の設定を行います。
新しいアプリをインストールする、をクリックします。
GitHubの画面でさきほど作成したリポジトリを選択してSaveします
接続のステータスが利用可能になっていることを確認します。
Blue/Greenデプロイメント実行
index.htmlを次のように更新し、GitHubのmainブランチにpushします。
index.html(クリックで展開)
index.html
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>testページ</title>
</head>
<body>
<h1>Blue/Green Deployment Demo</h1>
</body>
</html>
pushを契機としてパイプラインが起動されます。
しばらく待つと、ビルドも完了します。
ECRを確認すると、さきほどpushしたGitHubのコミットIDをタグとする新たなイメージが作成されています。
CodeDeployの画面では、ステップ1から順にデプロイが行われていきます。
しばらく待つとステップが進み、ステップ3の待機状態となります。
この状態でALBのテストポート(8080)にアクセスすると、デプロイ後の画面確認ができます。
本来のポート(80)では以前のページに引き続きアクセスできます。
この状態でCodeDeployの画面から再ルーティングを行います。
なお、5分間再ルーティングをしないと、ロールバックする設定になっていますので、ご注意ください。
再ルーティングを行った後に本来のポートにアクセスすると、デプロイ後の画面が確認できます。
ALBのターゲットグループを確認すると、Blue側のターゲットグループはunuedとなり、アクセスがなくなっています。
この時点では、古いタスクも起動中のまま残っています。
何か異常があった際には、このタスクに切り戻すことも可能です。
問題なければ、元のタスクセットの終了をクリックします。
デプロイが成功となり、古いタスクが終了されます。
作成リソースの削除
作成時とは逆の順番で、CloudFormationスタックを削除し、リソースを削除してください。
ただしCodeDeployアプリケーションはカスタムリソースで作成しましたので、コンソールから削除してください。
最後に
ECS Fargateのデプロイについて、Blue/Greenデプロイメント環境をCFnで構築する方法を紹介しました。
試したことのない方はぜひ一度やってみてください!
本ブログがどなたかのお役に立てば幸いです。