CloudFormation CodeDeploy 블루/그린 배포 템플릿을 Amazon ECS 블루/그린 CloudFormation 템플릿으로 마이그레이션하기
CloudFormation CodeDeploy 블루/그린 배포 템플릿을 Amazon ECS 블루/그린 CloudFormation 템플릿으로 마이그레이션하기
Amazon ECS 서비스에 CodeDeploy 블루/그린 배포를 사용하는 CloudFormation 템플릿을 네이티브 Amazon ECS 블루/그린 배포 전략을 사용하는 템플릿으로 마이그레이션해요. 마이그레이션은 "CodeDeploy에 사용된 것과 동일한 Elastic Load Balancing 리소스 재사용" 접근 방식을 따릅니다.
출처: 문서
본문
자세한 내용은 Migrate CodeDeploy blue/green deployments to Amazon ECS blue/green deployments 를 참고해 주세요.
소스 템플릿
이 템플릿은 AWS::CodeDeployBlueGreen transform과 AWS::CodeDeploy::BlueGreen 후크를 사용해 Amazon ECS 서비스에 대한 블루/그린 배포를 구현해요.
다음은 CodeDeploy 블루/그린 배포를 사용하는 완전한 CloudFormation 템플릿이에요. 자세한 내용은 AWS CloudFormation User Guide 의 Blue/green deployment template example 을 참고해 주세요.
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
"Vpc": {
"Type": "AWS::EC2::VPC::Id"
},
"Subnet1": {
"Type": "AWS::EC2::Subnet::Id"
},
"Subnet2": {
"Type": "AWS::EC2::Subnet::Id"
}
},
"Transform": [
"AWS::CodeDeployBlueGreen"
],
"Hooks": {
"CodeDeployBlueGreenHook": {
"Type": "AWS::CodeDeploy::BlueGreen",
"Properties": {
"TrafficRoutingConfig": {
"Type": "TimeBasedCanary",
"TimeBasedCanary": {
"StepPercentage": 15,
"BakeTimeMins": 5
}
},
"Applications": [
{
"Target": {
"Type": "AWS::ECS::Service",
"LogicalID": "ECSDemoService"
},
"ECSAttributes": {
"TaskDefinitions": [
"BlueTaskDefinition",
"GreenTaskDefinition"
],
"TaskSets": [
"BlueTaskSet",
"GreenTaskSet"
],
"TrafficRouting": {
"ProdTrafficRoute": {
"Type": "AWS::ElasticLoadBalancingV2::Listener",
"LogicalID": "ALBListenerProdTraffic"
},
"TargetGroups": [
"ALBTargetGroupBlue",
"ALBTargetGroupGreen"
]
}
}
}
]
}
}
},
"Resources": {
"ExampleSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Security group for ec2 access",
"VpcId": {"Ref": "Vpc"},
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": 80,
"ToPort": 80,
"CidrIp": "0.0.0.0/0"
},
{
"IpProtocol": "tcp",
"FromPort": 8080,
"ToPort": 8080,
"CidrIp": "0.0.0.0/0"
},
{
"IpProtocol": "tcp",
"FromPort": 22,
"ToPort": 22,
"CidrIp": "0.0.0.0/0"
}
]
}
},
"ALBTargetGroupBlue": {
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
"Properties": {
"HealthCheckIntervalSeconds": 5,
"HealthCheckPath": "/",
"HealthCheckPort": "80",
"HealthCheckProtocol": "HTTP",
"HealthCheckTimeoutSeconds": 2,
"HealthyThresholdCount": 2,
"Matcher": {
"HttpCode": "200"
},
"Port": 80,
"Protocol": "HTTP",
"Tags": [
{
"Key": "Group",
"Value": "Example"
}
],
"TargetType": "ip",
"UnhealthyThresholdCount": 4,
"VpcId": {"Ref": "Vpc"}
}
},
"ALBTargetGroupGreen": {
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
"Properties": {
"HealthCheckIntervalSeconds": 5,
"HealthCheckPath": "/",
"HealthCheckPort": "80",
"HealthCheckProtocol": "HTTP",
"HealthCheckTimeoutSeconds": 2,
"HealthyThresholdCount": 2,
"Matcher": {
"HttpCode": "200"
},
"Port": 80,
"Protocol": "HTTP",
"Tags": [
{
"Key": "Group",
"Value": "Example"
}
],
"TargetType": "ip",
"UnhealthyThresholdCount": 4,
"VpcId": {"Ref": "Vpc"}
}
},
"ExampleALB": {
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
"Properties": {
"Scheme": "internet-facing",
"SecurityGroups": [
{"Ref": "ExampleSecurityGroup"}
],
"Subnets": [
{"Ref": "Subnet1"},
{"Ref": "Subnet2"}
],
"Tags": [
{
"Key": "Group",
"Value": "Example"
}
],
"Type": "application",
"IpAddressType": "ipv4"
}
},
"ALBListenerProdTraffic": {
"Type": "AWS::ElasticLoadBalancingV2::Listener",
"Properties": {
"DefaultActions": [
{
"Type": "forward",
"ForwardConfig": {
"TargetGroups": [
{
"TargetGroupArn": {"Ref": "ALBTargetGroupBlue"},
"Weight": 1
}
]
}
}
],
"LoadBalancerArn": {"Ref": "ExampleALB"},
"Port": 80,
"Protocol": "HTTP"
}
},
"ALBListenerProdRule": {
"Type": "AWS::ElasticLoadBalancingV2::ListenerRule",
"Properties": {
"Actions": [
{
"Type": "forward",
"ForwardConfig": {
"TargetGroups": [
{
"TargetGroupArn": {"Ref": "ALBTargetGroupBlue"},
"Weight": 1
}
]
}
}
],
"Conditions": [
{
"Field": "http-header",
"HttpHeaderConfig": {
"HttpHeaderName": "User-Agent",
"Values": [
"Mozilla"
]
}
}
],
"ListenerArn": {"Ref": "ALBListenerProdTraffic"},
"Priority": 1
}
},
"ECSTaskExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
]
}
},
"BlueTaskDefinition": {
"Type": "AWS::ECS::TaskDefinition",
"Properties": {
"ExecutionRoleArn": {"Fn::GetAtt": ["ECSTaskExecutionRole", "Arn"]},
"ContainerDefinitions": [
{
"Name": "DemoApp",
"Image": "nginxdemos/hello:latest",
"Essential": true,
"PortMappings": [
{
"HostPort": 80,
"Protocol": "tcp",
"ContainerPort": 80
}
]
}
],
"RequiresCompatibilities": [
"FARGATE"
],
"NetworkMode": "awsvpc",
"Cpu": "256",
"Memory": "512",
"Family": "ecs-demo"
}
},
"ECSDemoCluster": {
"Type": "AWS::ECS::Cluster",
"Properties": {}
},
"ECSDemoService": {
"Type": "AWS::ECS::Service",
"Properties": {
"Cluster": {"Ref": "ECSDemoCluster"},
"DesiredCount": 1,
"DeploymentController": {
"Type": "EXTERNAL"
}
}
},
"BlueTaskSet": {
"Type": "AWS::ECS::TaskSet",
"Properties": {
"Cluster": {"Ref": "ECSDemoCluster"},
"LaunchType": "FARGATE",
"NetworkConfiguration": {
"AwsVpcConfiguration": {
"AssignPublicIp": "ENABLED",
"SecurityGroups": [
{"Ref": "ExampleSecurityGroup"}
],
"Subnets": [
{"Ref": "Subnet1"},
{"Ref": "Subnet2"}
]
}
},
"PlatformVersion": "1.4.0",
"Scale": {
"Unit": "PERCENT",
"Value": 100
},
"Service": {"Ref": "ECSDemoService"},
"TaskDefinition": {"Ref": "BlueTaskDefinition"},
"LoadBalancers": [
{
"ContainerName": "DemoApp",
"ContainerPort": 80,
"TargetGroupArn": {"Ref": "ALBTargetGroupBlue"}
}
]
}
},
"PrimaryTaskSet": {
"Type": "AWS::ECS::PrimaryTaskSet",
"Properties": {
"Cluster": {"Ref": "ECSDemoCluster"},
"Service": {"Ref": "ECSDemoService"},
"TaskSetId": {"Fn::GetAtt": ["BlueTaskSet", "Id"]}
}
}
}
}
마이그레이션 단계
CodeDeploy 특정 리소스 제거 다음 속성이 더 이상 필요하지 않아요.
AWS::CodeDeployBlueGreentransformCodeDeployBlueGreenHook후크GreenTaskDefinition및GreenTaskSet리소스(이들은 Amazon ECS가 관리)PrimaryTaskSet리소스(Amazon ECS가 태스크 세트를 내부적으로 관리)
로드 밸런서 리스너 재구성
ALBListenerProdTraffic 리소스를 두 개의 대상 그룹이 있는 forward 작업을 사용하도록 수정해요.
{
"DefaultActions": [
{
"Type": "forward",
"ForwardConfig": {
"TargetGroups": [
{
"TargetGroupArn": {"Ref": "ALBTargetGroupBlue"},
"Weight": 1
},
{
"TargetGroupArn": {"Ref": "ALBTargetGroupGreen"},
"Weight": 0
}
]
}
}
]
}
배포 속성 업데이트 다음을 업데이트하고 추가해 주세요.
DeploymentController속성을EXTERNAL에서ECS로 변경.Strategy속성을 추가하고BLUE_GREEN으로 설정.BakeTimeInMinutes속성을 추가.
{
"DeploymentConfiguration": {
"MaximumPercent": 200,
"MinimumHealthyPercent": 100,
"DeploymentCircuitBreaker": {
"Enable": true,
"Rollback": true
},
"BakeTimeInMinutes": 5,
"Strategy": "BLUE_GREEN"
}
}
서비스에 로드 밸런서 구성을 추가:
{
"LoadBalancers": [
{
"ContainerName": "DemoApp",
"ContainerPort": 80,
"TargetGroupArn": {"Ref": "ALBTargetGroupBlue"},
"AdvancedConfiguration": {
"AlternateTargetGroupArn": {"Ref": "ALBTargetGroupGreen"},
"ProductionListenerRule": {"Ref": "ALBListenerProdRule"},
"RoleArn": {"Fn::GetAtt": ["ECSInfrastructureRoleForLoadBalancers", "Arn"]}
}
}
]
}
서비스에 태스크 정의 참조를 추가:
{
"TaskDefinition": {"Ref": "BlueTaskDefinition"}
}
AmazonECSInfrastructureRolePolicyForLoadBalancers 역할 만들기 Amazon ECS가 로드 밸런서 리소스를 관리할 수 있게 하는 새 IAM 역할을 추가해 주세요. 자세한 내용은 Amazon ECS infrastructure IAM role for load balancers 를 참고해 주세요.
테스트 권장 사항
- 마이그레이션된 템플릿을 비프로덕션 환경에 배포해 주세요.
- 서비스가 초기 구성으로 올바르게 배포되는지 확인해 주세요.
- 태스크 정의를 업데이트해 배포를 테스트하고 블루/그린 배포 프로세스를 관찰해 주세요.
- 블루와 그린 배포 간에 트래픽이 올바르게 전환되는지 확인해 주세요.
- 배포 실패를 강제해 롤백 기능을 테스트해 주세요.
마이그레이션 후 템플릿 다음은 Amazon ECS 블루/그린 배포를 사용하는 완전한 CloudFormation 템플릿이에요.
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
"Vpc": {
"Type": "AWS::EC2::VPC::Id"
},
"Subnet1": {
"Type": "AWS::EC2::Subnet::Id"
},
"Subnet2": {
"Type": "AWS::EC2::Subnet::Id"
}
},
"Resources": {
"ExampleSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Security group for ec2 access",
"VpcId": {"Ref": "Vpc"},
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": 80,
"ToPort": 80,
"CidrIp": "0.0.0.0/0"
},
{
"IpProtocol": "tcp",
"FromPort": 8080,
"ToPort": 8080,
"CidrIp": "0.0.0.0/0"
},
{
"IpProtocol": "tcp",
"FromPort": 22,
"ToPort": 22,
"CidrIp": "0.0.0.0/0"
}
]
}
},
"ALBTargetGroupBlue": {
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
"Properties": {
"HealthCheckIntervalSeconds": 5,
"HealthCheckPath": "/",
"HealthCheckPort": "80",
"HealthCheckProtocol": "HTTP",
"HealthCheckTimeoutSeconds": 2,
"HealthyThresholdCount": 2,
"Matcher": {
"HttpCode": "200"
},
"Port": 80,
"Protocol": "HTTP",
"Tags": [
{
"Key": "Group",
"Value": "Example"
}
],
"TargetType": "ip",
"UnhealthyThresholdCount": 4,
"VpcId": {"Ref": "Vpc"}
}
},
"ALBTargetGroupGreen": {
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
"Properties": {
"HealthCheckIntervalSeconds": 5,
"HealthCheckPath": "/",
"HealthCheckPort": "80",
"HealthCheckProtocol": "HTTP",
"HealthCheckTimeoutSeconds": 2,
"HealthyThresholdCount": 2,
"Matcher": {
"HttpCode": "200"
},
"Port": 80,
"Protocol": "HTTP",
"Tags": [
{
"Key": "Group",
"Value": "Example"
}
],
"TargetType": "ip",
"UnhealthyThresholdCount": 4,
"VpcId": {"Ref": "Vpc"}
}
},
"ExampleALB": {
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
"Properties": {
"Scheme": "internet-facing",
"SecurityGroups": [
{"Ref": "ExampleSecurityGroup"}
],
"Subnets": [
{"Ref": "Subnet1"},
{"Ref": "Subnet2"}
],
"Tags": [
{
"Key": "Group",
"Value": "Example"
}
],
"Type": "application",
"IpAddressType": "ipv4"
}
},
"ALBListenerProdTraffic": {
"Type": "AWS::ElasticLoadBalancingV2::Listener",
"Properties": {
"DefaultActions": [
{
"Type": "forward",
"ForwardConfig": {
"TargetGroups": [
{
"TargetGroupArn": {"Ref": "ALBTargetGroupBlue"},
"Weight": 1
},
{
"TargetGroupArn": {"Ref": "ALBTargetGroupGreen"},
"Weight": 0
}
]
}
}
],
"LoadBalancerArn": {"Ref": "ExampleALB"},
"Port": 80,
"Protocol": "HTTP"
}
},
"ALBListenerProdRule": {
"Type": "AWS::ElasticLoadBalancingV2::ListenerRule",
"Properties": {
"Actions": [
{
"Type": "forward",
"ForwardConfig": {
"TargetGroups": [
{
"TargetGroupArn": {"Ref": "ALBTargetGroupBlue"},
"Weight": 1
},
{
"TargetGroupArn": {"Ref": "ALBTargetGroupGreen"},
"Weight": 0
}
]
}
}
],
"Conditions": [
{
"Field": "http-header",
"HttpHeaderConfig": {
"HttpHeaderName": "User-Agent",
"Values": [
"Mozilla"
]
}
}
],
"ListenerArn": {"Ref": "ALBListenerProdTraffic"},
"Priority": 1
}
},
"ECSTaskExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
]
}
},
"ECSInfrastructureRoleForLoadBalancers": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAccessToECSForInfrastructureManagement",
"Effect": "Allow",
"Principal": {
"Service": "ecs.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/AmazonECSInfrastructureRolePolicyForLoadBalancers"
]
}
},
"BlueTaskDefinition": {
"Type": "AWS::ECS::TaskDefinition",
"Properties": {
"ExecutionRoleArn": {"Fn::GetAtt": ["ECSTaskExecutionRole", "Arn"]},
"ContainerDefinitions": [
{
"Name": "DemoApp",
"Image": "nginxdemos/hello:latest",
"Essential": true,
"PortMappings": [
{
"HostPort": 80,
"Protocol": "tcp",
"ContainerPort": 80
}
]
}
],
"RequiresCompatibilities": [
"FARGATE"
],
"NetworkMode": "awsvpc",
"Cpu": "256",
"Memory": "512",
"Family": "ecs-demo"
}
},
"ECSDemoCluster": {
"Type": "AWS::ECS::Cluster",
"Properties": {}
},
"ECSDemoService": {
"Type": "AWS::ECS::Service",
"Properties": {
"Cluster": {"Ref": "ECSDemoCluster"},
"DesiredCount": 1,
"DeploymentController": {
"Type": "ECS"
},
"DeploymentConfiguration": {
"MaximumPercent": 200,
"MinimumHealthyPercent": 100,
"DeploymentCircuitBreaker": {
"Enable": true,
"Rollback": true
},
"BakeTimeInMinutes": 5,
"Strategy": "BLUE_GREEN"
},
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"AssignPublicIp": "ENABLED",
"SecurityGroups": [
{"Ref": "ExampleSecurityGroup"}
],
"Subnets": [
{"Ref": "Subnet1"},
{"Ref": "Subnet2"}
]
}
},
"LaunchType": "FARGATE",
"PlatformVersion": "1.4.0",
"TaskDefinition": {"Ref": "BlueTaskDefinition"},
"LoadBalancers": [
{
"ContainerName": "DemoApp",
"ContainerPort": 80,
"TargetGroupArn": {"Ref": "ALBTargetGroupBlue"},
"AdvancedConfiguration": {
"AlternateTargetGroupArn": {"Ref": "ALBTargetGroupGreen"},
"ProductionListenerRule": {"Ref": "ALBListenerProdRule"},
"RoleArn": {"Fn::GetAtt": ["ECSInfrastructureRoleForLoadBalancers", "Arn"]}
}
}
]
}
}
}
}