将 Amazon CloudFormation CodeDeploy 蓝绿部署模板迁移到 Amazon ECS 蓝绿部署 Amazon CloudFormation 模板 - Amazon Elastic Container Service
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

将 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 挂钩

  • GreenTaskDefinitionGreenTaskSet 资源(这些资源将由 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 角色

测试建议

  1. 将迁移后的模板部署到非生产环境。

  2. 确认服务是否使用初始配置正确部署。

  3. 通过更新任务定义并观察蓝绿部署过程来测试部署。

  4. 确认流量是否在蓝色部署和绿色部署之间正确转移。

  5. 通过强制触发部署失败来测试回滚功能。

迁移后的模板

这是使用 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"]} } } ] } } } }