如何使用 CloudFormation 组织 ECS CodePipeline

How to Organize ECS CodePipeline with CloudFormation

我正在尝试为我们的应用程序构建一系列部署管道。我们的应用程序部署在 ECS Fargate 中。我使用 CloudFormation(ALB、main/secondary 听众、main/secondary 目标组、FargateService、TaskDefinition)创建了基本基础设施。我还为我们的下层环境 ECS 集群创建了一个部署管道,如下所示 CodeBuild -> ECR(docker image)/S3(appspec.yml/taskdef.json) -> CodePipeline -> CodeDeploy ECS Blue/Green。我之所以在流程前面使用 CodeBuild 是为了能够基于事件构建到多个分支。

我想以同样的方式在 CloudFormation 中设置我的部署管道。由于其中一个目标组未在负载均衡器中注册,我无法将其添加到 CloudFormation 模板中。我认为这就是 CodeDeploy 的工作方式。作为部署过程的一部分,它在 ALB 中添加和删除目标组。我在这里缺少什么吗?有没有办法结合 CodePipeline 和 CodeDeploy for ECS 构建 CloudFormation 模板?

到目前为止,这是我的模板:

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  Cluster:
    Type: String
    Default: cluster-dev
  DesiredCount:
    Type: Number
    Default: 1
  ContainerPort:
    Type: Number
    Default: 8080
  LaunchType:
    Type: String
    Default: Fargate
    AllowedValues:
      - Fargate
  MainTargetGroupName:
    Type: String
    MaxLength: 32
  CodeDeployTargetGroupName:
    Type: String
    MaxLength: 32
  SourceSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup::Id'
  PrivateSubnets:
    Type: 'List<AWS::EC2::Subnet::Id>'
  PublicSubnets:
    Type: 'List<AWS::EC2::Subnet::Id>'
  ALBSecurityGroups:
    Type: 'List<AWS::EC2::SecurityGroup::Id>'
  VPC:
    Type: 'AWS::EC2::VPC::Id'
Conditions:
  Fargate: !Equals 
    - !Ref LaunchType
    - Fargate
  EC2: !Equals 
    - !Ref LaunchType
    - EC2
