

# 创建已扩展且负载均衡的应用程序
<a name="walkthrough-autoscaling"></a>

在本演练中，您将创建一个堆栈，该堆栈可帮助您设置已扩展且负载平衡的应用程序。此演练为您提供将用于创建堆栈的示例模板。此示例模板预置了自动扩缩组、应用程序负载均衡器、控制负载均衡器和自动扩缩组流量的安全组，以及用于发布有关扩展活动的通知的 Amazon SNS 通知配置。

本模板将创建一个或多个 Amazon EC2 实例和一个应用程序负载均衡器。如果您通过本模板创建堆栈，则需为使用的 AWS 资源支付相应费用。

## 完整堆栈模板
<a name="example-templates-autoscaling-full-stack-template"></a>

我们从使用模板开始。

**YAML**

```
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  InstanceType:
    Description: The EC2 instance type
    Type: String
    Default: t3.micro
    AllowedValues:
      - t3.micro
      - t3.small
      - t3.medium
  KeyName:
    Description: Name of an existing EC2 key pair to allow SSH access to the instances
    Type: AWS::EC2::KeyPair::KeyName
  LatestAmiId:
    Description: The latest Amazon Linux 2 AMI from the Parameter Store
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
  OperatorEmail:
    Description: The email address to notify when there are any scaling activities
    Type: String
  SSHLocation:
    Description: The IP address range that can be used to SSH to the EC2 instances
    Type: String
    MinLength: 9
    MaxLength: 18
    Default: 0.0.0.0/0
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
  Subnets:
    Type: 'List<AWS::EC2::Subnet::Id>'
    Description: At least two public subnets in different Availability Zones in the selected VPC
  VPC:
    Type: AWS::EC2::VPC::Id
    Description: A virtual private cloud (VPC) that enables resources in public subnets to connect to the internet
Resources:
  ELBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ELB Security Group
      VpcId: !Ref VPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: EC2 Security Group
      VpcId: !Ref VPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        SourceSecurityGroupId:
          Fn::GetAtt:
          - ELBSecurityGroup
          - GroupId
      - IpProtocol: tcp
        FromPort: 22
        ToPort: 22
        CidrIp: !Ref SSHLocation
  EC2TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 15
      HealthyThresholdCount: 5
      Matcher:
        HttpCode: '200'
      Name: EC2TargetGroup
      Port: 80
      Protocol: HTTP
      TargetGroupAttributes:
      - Key: deregistration_delay.timeout_seconds
        Value: '20'
      UnhealthyThresholdCount: 3
      VpcId: !Ref VPC
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref EC2TargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      Subnets: !Ref Subnets
      SecurityGroups:
        - !GetAtt ELBSecurityGroup.GroupId
  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties: 
      LaunchTemplateName: !Sub ${AWS::StackName}-launch-template
      LaunchTemplateData:
        ImageId: !Ref LatestAmiId
        InstanceType: !Ref InstanceType
        KeyName: !Ref KeyName
        SecurityGroupIds: 
          - !Ref EC2SecurityGroup
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            yum update -y
            yum install -y httpd
            systemctl start httpd
            systemctl enable httpd
            echo "<h1>Hello World!</h1>" > /var/www/html/index.html
  NotificationTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Endpoint: !Ref OperatorEmail
          Protocol: email
  WebServerGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate
        Version: !GetAtt LaunchTemplate.LatestVersionNumber
      MaxSize: '3'
      MinSize: '1'
      NotificationConfigurations:
        - TopicARN: !Ref NotificationTopic
          NotificationTypes: ['autoscaling:EC2_INSTANCE_LAUNCH', 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR', 'autoscaling:EC2_INSTANCE_TERMINATE', 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR']
      TargetGroupARNs:
        - !Ref EC2TargetGroup
      VPCZoneIdentifier: !Ref Subnets
```

**JSON**

