Create AMI image as part of a cloudformation stack

后端 未结 4 576
温柔的废话
温柔的废话 2020-12-08 14:42

I want to create an EC2 cloudformation stack which basically can be described in the following steps:

1.- Launch instance

2.- Provision the instance

4条回答
  •  南方客
    南方客 (楼主)
    2020-12-08 15:10

    Yes, you can create an AMI from an EC2 instance within a CloudFormation template by implementing a Custom Resource that calls the CreateImage API on create (and calls the DeregisterImage and DeleteSnapshot APIs on delete).

    Since AMIs can sometimes take a long time to create, a Lambda-backed Custom Resource will need to re-invoke itself if the wait has not completed before the Lambda function times out.

    Here's a complete example:

    Description: Create an AMI from an EC2 instance.
    Parameters:
      ImageId:
        Description: Image ID for base EC2 instance.
        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:
      # Completes when the instance is fully provisioned and ready for AMI creation.
      AMICreate:
        Type: AWS::CloudFormation::WaitCondition
        CreationPolicy:
          ResourceSignal:
            Timeout: PT10M
      Instance:
        Type: AWS::EC2::Instance
        Properties:
          ImageId: !Ref ImageId
          InstanceType: !Ref InstanceType
          UserData:
            "Fn::Base64": !Sub |
              #!/bin/bash -x
              yum -y install mysql # provisioning example
              /opt/aws/bin/cfn-signal \
                -e $? \
                --stack ${AWS::StackName} \
                --region ${AWS::Region} \
                --resource AMICreate
              shutdown -h now
      AMI:
        Type: Custom::AMI
        DependsOn: AMICreate
        Properties:
          ServiceToken: !GetAtt AMIFunction.Arn
          InstanceId: !Ref Instance
      AMIFunction:
        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) {
                console.log("Request received:\n", JSON.stringify(event));
                var physicalId = event.PhysicalResourceId;
                function success(data) {
                  return response.send(event, context, response.SUCCESS, data, physicalId);
                }
                function failed(e) {
                  return response.send(event, context, response.FAILED, e, physicalId);
                }
                // Call ec2.waitFor, continuing if not finished before Lambda function timeout.
                function wait(waiter) {
                  console.log("Waiting: ", JSON.stringify(waiter));
                  event.waiter = waiter;
                  event.PhysicalResourceId = physicalId;
                  var request = ec2.waitFor(waiter.state, waiter.params);
                  setTimeout(()=>{
                    request.abort();
                    console.log("Timeout reached, continuing function. Params:\n", JSON.stringify(event));
                    var lambda = new AWS.Lambda();
                    lambda.invoke({
                      FunctionName: context.invokedFunctionArn,
                      InvocationType: 'Event',
                      Payload: JSON.stringify(event)
                    }).promise().then((data)=>context.done()).catch((err)=>context.fail(err));
                  }, context.getRemainingTimeInMillis() - 5000);
                  return request.promise().catch((err)=>
                    (err.code == 'RequestAbortedError') ?
                      new Promise(()=>context.done()) :
                      Promise.reject(err)
                  );
                }
                var ec2 = new AWS.EC2(),
                    instanceId = event.ResourceProperties.InstanceId;
                if (event.waiter) {
                  wait(event.waiter).then((data)=>success({})).catch((err)=>failed(err));
                } else if (event.RequestType == 'Create' || event.RequestType == 'Update') {
                  if (!instanceId) { failed('InstanceID required'); }
                  ec2.waitFor('instanceStopped', {InstanceIds: [instanceId]}).promise()
                  .then((data)=>
                    ec2.createImage({
                      InstanceId: instanceId,
                      Name: event.RequestId
                    }).promise()
                  ).then((data)=>
                    wait({
                      state: 'imageAvailable',
                      params: {ImageIds: [physicalId = data.ImageId]}
                    })
                  ).then((data)=>success({})).catch((err)=>failed(err));
                } else if (event.RequestType == 'Delete') {
                  if (physicalId.indexOf('ami-') !== 0) { return success({});}
                  ec2.describeImages({ImageIds: [physicalId]}).promise()
                  .then((data)=>
                    (data.Images.length == 0) ? success({}) :
                    ec2.deregisterImage({ImageId: physicalId}).promise()
                  ).then((data)=>
                    ec2.describeSnapshots({Filters: [{
                      Name: 'description',
                      Values: ["*" + physicalId + "*"]
                    }]}).promise()
                  ).then((data)=>
                    (data.Snapshots.length === 0) ? success({}) :
                    ec2.deleteSnapshot({SnapshotId: data.Snapshots[0].SnapshotId}).promise()
                  ).then((data)=>success({})).catch((err)=>failed(err));
                }
              };
          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
          - arn:aws:iam::aws:policy/service-role/AWSLambdaRole
          Policies:
          - PolicyName: EC2Policy
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                  - 'ec2:DescribeInstances'
                  - 'ec2:DescribeImages'
                  - 'ec2:CreateImage'
                  - 'ec2:DeregisterImage'
                  - 'ec2:DescribeSnapshots'
                  - 'ec2:DeleteSnapshot'
                  Resource: ['*']
    Outputs:
      AMI:
        Value: !Ref AMI
    

提交回复
热议问题