ECS Fargate 的 CloudFormation 模板中的 HTTP 到 HTTPS 重定向

HTTP to HTTPS redirect in CloudFormation template for ECS Fargate

我有以下模板用于创建堆栈以在 AWS 上使用 ECS Fargate 进行托管。

AWSTemplateFormatVersion: 2010-09-09
Description: CloudFormation template for Storefront SSR hosting.
Parameters:
  VPC:
    Type: AWS::EC2::VPC::Id
  SubnetA:
    Type: AWS::EC2::Subnet::Id
  SubnetB:
    Type: AWS::EC2::Subnet::Id
  Certificate:
    Type: String
    # Update with the certificate ARN from Certificate Manager, which must exist in the same region.
    # In our case, it is storefront.domain.com
    Default: 'arn:aws:acm:us-east-1:5xxxxxxxxx3:certificate/1abedeff-ee6c-46c2-ac44-603dfd14dac1'
  Image:
    Type: String
    # Update with the Docker image. "You can use images in the Docker Hub registry or specify other repositories (repository-url/image:tag)."
    Default: 5xxxxxxxxx3.dkr.ecr.us-east-1.amazonaws.com/storefront-staging:latest
  ServiceName:
    Type: String
    # update with the name of the service
    Default: storefront-staging
  ContainerPort:
    Type: Number
    Default: 3000
  LoadBalancerPort:
    Type: Number
    Default: 443
  HealthCheckPath:
    Type: String
    Default: /categories
  HostedZoneName:
    Type: String
    Default: domain.com
  Subdomain:
    Type: String
    Default: storefront
  # for autoscaling
  MinContainers:
    Type: Number
    Default: 1
  # for autoscaling
  MaxContainers:
    Type: Number
    Default: 2
  # target CPU utilization (%)
  AutoScalingTargetValue:
    Type: Number
    Default: 50