```
{
  "AWSTemplateFormatVersion":"2010-09-09",
  "Parameters":{
    "InstanceType":{
      "Description":"The EC2 instance type",
      "Type":"String",
      "Default":"t3.micro",
      "AllowedValues":[
        "t3.micro",
        "t3.small",
        "t3.medium"
      ]
    },
    "KeyName":{
      "Description":"Name of an existing EC2 key pair to allow SSH access to the instances",
      "Type":"AWS::EC2::KeyPair::KeyName"
    },
    "LatestAmiId":{
      "Description":"The latest Amazon Linux 2 AMI from the Parameter Store",
      "Type":"AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>",
      "Default":"/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
    },
    "OperatorEmail":{
      "Description":"The email address to notify when there are any scaling activities",
      "Type":"String"
    },
    "SSHLocation":{
      "Description":"The IP address range that can be used to SSH to the EC2 instances",
      "Type":"String",
      "MinLength":9,
      "MaxLength":18,
      "Default":"0.0.0.0/0",
      "ConstraintDescription":"Must be a valid IP CIDR range of the form x.x.x.x/x."
    },
    "Subnets":{
      "Type":"List<AWS::EC2::Subnet::Id>",
      "Description":"At least two public subnets in different Availability Zones in the selected VPC"
    },
    "VPC":{
      "Type":"AWS::EC2::VPC::Id",
      "Description":"A virtual private cloud (VPC) that enables resources in public subnets to connect to the internet"
    }
  },
  "Resources":{
    "ELBSecurityGroup":{
      "Type":"AWS::EC2::SecurityGroup",
      "Properties":{
        "GroupDescription":"ELB Security Group",
        "VpcId":{
          "Ref":"VPC"
        },
        "SecurityGroupIngress":[
          {
            "IpProtocol":"tcp",
            "FromPort":80,
            "ToPort":80,
            "CidrIp":"0.0.0.0/0"
          }
        ]
      }
    },
    "EC2SecurityGroup":{
      "Type":"AWS::EC2::SecurityGroup",
      "Properties":{
        "GroupDescription":"EC2 Security Group",
        "VpcId":{
          "Ref":"VPC"
        },
        "SecurityGroupIngress":[
          {
            "IpProtocol":"tcp",
            "FromPort":80,
            "ToPort":80,
            "SourceSecurityGroupId":{
              "Fn::GetAtt":[
                "ELBSecurityGroup",
                "GroupId"
              ]
            }
          },
          {
            "IpProtocol":"tcp",
            "FromPort":22,
            "ToPort":22,
            "CidrIp":{
              "Ref":"SSHLocation"
            }
          }
        ]
      }
    },
    "EC2TargetGroup":{
      "Type":"AWS::ElasticLoadBalancingV2::TargetGroup",
      "Properties":{
        "HealthCheckIntervalSeconds":30,
        "HealthCheckProtocol":"HTTP",
        "HealthCheckTimeoutSeconds":15,
        "HealthyThresholdCount":5,
        "Matcher":{
          "HttpCode":"200"
        },
        "Name":"EC2TargetGroup",
        "Port":80,
        "Protocol":"HTTP",
        "TargetGroupAttributes":[
          {
            "Key":"deregistration_delay.timeout_seconds",
            "Value":"20"
          }
        ],
        "UnhealthyThresholdCount":3,
        "VpcId":{
          "Ref":"VPC"
        }
      }
    },
    "ALBListener":{
      "Type":"AWS::ElasticLoadBalancingV2::Listener",
      "Properties":{
        "DefaultActions":[
          {
            "Type":"forward",
            "TargetGroupArn":{
              "Ref":"EC2TargetGroup"
            }
          }
        ],
        "LoadBalancerArn":{
          "Ref":"ApplicationLoadBalancer"
        },
        "Port":80,
        "Protocol":"HTTP"
      }
    },
    "ApplicationLoadBalancer":{
      "Type":"AWS::ElasticLoadBalancingV2::LoadBalancer",
      "Properties":{
        "Scheme":"internet-facing",
        "Subnets":{
          "Ref":"Subnets"
        },
        "SecurityGroups":[
          {
            "Fn::GetAtt":[
              "ELBSecurityGroup",
              "GroupId"
            ]
          }
        ]
      }
    },
    "LaunchTemplate":{
      "Type":"AWS::EC2::LaunchTemplate",
      "Properties":{
        "LaunchTemplateName":{
          "Fn::Sub":"${AWS::StackName}-launch-template"
        },
        "LaunchTemplateData":{
          "ImageId":{
            "Ref":"LatestAmiId"
          },
          "InstanceType":{
            "Ref":"InstanceType"
          },
          "KeyName":{
            "Ref":"KeyName"
          },
          "SecurityGroupIds":[
            {
              "Ref":"EC2SecurityGroup"
            }
          ],
          "UserData":{
            "Fn::Base64":{
              "Fn::Join":[
                "",
                [
                  "#!/bin/bash\n",
                  "yum update -y\n",
                  "yum install -y httpd\n",
                  "systemctl start httpd\n",
                  "systemctl enable httpd\n",
                  "echo \"<h1>Hello World!</h1>\" > /var/www/html/index.html"
                ]
              ]
            }
          }
        }
      }
    },
    "NotificationTopic":{
      "Type":"AWS::SNS::Topic",
      "Properties":{
        "Subscription":[
          {
            "Endpoint":{
              "Ref":"OperatorEmail"
            },
            "Protocol":"email"
          }
        ]
      }
    },
    "WebServerGroup":{
      "Type":"AWS::AutoScaling::AutoScalingGroup",
      "Properties":{
        "LaunchTemplate":{
          "LaunchTemplateId":{
            "Ref":"LaunchTemplate"
          },
          "Version":{
            "Fn::GetAtt":[
              "LaunchTemplate",
              "LatestVersionNumber"
            ]
          }
        },
        "MaxSize":"3",
        "MinSize":"1",
        "NotificationConfigurations":[
          {
            "TopicARN":{
              "Ref":"NotificationTopic"
            },
            "NotificationTypes":[
              "autoscaling:EC2_INSTANCE_LAUNCH",
              "autoscaling:EC2_INSTANCE_LAUNCH_ERROR",
              "autoscaling:EC2_INSTANCE_TERMINATE",
              "autoscaling:EC2_INSTANCE_TERMINATE_ERROR"
            ]
          }
        ],
        "TargetGroupARNs":[
          {
            "Ref":"EC2TargetGroup"
          }
        ],
        "VPCZoneIdentifier":{
          "Ref":"Subnets"
        }
      }
    }
  }
}
```

