2014-01-29 127 views
13

我想創建基本上可以通過以下步驟來描述一個EC2 cloudformation堆疊的一部分:AMI創建圖像作爲cloudformation堆

1.-啓動實例

2.-提供實例

3.-停止實例和出它

創建AMI圖像4.-與創建AMI圖像源推出新實例創建一個自動縮放組。

基本上我可以做1和2在一個cloudformation模板和4在第二個模板。我似乎無法做到的是從雲信息模板中的實例創建AMI映像,這基本上會產生如果我想要刪除堆棧時必須手動刪除AMI的問題。

話雖這麼說,我的問題是:

1.-有沒有辦法從內部cloudformation模板實例創建AMI圖像?

2.-如果1的答案是否定的,是否有辦法添加一個AMI圖像(或任何其他資源)以使其成爲完整堆棧的一部分?

編輯:

只是爲了澄清,我已經解決了創建AMI,並在cloudformation模板使用它的問題,我只是不能創建AMI內cloudformation模板或以某種方式將其添加到創建的堆棧。

正如我在波多黎各的解答發表了評論,我現在要做的是使用ansible劇本基本上有3個步驟:

1.-用cloudformation模板

2:創建創建一個基本實例,使用在第1步創建的實例的AMI,可以創建在第1步創建的實例的AMI

3.-使用第二個cloudformation模板創建堆棧的其餘部分(ELB,autoscaling組等),該模板更新步驟1中創建的一個,並使用在步驟2創建的AMI啓動實例。

這就是我現在如何管理它,但我想知道是否有任何方法來創建一個AMI內部的雲信息模板,或者如果有可能將創建的AMI添加到堆棧(就像告訴堆棧,「嘿,這也屬於你,所以處理它「)。

回答

2
  1. 我想是的。一旦堆棧可以使用「更新堆棧」操作。您需要提供初始堆棧的完整JSON模板+在同一文件中更改您的更改(更改AMI)我會先在測試環境(而不是生產)中運行此操作,因爲我不確定操作對現有的實例。

爲什麼不在最初的cloudformation之外創建AMI,然後在最終的cloudformation模板中使用該AMI?

另一種選擇是編寫一些自動化功能來創建兩個cloudformation堆棧,並且可以在創建AMI後最先刪除第一個。

+0

波多黎各,如果我沒有記錯的話(和我做它現在我不認爲我),你可以修改後創建一個堆棧,通過更新它。在雲計算外部創建AMI的想法是我現在正在處理的。基本上,我使用ansible劇本與3個步驟: 1.-與cloudformation 創建實例2.-與ansible 創建實例的AMI創建3.-堆疊的其餘部分(更新創建的)使用AMI與ansible 創建我的問題實際上指向使得堆棧或作爲的cloudformation步驟的一部分的AMI的一部分。我會更新我的問題以澄清。 – dibits

+0

@dibits得到你。所以我改變了我對數字2的回答。現在我記得有一個「更新堆棧」操作。您需要提供初始堆棧的完整JSON模板+您在同一文件中的更改。 – Rico

+0

如果我明白你的意思,那麼我也只是在cloudformation模板更新AMI的ID,但我不會被合併AMI圖像進棧。也許,以進一步澄清,因爲我做的一切在一個堆棧,我希望能夠刪除棧,並有AMI創建堆棧自動的註銷(因爲它與實例,elbs情況等等)。 – dibits

16

是的,你可以從一個EC2實例的CloudFormation模板內實現Custom Resource調用上創建CreateImage API(並呼籲刪除的DeregisterImageDeleteSnapshot API)來創建一個AMI。

由於AMI的,有時需要很長的時間來創建,拉姆達支持自定義資源將需要重新調用本身如果等待沒有lambda函數超時之前完成。

這裏有一個完整的例子:

Launch Stack

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 
1

對於它的價值,這裏的wjordan的AMIFunction定義in the original answer的Python的變種。在原來的YAML所有其他資源保持不變:

AMIFunction: 
    Type: AWS::Lambda::Function 
    Properties: 
    Handler: index.handler 
    Role: !GetAtt LambdaExecutionRole.Arn 
    Code: 
     ZipFile: !Sub | 
     import logging 
     import cfnresponse 
     import json 
     import boto3 
     from threading import Timer 
     from botocore.exceptions import WaiterError 

     logger = logging.getLogger() 
     logger.setLevel(logging.INFO) 

     def handler(event, context): 

      ec2 = boto3.resource('ec2') 
      physicalId = event['PhysicalResourceId'] if 'PhysicalResourceId' in event else None 

      def success(data={}): 
      cfnresponse.send(event, context, cfnresponse.SUCCESS, data, physicalId) 

      def failed(e): 
      cfnresponse.send(event, context, cfnresponse.FAILED, str(e), physicalId) 

      logger.info('Request received: %s\n' % json.dumps(event)) 

      try: 
      instanceId = event['ResourceProperties']['InstanceId'] 
      if (not instanceId): 
       raise 'InstanceID required' 

      if not 'RequestType' in event: 
       success({'Data': 'Unhandled request type'}) 
       return 

      if event['RequestType'] == 'Delete': 
       if (not physicalId.startswith('ami-')): 
       raise 'Unknown PhysicalId: %s' % physicalId 

       ec2client = boto3.client('ec2') 
       images = ec2client.describe_images(ImageIds=[physicalId]) 
       for image in images['Images']: 
       ec2.Image(image['ImageId']).deregister() 
       snapshots = ([bdm['Ebs']['SnapshotId'] 
           for bdm in image['BlockDeviceMappings'] 
           if 'Ebs' in bdm and 'SnapshotId' in bdm['Ebs']]) 
       for snapshot in snapshots: 
        ec2.Snapshot(snapshot).delete() 

       success({'Data': 'OK'}) 
      elif event['RequestType'] in set(['Create', 'Update']): 
       if not physicalId: # AMI creation has not been requested yet 
       instance = ec2.Instance(instanceId) 
       instance.wait_until_stopped() 

       image = instance.create_image(Name="Automatic from CloudFormation stack ${AWS::StackName}") 

       physicalId = image.image_id 
       else: 
       logger.info('Continuing in awaiting image available: %s\n' % physicalId) 

       ec2client = boto3.client('ec2') 
       waiter = ec2client.get_waiter('image_available') 

       try: 
       waiter.wait(ImageIds=[physicalId], WaiterConfig={'Delay': 30, 'MaxAttempts': 6}) 
       except WaiterError as e: 
       # Request the same event but set PhysicalResourceId so that the AMI is not created again 
       event['PhysicalResourceId'] = physicalId 
       logger.info('Timeout reached, continuing function: %s\n' % json.dumps(event)) 
       lambda_client = boto3.client('lambda') 
       lambda_client.invoke(FunctionName=context.invoked_function_arn, 
             InvocationType='Event', 
             Payload=json.dumps(event)) 
       return 

       success({'Data': 'OK'}) 
      else: 
       success({'Data': 'OK'}) 
      except Exception as e: 
      failed(e) 
    Runtime: python2.7 
    Timeout: 300