我想创建一个EC2 cloudformation堆栈,基本上可以按照以下步骤进行描述:
1.-启动实例
2.-提供实例
3.-停止实例并从中创建AMI图像
4.-创建一个自动缩放组,将创建的AMI映像作为源以启动新实例.
基本上我可以在一个cloudformation模板中执行1和2,在第二个模板中执行4.我似乎无法做的是从云信息模板中的实例创建AMI图像,如果我想删除堆栈,基本上会产生必须手动删除AMI的问题.
话虽这么说,我的问题是:
1.-有没有办法从cloudformation模板中的实例创建AMI图像?
2.-如果1的答案为否,是否有办法添加AMI图像(或任何其他资源)使其成为完成堆栈的一部分?
编辑:
为了澄清,我已经解决了创建AMI并在云信息模板中使用它的问题,我只是无法创建AMI INSIDE的cloudformation模板或以某种方式将其添加到创建的堆栈中.
正如我对Rico的回答所评论的那样,我现在所做的是使用一个基本上有3个步骤的ansible playbook:
1.-使用cloudformation模板创建基本实例
2.-使用ansible创建在步骤1中创建的实例的AMI
3.-使用第二个云形式模板创建堆栈的其余部分(ELB,自动扩展组等),该模板更新在步骤1中创建的模板,并使用在步骤2中创建的AMI来启动实例.
这就是我现在管理它的方式,但我想知道是否有任何方法可以创建AMI INSIDE一个cloudformation模板,或者是否可以将创建的AMI添加到堆栈中(类似于告诉堆栈,"嘿,这属于你也是,所以处理它").
对于它的价值,这里是wjordan 在原始答案中AMIFunction
定义的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
是的,您可以通过实现在create上调用CreateImage API 的自定义资源(并在删除时调用DeregisterImage和DeleteSnapshot API)从CloudFormation模板中的EC2实例创建AMI .
由于AMI有时需要很长时间才能创建,因此如果在Lambda函数超时之前等待尚未完成,则Lambda支持的自定义资源将需要重新调用自身.
这是一个完整的例子:
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