Resources:
  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Join ['', [!Ref ServiceName, Cluster]]
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    # Makes sure the log group is created before it is used.
    DependsOn: LogGroup
    Properties:
      # Name of the task definition. Subsequent versions of the task definition are grouped together under this name.
      Family: !Join ['', [!Ref ServiceName, TaskDefinition]]
      # awsvpc is required for Fargate
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      # 256 (.25 vCPU) - Available memory values: 0.5GB, 1GB, 2GB
      # 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB
      # 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB
      # 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments
      # 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments
      Cpu: 256
      # 0.5GB, 1GB, 2GB - Available cpu values: 256 (.25 vCPU)
      # 1GB, 2GB, 3GB, 4GB - Available cpu values: 512 (.5 vCPU)
      # 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB - Available cpu values: 1024 (1 vCPU)
      # Between 4GB and 16GB in 1GB increments - Available cpu values: 2048 (2 vCPU)
      # Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU)
      Memory: 0.5GB
      # A role needed by ECS.
      # "The ARN of the task execution role that containers in this task can assume. All containers in this task are granted the permissions that are specified in this role."
      # "There is an optional task execution IAM role that you can specify with Fargate to allow your Fargate tasks to make API calls to Amazon ECR."
      ExecutionRoleArn: !Ref ExecutionRole
      # "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that grants containers in the task permission to call AWS APIs on your behalf."
      TaskRoleArn: !Ref TaskRole
      ContainerDefinitions:
        - Name: !Ref ServiceName
          Image: !Ref Image
          PortMappings:
            - ContainerPort: !Ref ContainerPort
          # Send logs to CloudWatch Logs
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-region: !Ref AWS::Region
              awslogs-group: !Ref LogGroup
              awslogs-stream-prefix: ecs
  # A role needed by ECS
  ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ['', [!Ref ServiceName, ExecutionRole]]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'
  # A role for the containers
  TaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ['', [!Ref ServiceName, TaskRole]]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      # ManagedPolicyArns:
      #   -
      # Policies:
      #   -
  # A role needed for auto scaling
  AutoScalingRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ['', [!Ref ServiceName, AutoScalingRole]]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole'
  ContainerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Join ['', [!Ref ServiceName, ContainerSecurityGroup]]
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: !Ref ContainerPort
          ToPort: !Ref ContainerPort
          SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription:
        !Join ['', [!Ref ServiceName, LoadBalancerSecurityGroup]]
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: !Ref LoadBalancerPort
          ToPort: !Ref LoadBalancerPort
          CidrIp: 0.0.0.0/0
  Service:
    Type: AWS::ECS::Service
    # This dependency is needed so that the load balancer is setup correctly in time
    DependsOn:
      - ListenerHTTPS
    Properties:
      ServiceName: !Ref ServiceName
      Cluster: !Ref Cluster
      TaskDefinition: !Ref TaskDefinition
      DeploymentConfiguration:
        MinimumHealthyPercent: 100
        MaximumPercent: 200
      DesiredCount: 2
      # This may need to be adjusted if the container takes a while to start up
      HealthCheckGracePeriodSeconds: 30
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
          # change to DISABLED if you're using private subnets that have access to a NAT gateway
          AssignPublicIp: ENABLED
          Subnets:
            - !Ref SubnetA
            - !Ref SubnetB
          SecurityGroups:
            - !Ref ContainerSecurityGroup
      LoadBalancers:
        - ContainerName: !Ref ServiceName
          ContainerPort: !Ref ContainerPort
          TargetGroupArn: !Ref TargetGroup
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 300
      # will look for a 200 status code by default unless specified otherwise
      HealthCheckPath: !Ref HealthCheckPath
      HealthCheckTimeoutSeconds: 5
      UnhealthyThresholdCount: 2
      HealthyThresholdCount: 2
      Name: !Join ['', [!Ref ServiceName, TargetGroup]]
      Port: !Ref ContainerPort
      Protocol: HTTP
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 300 # default is 300
      TargetType: ip
      VpcId: !Ref VPC
  ListenerHTTPS:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      LoadBalancerArn: !Ref LoadBalancer
      Port: !Ref LoadBalancerPort
      Protocol: HTTPS
      Certificates:
        - CertificateArn: !Ref Certificate
  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      LoadBalancerAttributes:
        # this is the default, but is specified here in case it needs to be changed
        - Key: idle_timeout.timeout_seconds
          Value: 60
      Name: !Join ['', [!Ref ServiceName, LoadBalancer]]
      # "internal" is also an option
      Scheme: internet-facing
      SecurityGroups:
        - !Ref LoadBalancerSecurityGroup
      Subnets:
        - !Ref SubnetA
        - !Ref SubnetB
  DNSRecord:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneName: !Join ['', [!Ref HostedZoneName, .]]
      Name: !Join ['', [!Ref Subdomain, ., !Ref HostedZoneName, .]]
      Type: A
      AliasTarget:
        DNSName: !GetAtt LoadBalancer.DNSName
        HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Join ['', [/ecs/, !Ref ServiceName, TaskDefinition]]
  AutoScalingTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MinCapacity: !Ref MinContainers
      MaxCapacity: !Ref MaxContainers
      ResourceId: !Join ['/', [service, !Ref Cluster, !GetAtt Service.Name]]
      ScalableDimension: ecs:service:DesiredCount
      ServiceNamespace: ecs
      # "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that allows Application Auto Scaling to modify your scalable target."
      RoleARN: !GetAtt AutoScalingRole.Arn
  AutoScalingPolicy:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: !Join ['', [!Ref ServiceName, AutoScalingPolicy]]
      PolicyType: TargetTrackingScaling
      ScalingTargetId: !Ref AutoScalingTarget
      TargetTrackingScalingPolicyConfiguration:
        PredefinedMetricSpecification:
          PredefinedMetricType: ECSServiceAverageCPUUtilization
        ScaleInCooldown: 10
        ScaleOutCooldown: 10
        # Keep things at or lower than 50% CPU utilization, for example
        TargetValue: !Ref AutoScalingTargetValue
Outputs:
  Endpoint:
    Description: Endpoint
    Value: !Join ['', ['https://', !Ref DNSRecord]]

它工作正常,但模板没有 HTTP->HTTPS 重定向。创建服务后,我必须在端口 80 上手动将侦听器添加到 ECS 负载均衡器,因此它会重定向到 HTTPS redirecting to HTTPS://#{host}:443/#{path}?#{query}。此外,我的安全组目前仅支持端口 443 上的传入流量,而我还需要允许端口 80 上的传入流量。

此外,我的设置如下:

  Subdomain:
    Type: String
    Default: storefront

