将 Amazon CloudFormation CodeDeploy 蓝绿部署模板迁移到 Amazon ECS 蓝绿部署 Amazon CloudFormation 模板
将使用适用于 Amazon ECS 服务的 CodeDeploy 蓝绿部署的 Amazon CloudFormation 模板迁移到使用原生 Amazon ECS 蓝绿部署策略的模板。迁移遵循“重复使用 CodeDeploy 所使用的同一 Elastic Load Balancing 资源”的方法。有关更多信息,请参阅 将 CodeDeploy 蓝绿部署迁移到 Amazon ECS 蓝绿部署。
源模板
此模板使用 AWS::CodeDeployBlueGreen 转换和 AWS::CodeDeploy::BlueGreen 挂钩为 Amazon ECS 服务实现蓝绿部署。
这是使用 CodeDeploy 蓝绿部署的完整 Amazon CloudFormation 模板。有关更多信息,请参阅《Amazon CloudFormation 用户指南》中的蓝绿部署模板示例:
{ "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::CodeDeployBlueGreen转换 -
CodeDeployBlueGreenHook挂钩 -
GreenTaskDefinition和GreenTaskSet资源(这些资源将由 Amazon ECS 管理) -
PrimaryTaskSet资源(Amazon ECS 将在内部管理任务集)
重新配置负载均衡器侦听器
修改 ALBListenerProdTraffic 资源,使用带有两个目标组的转发操作:
{ "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 基础设施 IAM 角色。
测试建议
-
将迁移后的模板部署到非生产环境。
-
确认服务是否使用初始配置正确部署。
-
通过更新任务定义并观察蓝绿部署过程来测试部署。
-
确认流量是否在蓝色部署和绿色部署之间正确转移。
-
通过强制触发部署失败来测试回滚功能。
迁移后的模板
这是使用 Amazon ECS 蓝绿部署的完整 Amazon 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"]} } } ] } } } }