## 模板演练
<a name="example-templates-autoscaling-description"></a>

此模板的第一部分指定了 `Parameters`。必须向每个参数分配一个运行时的值，使 CloudFormation 能够成功预置堆栈。稍后在模板中指定的资源会引用这些值并使用这些数据。
+ `InstanceType`：Amazon EC2 Auto Scaling 预置的 EC2 实例类型。如果未指定，则默认使用 `t3.micro`。
+ `KeyName`：用于允许 SSH 访问实例的现有 EC2 密钥对。
+ `LatestAmiId`：实例的亚马逊机器映像（AMI）。如果未指定，则您的实例将使用由 AWS 维护的 AWS Systems Manager 公有参数，通过 Amazon Linux 2 AMI 启动。有关更多信息，请参阅 *AWS Systems Manager User Guide* 中的 [Finding public parameters](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-finding-public-parameters.html)。
+ `OperatorEmail`：您要发送扩展活动通知的电子邮件地址。
+ `SSHLocation`：可用于通过 SSH 连接到实例的 IP 地址范围。
+ `Subnets`：位于不同可用区的至少两个公有子网。
+ `VPC`：您账户中的虚拟私有云（VPC），其允许公有子网中的资源连接到互联网。
**注意**  
您可以使用默认 VPC 和默认子网来允许实例访问互联网。如果使用您自己的 VPC，请确保它拥有映射到您工作时所在区域的每个可用区的子网。您至少必须具有两个公有子网，且这些子网可用于创建负载均衡器。