我该怎么做才能支持没有前缀的裸域,例如 domain.com

要将 http 重定向到 https,需要进行以下两项更改

  1. 将端口 80 添加到 LoadBalancerSecurityGroup
  2. 添加监听器ListenerHTTP

在您的模板的以下版本中进行了更改:

AWSTemplateFormatVersion: 2010-09-09
Description: CloudFormation template for Storefront SSR hosting.
Parameters:
  VPC:
    Type: AWS::EC2::VPC::Id
  SubnetA:
    Type: AWS::EC2::Subnet::Id
  SubnetB:
    Type: AWS::EC2::Subnet::Id
  Certificate:
    Type: String
    # Update with the certificate ARN from Certificate Manager, which must exist in the same region.
    # In our case, it is storefront.domain.com
    Default: 'arn:aws:acm:us-east-1:5xxxxxxxxx3:certificate/1abedeff-ee6c-46c2-ac44-603dfd14dac1'
  Image:
    Type: String
    # Update with the Docker image. "You can use images in the Docker Hub registry or specify other repositories (repository-url/image:tag)."
    Default: 5xxxxxxxxx3.dkr.ecr.us-east-1.amazonaws.com/storefront-staging:latest
  ServiceName:
    Type: String
    # update with the name of the service
    Default: storefront-staging
  ContainerPort:
    Type: Number
    Default: 3000
  LoadBalancerPort:
    Type: Number
    Default: 443
  HealthCheckPath:
    Type: String
    Default: /categories
  HostedZoneName:
    Type: String
    Default: domain.com
  Subdomain:
    Type: String
    Default: storefront
  # for autoscaling
  MinContainers:
    Type: Number
    Default: 1
  # for autoscaling
  MaxContainers:
    Type: Number
    Default: 2
  # target CPU utilization (%)
  AutoScalingTargetValue:
    Type: Number
    Default: 50