Resources:
  ApplicationLoadBalancer:
    Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
    Properties:
        Name: "test-Application-Load-Balancer"
        Scheme: "internet-facing"
        Type: "application"
        Subnets: !Ref PublicSubnets
        SecurityGroups: !Ref ALBSecurityGroups
        IpAddressType: "ipv4"
        LoadBalancerAttributes: 
            - Key: "access_logs.s3.enabled"
              Value: "false"
            - Key: "idle_timeout.timeout_seconds"
              Value: "60"
            - Key: "deletion_protection.enabled"
              Value: "false"
            - Key: "routing.http2.enabled"
              Value: "true"
            - Key: "routing.http.drop_invalid_header_fields.enabled"
              Value: "false"
  ServiceTargetGroup:
    Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
    Properties:
        HealthCheckIntervalSeconds: 30
        HealthCheckPath: "/ping"
        Port: 443
        Protocol: "HTTPS"
        HealthCheckPort: "traffic-port"
        HealthCheckProtocol: "HTTPS"
        HealthCheckTimeoutSeconds: 5
        UnhealthyThresholdCount: 2
        TargetType: "ip"
        Matcher: 
            HttpCode: "200"
        HealthyThresholdCount: 5
        VpcId: !Ref VPC
        Name: !Ref MainTargetGroupName
        HealthCheckEnabled: true
        TargetGroupAttributes: 
          - Key: "stickiness.enabled"
            Value: "false"
          - Key: "deregistration_delay.timeout_seconds"
            Value: "300"
          - Key: "stickiness.type"
            Value: "lb_cookie"
          - Key: "stickiness.lb_cookie.duration_seconds"
            Value: "86400"
          - Key: "slow_start.duration_seconds"
            Value: "0"
          - Key: "load_balancing.algorithm.type"
            Value: "round_robin"
  GreenTargetGroup:
    Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
    Properties:
        HealthCheckIntervalSeconds: 30
        HealthCheckPath: "/ping"
        Port: 8443
        Protocol: "HTTPS"
        HealthCheckPort: "traffic-port"
        HealthCheckProtocol: "HTTPS"
        HealthCheckTimeoutSeconds: 5
        UnhealthyThresholdCount: 2
        TargetType: "ip"
        Matcher: 
            HttpCode: "200"
        HealthyThresholdCount: 5
        VpcId: !Ref VPC
        Name: !Ref CodeDeployTargetGroupName
        HealthCheckEnabled: true
        TargetGroupAttributes: 
          - Key: "stickiness.enabled"
            Value: "false"
          - Key: "deregistration_delay.timeout_seconds"
            Value: "300"
          - Key: "stickiness.type"
            Value: "lb_cookie"
          - Key: "stickiness.lb_cookie.duration_seconds"
            Value: "86400"
          - Key: "slow_start.duration_seconds"
            Value: "0"
          - Key: "load_balancing.algorithm.type"
            Value: "round_robin"
  HTTPSListener:
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties:
        LoadBalancerArn: !Ref ApplicationLoadBalancer
        Port: 443
        Protocol: "HTTPS"
        SslPolicy: "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
        Certificates: 
          - CertificateArn: '*************************************'
        DefaultActions: 
          - Order: 1
            TargetGroupArn: !Ref ServiceTargetGroup
            Type: "forward"
  GreenListener:
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties:
        LoadBalancerArn: !Ref ApplicationLoadBalancer
        Port: 8443
        Protocol: "HTTPS"
        SslPolicy: "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
        Certificates: 
          - CertificateArn: '************************'
        DefaultActions: 
          - Order: 1
            TargetGroupArn: !Ref GreenTargetGroup
            Type: "forward"
  CloudWatchLogsGroup:
    Type: AWS::Logs::LogGroup
    Properties: 
      LogGroupName: !Ref AWS::StackName
      RetentionInDays: 7  
  ECSTaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::StackName}-task
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: [ecs-tasks.amazonaws.com]
            Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
      Policies:
        - PolicyName: ssm
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ssmmessages:CreateControlChannel
                  - ssmmessages:CreateDataChannel
                  - ssmmessages:OpenControlChannel
                  - ssmmessages:OpenDataChannel
                Resource:
                  - '*'
  ECSTaskExecRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::StackName}-taskexec
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: [ecs-tasks.amazonaws.com]
            Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
  FargateService:
    Type: 'AWS::ECS::Service'
    Condition: Fargate
    DependsOn: 
      - HTTPSListener
      - GreenListener
    Properties:
      Cluster: !Ref Cluster
      DesiredCount: !Sub ${DesiredCount}
      TaskDefinition: !Ref TaskDefinition
      LaunchType: FARGATE
      HealthCheckGracePeriodSeconds: 300
      EnableExecuteCommand: true
      DeploymentController: 
        Type: CODE_DEPLOY
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !Ref SourceSecurityGroup
          Subnets: !Ref PrivateSubnets
      LoadBalancers:
        - ContainerName: !Sub '${AWS::StackName}'
          ContainerPort: !Sub ${ContainerPort}
          TargetGroupArn: !Ref ServiceTargetGroup
    Metadata:
      'AWS::CloudFormation::Designer':
        id: ***********
  TaskDefinition:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      Family: !Sub '${AWS::StackName}'
      RequiresCompatibilities:
        - FARGATE
      Memory: 1024
      Cpu: 512
      NetworkMode: awsvpc
      TaskRoleArn: !GetAtt ECSTaskRole.Arn
      ExecutionRoleArn: !GetAtt ECSTaskExecRole.Arn
      ContainerDefinitions:
        - Name: !Sub '${AWS::StackName}'
          Image: '**********'
          EntryPoint:
            - 'java'
            - '-jar'
            - 'app-1.0.jar'
          Essential: true
          Memory: 1024
          Cpu: 512
          PortMappings:
            - ContainerPort: !Sub ${ContainerPort}
              HostPort: 8080
              Protocol: 'tcp'
          HealthCheck:
              Command: [ "CMD-SHELL", "curl -k -f https://localhost:8080/ping || exit 1" ]
              Interval: 30
              Retries: 5
              Timeout: 10
              StartPeriod: 30
          LogConfiguration:
            LogDriver: awslogs
            Options:
                awslogs-group: !Ref AWS::StackName
                awslogs-region: !Ref AWS::Region
                awslogs-stream-prefix: !Ref AWS::StackName
    Metadata:
      'AWS::CloudFormation::Designer':
        id: ********
Outputs:
  Service:
    Value: !Ref FargateService

如有任何帮助,我们将不胜感激。

杰夫

秘诀是将以下内容添加到两个目标组中。

DependsOn:
  - ApplicationLoadBalancer

经过进一步审查,我认为 CloudFormation 不支持 CodePipeline 的 BlueGreen ECS 部署设置。这有点牵强,但在尝试这样做时会出现重大错误。我在 Whosebug 甚至 AWS 上看到了一些帖子,因为这是不可能的。