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

niv*_*ech 27 amazon-ec2 amazon-web-services aws-cloudformation autoscaling

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

EC2 API和管理工具允许启动同一AMI的多个实例,但我无法使用Cloudformation找到如何执行此操作.

wjo*_*dan 22

AWS::EC2::Instance资源不支持MinCount/ MaxCount底层的参数RunInstancesAPI,所以它不可能通过参数传递给这个资源的一个副本创建一个变量数EC2实例.

要根据模板参数在CloudFormation模板中创建可变数量的EC2实例资源,而不是部署Auto Scaling组,有两个选项:

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
Run Code Online (Sandbox Code Playgroud)

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-%>
Run Code Online (Sandbox Code Playgroud)

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

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

为方便起见,这里有一个要点,它为10个变量EC2实例生成了输出YAML .

2.自定义资源

另一种方法是实现直接调用/ API 的自定义资源:RunInstancesTerminateInstances

启动堆栈

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]
Run Code Online (Sandbox Code Playgroud)

  • @user2501165 上面“1a”中的示例模板和命令行指令可以为任意最大数量的实例生成条件。为方便起见,这里有一个为 [10 个可变 EC2 实例] (https://gist.github.com/wjordan/105da42b33667fdf3007f6dc4833208c) 生成的输出 YAML 的要点。 (2认同)

小智 14

我认为原始海报之后的内容是:

"Parameters" : {
    "InstanceCount" : {
        "Description" : "Number of instances to start",
        "Type" : "String"
    },
Run Code Online (Sandbox Code Playgroud)

...

"MyAutoScalingGroup" : {
        "Type" : "AWS::AutoScaling::AutoScalingGroup",
        "Properties" : {
        "AvailabilityZones" : {"Fn::GetAZs" : ""},
        "LaunchConfigurationName" : { "Ref" : "MyLaunchConfiguration" },
        "MinSize" : "1",
        "MaxSize" : "2",
        "DesiredCapacity" : **{ "Ref" : "InstanceCount" }**,
        }
    },
Run Code Online (Sandbox Code Playgroud)

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


hue*_*ois 5

简短的回答是:你做不到.您无法获得完全相同的结果(N个相同的EC2实例,不受自动缩放组的限制).

从控制台启动多个实例与创建具有N个实例所需容量的自动扩展组不同.它只是一个有用的快捷方式,而不必通过相同的EC2创建过程进行N次.它被称为"预约"(与保留实例无关).自动缩放组是一种不同的野兽(即使你最终得到N个相同的EC2实例).

你可以:

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

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

  • 使用前端到CloudFormation模板,比如troposphere,它允许你在函数内写入EC2描述,并调用函数N次(我现在选择).最后,您已经有了一个CloudFormation模板来完成这项工作,但您只编写了一次EC2创建代码.它不是真正的 CloudFormation参数,但在一天结束时,您将获得EC2的动态数量.


Ste*_*pel 3

同时,有许多可用的AWS CloudFormation 示例模板,其中一些模板包括启动多个实例,尽管通常会并行演示其他功能;例如,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"
            }
        ]
    }

},
Run Code Online (Sandbox Code Playgroud)

还有更高级/完整的示例可用,例如用于具有多可用区 Amazon RDS 数据库实例并使用 S3 存储文件内容的高可用 Web 服务器的Drupal 模板,当前配置为允许 1-5 个 Web 服务器实例进行通信到多可用区MySQL Amazon RDS数据库实例并在弹性负载均衡器后面运行,该负载均衡器通过Auto Scaling编排 Web 服务器实例。