Resources:

  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Join ['', [!Ref ServiceName, Cluster]]

  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    # Makes sure the log group is created before it is used.
    DependsOn: LogGroup
    Properties:
      # Name of the task definition. Subsequent versions of the task definition are grouped together under this name.
      Family: !Join ['', [!Ref ServiceName, TaskDefinition]]
      # awsvpc is required for Fargate
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      # 256 (.25 vCPU) - Available memory values: 0.5GB, 1GB, 2GB
      # 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB
      # 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB
      # 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments
      # 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments
      Cpu: 256
      # 0.5GB, 1GB, 2GB - Available cpu values: 256 (.25 vCPU)
      # 1GB, 2GB, 3GB, 4GB - Available cpu values: 512 (.5 vCPU)
      # 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB - Available cpu values: 1024 (1 vCPU)
      # Between 4GB and 16GB in 1GB increments - Available cpu values: 2048 (2 vCPU)
      # Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU)
      Memory: 0.5GB
      # A role needed by ECS.
      # "The ARN of the task execution role that containers in this task can assume. All containers in this task are granted the permissions that are specified in this role."
      # "There is an optional task execution IAM role that you can specify with Fargate to allow your Fargate tasks to make API calls to Amazon ECR."
      ExecutionRoleArn: !Ref ExecutionRole
      # "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that grants containers in the task permission to call AWS APIs on your behalf."
      TaskRoleArn: !Ref TaskRole
      ContainerDefinitions:
        - Name: !Ref ServiceName
          Image: !Ref Image
          PortMappings:
            - ContainerPort: !Ref ContainerPort
          # Send logs to CloudWatch Logs
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-region: !Ref AWS::Region
              awslogs-group: !Ref LogGroup
              awslogs-stream-prefix: ecs

  # A role needed by ECS
  ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ['', [!Ref ServiceName, ExecutionRole]]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'

  # A role for the containers
  TaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ['', [!Ref ServiceName, TaskRole]]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      # ManagedPolicyArns:
      #   -
      # Policies:
      #   -
  # A role needed for auto scaling

  AutoScalingRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ['', [!Ref ServiceName, AutoScalingRole]]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole'

  ContainerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Join ['', [!Ref ServiceName, ContainerSecurityGroup]]
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: !Ref ContainerPort
          ToPort: !Ref ContainerPort
          SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup

  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription:
        !Join ['', [!Ref ServiceName, LoadBalancerSecurityGroup]]
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: !Ref LoadBalancerPort
          ToPort: !Ref LoadBalancerPort
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0  

  Service:
    Type: AWS::ECS::Service
    # This dependency is needed so that the load balancer is setup correctly in time
    DependsOn:
      - ListenerHTTPS
    Properties:
      ServiceName: !Ref ServiceName
      Cluster: !Ref Cluster
      TaskDefinition: !Ref TaskDefinition
      DeploymentConfiguration:
        MinimumHealthyPercent: 100
        MaximumPercent: 200
      DesiredCount: 2
      # This may need to be adjusted if the container takes a while to start up
      HealthCheckGracePeriodSeconds: 30
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
          # change to DISABLED if you're using private subnets that have access to a NAT gateway
          AssignPublicIp: ENABLED
          Subnets:
            - !Ref SubnetA
            - !Ref SubnetB
          SecurityGroups:
            - !Ref ContainerSecurityGroup
      LoadBalancers:
        - ContainerName: !Ref ServiceName
          ContainerPort: !Ref ContainerPort
          TargetGroupArn: !Ref TargetGroup

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 300
      # will look for a 200 status code by default unless specified otherwise
      HealthCheckPath: !Ref HealthCheckPath
      HealthCheckTimeoutSeconds: 5
      UnhealthyThresholdCount: 2
      HealthyThresholdCount: 2
      Name: !Join ['', [!Ref ServiceName, TargetGroup]]
      Port: !Ref ContainerPort
      Protocol: HTTP
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 300 # default is 300
      TargetType: ip
      VpcId: !Ref VPC

  ListenerHTTP:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: "redirect"
          RedirectConfig:
            Protocol: "HTTPS"
            Port: 443
            Host: "#{host}"
            Path: "/#{path}"
            Query: "#{query}"
            StatusCode: "HTTP_301"
      LoadBalancerArn: !Ref LoadBalancer
      Port: 80
      Protocol: HTTP

  ListenerHTTPS:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      LoadBalancerArn: !Ref LoadBalancer
      Port: !Ref LoadBalancerPort
      Protocol: HTTPS
      Certificates:
        - CertificateArn: !Ref Certificate

  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      LoadBalancerAttributes:
        # this is the default, but is specified here in case it needs to be changed
        - Key: idle_timeout.timeout_seconds
          Value: 60
      Name: !Join ['', [!Ref ServiceName, LoadBalancer]]
      # "internal" is also an option
      Scheme: internet-facing
      SecurityGroups:
        - !Ref LoadBalancerSecurityGroup
      Subnets:
        - !Ref SubnetA
        - !Ref SubnetB

  DNSRecord:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneName: !Join ['', [!Ref HostedZoneName, .]]
      Name: !Join ['', [!Ref Subdomain, ., !Ref HostedZoneName, .]]
      Type: A
      AliasTarget:
        DNSName: !GetAtt LoadBalancer.DNSName
        HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Join ['', [/ecs/, !Ref ServiceName, TaskDefinition]]

  AutoScalingTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MinCapacity: !Ref MinContainers
      MaxCapacity: !Ref MaxContainers
      ResourceId: !Join ['/', [service, !Ref Cluster, !GetAtt Service.Name]]
      ScalableDimension: ecs:service:DesiredCount
      ServiceNamespace: ecs
      # "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that allows Application Auto Scaling to modify your scalable target."
      RoleARN: !GetAtt AutoScalingRole.Arn

  AutoScalingPolicy:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: !Join ['', [!Ref ServiceName, AutoScalingPolicy]]
      PolicyType: TargetTrackingScaling
      ScalingTargetId: !Ref AutoScalingTarget
      TargetTrackingScalingPolicyConfiguration:
        PredefinedMetricSpecification:
          PredefinedMetricType: ECSServiceAverageCPUUtilization
        ScaleInCooldown: 10
        ScaleOutCooldown: 10
        # Keep things at or lower than 50% CPU utilization, for example
        TargetValue: !Ref AutoScalingTargetValue

Outputs:
  Endpoint:
    Description: Endpoint
    Value: !Join ['', ['https://', !Ref DNSRecord]]

要支持裸域,您必须为此类域获取 ACM 证书并从 DNSRecord 中删除 Subdomain