【问题标题】:How to create variable number of EC2 instance resources in Cloudformation template?如何在 Cloudformation 模板中创建可变数量的 EC2 实例资源?
【发布时间】:2011-07-12 05:41:11
【问题描述】:

如何根据模板参数在Cloudformation模板中创建可变数量的EC2实例资源?

EC2 API 和管理工具允许启动同一个 AMI 的多个实例,但我找不到使用 Cloudformation 的方法。

【问题讨论】:

标签: amazon-web-services amazon-ec2 autoscaling amazon-cloudformation


【解决方案1】:

同时有很多AWS CloudFormation Sample Templates 可用,其中一些包括启动多个实例,尽管通常同时演示其他功能;例如,AutoScalingKeepAtNSample.template 创建了一个负载平衡、Auto Scaled 示例网站,并被配置为根据此模板摘录为此目的启动 2 个 EC2 实例:

"WebServerGroup": {

    "Type": "AWS::AutoScaling::AutoScalingGroup",
    "Properties": {
        "AvailabilityZones": {
            "Fn::GetAZs": ""
        },
        "LaunchConfigurationName": {
            "Ref": "LaunchConfig"
        },
        "MinSize": "2",
        "MaxSize": "2",
        "LoadBalancerNames": [
            {
                "Ref": "ElasticLoadBalancer"
            }
        ]
    }

},

还有更高级/完整的示例可用,例如Highly Available Web Server with Multi-AZ Amazon RDS database instance and using S3 for storing file content 的 Drupal 模板,当前配置为允许 1-5 个 Web 服务器实例与 Multi-AZ MySQL Amazon RDS 数据库实例通信并在 Elastic Load Balancer 后面运行,后者通过 @987654327 编排 Web 服务器实例@。