此模板的下一个部分指定了 `Resources`。本部分指定堆栈资源及其属性。

[https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-ec2-securitygroup.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-ec2-securitygroup.html) 资源 `ELBSecurityGroup` 
+ `SecurityGroupIngress` 包含一个 TCP 入口规则，该规则允许来自端口 80 上的*所有 IP 地址*（"CidrIp" : "0.0.0.0/0"）的访问。

[https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-ec2-securitygroup.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-ec2-securitygroup.html) 资源 `EC2SecurityGroup` 
+ `SecurityGroupIngress` 包含两个入口规则：1) 允许来自您为 `SSHLocation` 输入参数提供的 IP 地址范围内的 SSH 访问（端口 22）的 TCP 入口规则；2) 允许通过指定负载均衡器的安全组实现的来自负载均衡器的访问的 TCP 入口规则。[GetAtt](resources-section-structure.md#resource-properties-getatt) 函数用于获取具有逻辑名称 `ELBSecurityGroup` 的安全组 ID。

[AWS::ElasticLoadBalancingV2::TargetGroup](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-elasticloadbalancingv2-targetgroup.html) 资源 `EC2TargetGroup`
+ `Port`、`Protocol` 和 `HealthCheckProtocol` 指定 `ApplicationLoadBalancer` 要向其中路由流量以及 Elastic Load Balancing 用于检查 EC2 实例运行状况的 EC2 实例端口（80）和协议（HTTP）。
+ `HealthCheckIntervalSeconds` 指定 EC2 实例的每次运行状况检查之间有 30 秒的时间间隔。`HealthCheckTimeoutSeconds` 定义为 Elastic Load Balancing 等待来自运行状况检查目标响应的时间（本示例中为 15 秒）。超时时间超过之后，Elastic Load Balancing 将标记 EC2 实例运行状况检查为“运行状况不佳”。当 EC2 实例连续三次未通过运行状况检查 (`UnhealthyThresholdCount`) 时，Elastic Load Balancing 会停止将流量路由到该 EC2 实例，直到该实例连续五次运行状况检查均正常 (`HealthyThresholdCount`)。此时，Elastic Load Balancing 认为实例运行状况良好，并再次开始将流量路由到该实例。
+ `TargetGroupAttributes` 将目标组的取消注册延迟值更新为 20 秒。默认情况下，Elastic Load Balancing 会等待 300 秒，才会完成注销过程。

[AWS::ElasticLoadBalancingV2::Listener](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-elasticloadbalancingv2-listener.html) 资源 `ALBListener`
+ `DefaultActions` 指定负载均衡器侦听的端口、负载均衡器转发请求的目标组以及用于路由请求的协议。

[AWS::ElasticLoadBalancingV2::LoadBalancer](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-elasticloadbalancingv2-loadbalancer.html) 资源 `ApplicationLoadBalancer`
+ `Subnets` 将 `Subnets` 输入参数的值作为要在其中创建负载均衡器节点的公有子网列表。
+ `SecurityGroup` 获取安全组的 ID，该安全组充当虚拟防火墙，以便负载均衡器节点控制传入流量。[GetAtt](resources-section-structure.md#resource-properties-getatt) 函数用于获取具有逻辑名称 `ELBSecurityGroup` 的安全组 ID。

[AWS::EC2::LaunchTemplate](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-ec2-launchtemplate.html) 资源 `LaunchTemplate`
+ `ImageId` 将 `LatestAmiId` 输入参数的值作为要使用的 AMI。
+ `KeyName` 将 `KeyName` 输入参数的值作为要使用的 EC2 密钥对。
+ `SecurityGroupIds` 获取逻辑名称为 `EC2SecurityGroup` 的安全组的 ID，该安全组充当虚拟防火墙，以便 EC2 实例控制传入流量。
+ `UserData` 是在实例启动并运行之后运行的配置脚本。在此示例中，脚本安装 Apache 并创建 index.html 文件。

[AWS::SNS::Topic](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-sns-topic.html) 资源 `NotificationTopic`
+ 当有任何扩展活动时，`Subscription` 将 `OperatorEmail` 输入参数的值作为通知收件人的电子邮件地址。

[AWS::AutoScaling::AutoScalingGroup](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-autoscaling-autoscalinggroup.html) 资源 `WebServerGroup`
+ `MinSize` 和 `MaxSize` 设置自动扩缩组中的 EC2 实例的最小和最大数量。
+ `TargetGroupARNs` 使用逻辑名称为 `EC2TargetGroup` 的目标组的 ARN。随着此自动扩缩组的扩展，它会自动向该目标组注册和注销实例。
+ `VPCZoneIdentifier` 将 `Subnets` 输入参数的值作为要在其中创建 EC2 实例的公有子网列表。

## 步骤 1：启动堆栈
<a name="example-templates-autoscaling-launch-stack"></a>

在启动堆栈之前，请检查您是否拥有可以使用以下所有服务的 AWS Identity and Access Management（IAM）权限：Amazon EC2、Amazon EC2 Auto Scaling、AWS Systems Manager、Elastic Load Balancing、Amazon SNS 和 CloudFormation。

以下过程涉及从文件上传示例堆栈模板。在本地计算机上打开文本编辑器并添加其中一个模板。使用文件名 `sampleloadbalancedappstack.template` 保存该文件。

**启动堆栈模板**

1. 登录到 AWS 管理控制台 并打开 CloudFormation 控制台 [https://console.aws.amazon.com/cloudformation](https://console.aws.amazon.com/cloudformation/)。

1. 依次选择**创建堆栈**和**使用新资源（标准）**。

1. 在**指定模板**下，选择**上传模板文件**，然后选择**选择文件**，上传 `sampleloadbalancedappstack.template` 文件。

1. 选择**下一步**。

1. 在**指定堆栈详细信息**页面上，键入堆栈的名称（例如 **SampleLoadBalancedAppStack**）。

1. 在**参数**下，查看堆栈的参数并为没有默认值的所有参数提供值，包括 **OperatorEmail**、**SSHLocation**、**KeyName**、**VPC** 和 **Subnets**。

1. 选择**下一步**两次。

1. 在**审核**页面上，审核并确认设置。

1. 选择**提交**。

   您可以在 CloudFormation 控制台的**状态**列中查看堆栈的状态。CloudFormation 成功创建堆栈后，您将收到 **CREATE\_COMPLETE** 状态。
**注意**  
创建堆栈后，必须先确认订阅，电子邮件地址才能开始接收通知。有关更多信息，请参阅《Amazon EC2 Auto Scaling 用户指南》中的[自动扩缩组扩展时获取 Amazon SNS 通知](https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-sns-notifications.html)。

## 步骤 2：清除示例资源
<a name="example-templates-autoscaling-clean-up"></a>

为确保您无需为未使用的示例资源付费，请删除堆栈。

**删除堆栈**

1. 在 CloudFormation 控制台中，选择 **SampleLoadBalancedAppStack** 堆栈。

1. 选择**删除**。

1. 在确认消息中，选择**删除堆栈**。

   **SampleLoadBalancedAppStack** 的状态更改为 **DELETE\_IN\_PROGRESS**。当 CloudFormation 完成删除堆栈后，它会将从列表中移堆栈除。

使用本演练中的示例模板构建您自己的堆栈模板。有关更多信息，请参阅 *Amazon EC2 Auto Scaling User Guide* 中的 [Tutorial: Set up a scaled and load-balanced application](https://docs.aws.amazon.com/autoscaling/ec2/userguide/tutorial-ec2-auto-scaling-load-balancer.html)。