amazon-ec2 - son - tipos de cloud computing ejemplos
Crea una imagen AMI como parte de una pila de formación de nubes. (4)
- No.
- Supongo que sí. Una vez que la pila puede utilizar la operación "Actualizar pila". Debe proporcionar la plantilla JSON completa de la pila inicial + sus cambios en el mismo archivo (AMI modificado). Primero ejecutaría esto en un entorno de prueba (no de producción), ya que no estoy realmente seguro de lo que hace la operación al Instancias existentes.
¿Por qué no crea una AMI inicialmente fuera de la formación de nubes y luego la utiliza en la plantilla final de la nube?
Otra opción es escribir algo de automatización para crear dos pilas de información en la nube y puede eliminar la primera una vez que finalice la AMI que ha creado.
Quiero crear una pila de información en la nube EC2 que básicamente se puede describir en los siguientes pasos:
1.- Lanzar instancia.
2.- Aprovisionar la instancia.
3.- Detenga la instancia y cree una imagen AMI a partir de ella.
4.- Cree un grupo de autoescala con la imagen AMI creada como fuente para iniciar nuevas instancias.
Básicamente puedo hacer 1 y 2 en una plantilla de formación de nubes y 4 en una segunda plantilla. Lo que no puedo hacer es crear una imagen AMI desde una instancia dentro de una plantilla de formación de nubes, lo que básicamente genera el problema de tener que eliminar manualmente la AMI si quiero eliminar la pila.
Dicho esto, mis preguntas son:
1.- ¿Hay una manera de crear una imagen AMI desde una instancia DENTRO de la plantilla de formación de nubes?
2.- Si la respuesta a 1 es no, ¿hay una manera de agregar una imagen AMI (o cualquier otro recurso para esa materia) para que sea parte de una pila completa?
EDITAR:
Solo para aclarar, ya resolví el problema de crear la AMI y usarla en una plantilla de información en la nube, simplemente no puedo crear la AMI DENTRO de la plantilla de la nube o agregarla de alguna manera a la pila creada.
Como comenté en la respuesta de Rico, lo que hago ahora es usar un libro de jugabilidad ansible que básicamente tiene 3 pasos:
1.- Crear una instancia base con una plantilla de formación de nubes.
2.- Cree, utilizando ansible, una AMI de la instancia creada en el paso 1
3.- Cree el resto de la pila (ELB, grupos de autoescalado, etc.) con una segunda plantilla de formación de nubes que actualice la creada en el paso 1 y que use la AMI creada en el paso 2 para iniciar instancias.
Así es como lo gestiono ahora, pero quería saber si hay alguna forma de crear un AMI DENTRO de una plantilla de formación de nubes o si es posible agregar el AMI creado a la pila (algo así como decirle a la pila, "Oye, esto pertenece a Tú también, así que manéjalo ").
Para lo que vale, aquí está la variante de Python de la definición AMIFunction de AMIFunction
en la respuesta original . Todos los otros recursos en el yaml original permanecen sin cambios:
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
Sí, puede crear una AMI a partir de una instancia de EC2 dentro de una plantilla de CloudFormation implementando un recurso personalizado que llama a la API CreateImage en la creación (y llama a las API DeleteSnapshot y DeleteSnapshot en la eliminación).
Como las AMI a veces pueden tardar mucho tiempo en crearse, un recurso personalizado respaldado por Lambda tendrá que volver a invocarse a sí mismo si la espera no se ha completado antes de que la función Lambda se agote.
Aquí hay un ejemplo completo:
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
Si bien la solución de @ wjdordan es buena para casos de uso simples, la actualización de los Datos del usuario no actualizará la AMI.
(DESCARGO DE RESPONSABILIDAD: Soy el autor original) cloudformation-ami
tiene como objetivo permitirle declarar las AMI en CloudFormation que se pueden crear, actualizar y eliminar de forma confiable. Uso de cloudformation-ami
Puede declarar AMI personalizadas de la siguiente manera:
MyAMI:
Type: Custom::AMI
Properties:
ServiceToken: !ImportValue AMILambdaFunctionArn
Image:
Name: my-image
Description: some description for the image
TemplateInstance:
ImageId: ami-467ca739
IamInstanceProfile:
Arn: arn:aws:iam::1234567890:instance-profile/MyProfile-ASDNSDLKJ
UserData:
Fn::Base64: !Sub |
#!/bin/bash -x
yum -y install mysql # provisioning example
# Signal that the instance is ready
INSTANCE_ID=`wget -q -O - http://169.254.169.254/latest/meta-data/instance-id`
aws ec2 create-tags --resources $INSTANCE_ID --tags Key=UserDataFinished,Value=true --region ${AWS::Region}
KeyName: my-key
InstanceType: t2.nano
SecurityGroupIds:
- sg-d7bf78b0
SubnetId: subnet-ba03aa91
BlockDeviceMappings:
- DeviceName: "/dev/xvda"
Ebs:
VolumeSize: ''10''
VolumeType: gp2