【讨论】:

    【解决方案2】:

    我认为最初的海报是这样的:

    "Parameters" : {
        "InstanceCount" : {
            "Description" : "Number of instances to start",
            "Type" : "String"
        },
    

    ...

    "MyAutoScalingGroup" : {
            "Type" : "AWS::AutoScaling::AutoScalingGroup",
            "Properties" : {
            "AvailabilityZones" : {"Fn::GetAZs" : ""},
            "LaunchConfigurationName" : { "Ref" : "MyLaunchConfiguration" },
            "MinSize" : "1",
            "MaxSize" : "2",
            "DesiredCapacity" : **{ "Ref" : "InstanceCount" }**,
            }
        },
    

    ...换句话说,从参数中插入初始实例的数量(容量)。

    【讨论】:

      【解决方案3】:

      使用Ref 函数。

      http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html

      用户定义的变量在配置文件的"Parameters" 部分定义。在配置文件的"Resources" 部分,您可以使用对这些参数的引用来填写值。

      {
          "AWSTemplateFormatVersion": "2010-09-09",
          ...
          "Parameters": {
              "MinNumInstances": {
                  "Type": "Number",
                  "Description": "Minimum number of instances to run.",
                  "Default": "1",
                  "ConstraintDescription": "Must be an integer less than MaxNumInstances."
              },
              "MaxNumInstances": {
                  "Type": "Number",
                  "Description": "Maximum number of instances to run.",
                  "Default": "5",
                  "ConstraintDescription": "Must be an integer greater than MinNumInstances."
              },
              "DesiredNumInstances": {
                  "Type": "Number",
                  "Description": "Number of instances that need to be running before creation is marked as complete in CloudFormation management console.",
                  "Default": "1",
                  "ConstraintDescription": "Must be an integer in the range specified by MinNumInstances..MaxNumInstances."
              }
          },
          "Resources": {
              "MyAutoScalingGroup": {
                  "Type": "AWS::AutoScaling::AutoScalingGroup",
                  "Properties": {
                      ...
                      "MinSize": { "Ref": "MinNumInstances" },
                      "MaxSize": { "Ref": "MaxNumInstances" },
                      "DesiredCapacity": { "Ref": "DesiredNumInstances" },
                      ...
                  },
              },
              ...
          },
          ...
      }
      

      在上面的示例中,{ "Ref": ... } 用于将值填充到模板中。在这种情况下,我们提供整数作为 "MinSize" 和 "MaxSize" 的值。

      【讨论】:

        【解决方案4】:

        简短的回答是:你不能。您无法获得完全相同的结果(N 个相同的 EC2 实例,未由 Auto Scaling 组绑定)。

        从控制台启动多个类似的实例与创建一个具有 N 个实例作为所需容量的 Auto Scaling 组不同。这只是您拥有的一个有用的快捷方式,而不必通过相同的 EC2 创建过程进行 N 次。它被称为“预留”(与预留实例无关)。 Auto Scaling 组是不同的野兽(即使您最终拥有 N 个相同的 EC2 实例)。

        您可以:

        • 复制 (yuk) 模板中的 EC2 资源
        • 使用嵌套模板,该模板将自行创建 EC2,并从您的主堆栈调用它 N 次,每次都使用相同的参数提供给它

        问题是,EC2 实例的数量不会是动态的,它不能是参数。

        • 使用 CloudFormation 模板的前端,例如对流层,它允许您在函数内编写 EC2 描述,并调用该函数 N 次(我现在选择)。最后,您有一个 CloudFormation 模板可以完成这项工作,但您只编写了一次 EC2 创建代码。 这不是 真正的 CloudFormation 参数,但最终,您会获得 EC2 的动态数量。

        【讨论】:

        • 我会支持对流层评论,这是解决此类问题的最有效方法。
        【解决方案5】:

        AWS::EC2::Instance 资源不支持底层RunInstances API 的MinCount/MaxCount 参数,因此无法通过将参数传递给该 API 的单个副本来创建可变数量的 EC2 实例资源。

        根据模板参数在 CloudFormation 模板中创建可变数量的 EC2 实例资源,而不部署 Auto Scaling Group,有两种选择:

        1。条件

        您可以使用Conditions 根据参数创建可变数量的AWS::EC2::Instance 资源。

        这有点冗长(因为您必须使用Fn::Equals),但它确实有效。

        这是一个允许用户指定最多 5 个实例的工作示例:

        Description: Create a variable number of EC2 instance resources.
        Parameters:
          InstanceCount:
            Description: Number of EC2 instances (must be between 1 and 5).
            Type: Number
            Default: 1
            MinValue: 1
            MaxValue: 5
            ConstraintDescription: Must be a number between 1 and 5.
          ImageId:
            Description: Image ID to launch EC2 instances.
            Type: AWS::EC2::Image::Id
            # amzn-ami-hvm-2016.09.1.20161221-x86_64-gp2
            Default: ami-9be6f38c
          InstanceType:
            Description: Instance type to launch EC2 instances.
            Type: String
            Default: m3.medium
            AllowedValues: [ m3.medium, m3.large, m3.xlarge, m3.2xlarge ]
        Conditions:
          Launch1: !Equals [1, 1]
          Launch2: !Not [!Equals [1, !Ref InstanceCount]]
          Launch3: !And
          - !Not [!Equals [1, !Ref InstanceCount]]
          - !Not [!Equals [2, !Ref InstanceCount]]
          Launch4: !Or
          - !Equals [4, !Ref InstanceCount]
          - !Equals [5, !Ref InstanceCount]
          Launch5: !Equals [5, !Ref InstanceCount]
        Resources:
          Instance1:
            Condition: Launch1
            Type: AWS::EC2::Instance
            Properties:
              ImageId: !Ref ImageId
              InstanceType: !Ref InstanceType
          Instance2:
            Condition: Launch2
            Type: AWS::EC2::Instance
            Properties:
              ImageId: !Ref ImageId
              InstanceType: !Ref InstanceType
          Instance3:
            Condition: Launch3
            Type: AWS::EC2::Instance
            Properties:
              ImageId: !Ref ImageId
              InstanceType: !Ref InstanceType
          Instance4:
            Condition: Launch4
            Type: AWS::EC2::Instance
            Properties:
              ImageId: !Ref ImageId
              InstanceType: !Ref InstanceType
          Instance5:
            Condition: Launch5
            Type: AWS::EC2::Instance
            Properties:
              ImageId: !Ref ImageId
              InstanceType: !Ref InstanceType
        

        1a。带有条件的模板预处理器

        作为上述的一种变体,您可以使用像 Ruby 的 Erb 这样的模板预处理器来根据指定的最大值生成上述模板,从而使您的源代码更加紧凑并消除重复:

        <%max = 10-%>
        Description: Create a variable number of EC2 instance resources.
        Parameters:
          InstanceCount:
            Description: Number of EC2 instances (must be between 1 and <%=max%>).
            Type: Number
            Default: 1
            MinValue: 1
            MaxValue: <%=max%>
            ConstraintDescription: Must be a number between 1 and <%=max%>.
          ImageId:
            Description: Image ID to launch EC2 instances.
            Type: AWS::EC2::Image::Id
            # amzn-ami-hvm-2016.09.1.20161221-x86_64-gp2
            Default: ami-9be6f38c
          InstanceType:
            Description: Instance type to launch EC2 instances.
            Type: String
            Default: m3.medium
            AllowedValues: [ m3.medium, m3.large, m3.xlarge, m3.2xlarge ]
        Conditions:
          Launch1: !Equals [1, 1]
          Launch2: !Not [!Equals [1, !Ref InstanceCount]]
        <%(3..max-1).each do |x|
            low = (max-1)/(x-1) <= 1-%>
          Launch<%=x%>: !<%=low ? 'Or' : 'And'%>
        <%  (1..max).each do |i|
              if low && i >= x-%>
          - !Equals [<%=i%>, !Ref InstanceCount]
        <%    elsif !low && i < x-%>
          - !Not [!Equals [<%=i%>, !Ref InstanceCount]]
        <%    end
            end
          end-%>
          Launch<%=max%>: !Equals [<%=max%>, !Ref InstanceCount]
        Resources:
        <%(1..max).each do |x|-%>
          Instance<%=x%>:
            Condition: Launch<%=x%>
            Type: AWS::EC2::Instance
            Properties:
              ImageId: !Ref ImageId
              InstanceType: !Ref InstanceType
        <%end-%>
        

        要将上述源处理为与 CloudFormation 兼容的模板,请运行:

        ruby -rerb -e "puts ERB.new(ARGF.read, nil, '-').result" < template.yml > template-out.yml
        

        为方便起见,这里是为10 variable EC2 instances 生成的输出 YAML 的要点。

        2。自定义资源

        另一种方法是实现一个Custom Resource,它直接调用RunInstances/TerminateInstances API:

        Description: Create a variable number of EC2 instance resources.
        Parameters:
          InstanceCount:
            Description: Number of EC2 instances (must be between 1 and 10).
            Type: Number
            Default: 1
            MinValue: 1
            MaxValue: 10
            ConstraintDescription: Must be a number between 1 and 10.
          ImageId:
            Description: Image ID to launch EC2 instances.
            Type: AWS::EC2::Image::Id
            # amzn-ami-hvm-2016.09.1.20161221-x86_64-gp2
            Default: ami-9be6f38c
          InstanceType:
            Description: Instance type to launch EC2 instances.
            Type: String
            Default: m3.medium
            AllowedValues: [ m3.medium, m3.large, m3.xlarge, m3.2xlarge ]
        Resources:
          EC2Instances:
            Type: Custom::EC2Instances
            Properties:
              ServiceToken: !GetAtt EC2InstancesFunction.Arn
              ImageId: !Ref ImageId
              InstanceType: !Ref InstanceType
              MinCount: !Ref InstanceCount
              MaxCount: !Ref InstanceCount
          EC2InstancesFunction:
            Type: AWS::Lambda::Function
            Properties:
              Handler: index.handler
              Role: !GetAtt LambdaExecutionRole.Arn
              Code:
                ZipFile: !Sub |
                  var response = require('cfn-response');
                  var AWS = require('aws-sdk');
                  exports.handler = function(event, context) {
                    var physicalId = event.PhysicalResourceId || 'none';
                    function success(data) {
                      return response.send(event, context, response.SUCCESS, data, physicalId);
                    }
                    function failed(e) {
                      return response.send(event, context, response.FAILED, e, physicalId);
                    }
                    var ec2 = new AWS.EC2();
                    var instances;
                    if (event.RequestType == 'Create') {
                      var launchParams = event.ResourceProperties;
                      delete launchParams.ServiceToken;
                      ec2.runInstances(launchParams).promise().then((data)=> {
                        instances = data.Instances.map((data)=> data.InstanceId);
                        physicalId = instances.join(':');
                        return ec2.waitFor('instanceRunning', {InstanceIds: instances}).promise();
                      }).then((data)=> success({Instances: instances})
                      ).catch((e)=> failed(e));
                    } else if (event.RequestType == 'Delete') {
                      if (physicalId == 'none') {return success({});}
                      var deleteParams = {InstanceIds: physicalId.split(':')};
                      ec2.terminateInstances(deleteParams).promise().then((data)=>
                        ec2.waitFor('instanceTerminated', deleteParams).promise()
                      ).then((data)=>success({})
                      ).catch((e)=>failed(e));
                    } else {
                      return failed({Error: "In-place updates not supported."});
                    }
                  };
              Runtime: nodejs4.3
              Timeout: 300
          LambdaExecutionRole:
            Type: AWS::IAM::Role
            Properties:
              AssumeRolePolicyDocument:
                Version: '2012-10-17'
                Statement:
                - Effect: Allow
                  Principal: {Service: [lambda.amazonaws.com]}
                  Action: ['sts:AssumeRole']
              Path: /
              ManagedPolicyArns:
              - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
              Policies:
              - PolicyName: EC2Policy
                PolicyDocument:
                  Version: '2012-10-17'
                  Statement:
                    - Effect: Allow
                      Action:
                      - 'ec2:RunInstances'
                      - 'ec2:DescribeInstances'
                      - 'ec2:DescribeInstanceStatus'
                      - 'ec2:TerminateInstances'
                      Resource: ['*']
        Outputs:
          Instances:
            Value: !Join [',', !GetAtt EC2Instances.Instances]
        

        【讨论】:

        • 如果自动缩放组不是一个选项(不是我的一个用例),那么这个自定义资源非常方便!感谢分享!
        • @wjordan 您能否给出 10 个实例的条件示例?我知道您在“1. 条件”中最多有 5 个。赞赏。
        • 这太棒了,我正在寻找类似的实现。谢谢!
        • @user2501165 上面“1a”中的示例模板和命令行指令可以为任意最大实例数生成条件。为方便起见,这里是为10 variable EC2 instances 生成的输出 YAML 的要点。
        • 刚刚修复了选项 1 / 1a 模板代码条件中的一个逻辑错误,对于低编号 (!Not) 条件,!Or 函数应该是 !And。感谢@gaurav-goel 和@krish 指出这一点。
        猜你喜欢
        • 1970-01-01
        • 2017-07-15
        • 1970-01-01
        • 2014-02-18
        • 2013-02-22
        • 2020-03-07
        • 2012-08-09
        • 1970-01-01
        • 2021-04-22
        相关资源
        最近更新 更多