Automatizaci贸n de la gesti贸n de AWS EC2 con Python y Boto3

    Introducci贸n

    En este art铆culo, demostrar茅 el uso de Python junto con el kit de desarrollo de software (SDK) Boto3 Amazon Web Services (AWS) que permite a las personas con conocimientos de programaci贸n en Python utilizar las intrincadas API REST de AWS para administrar sus recursos en la nube. Debido a la inmensidad de la API REST de AWS y los servicios en la nube asociados, me centrar茅 solo en el servicio AWS Elastic Cloud Compute (EC2).

    Estos son los temas que cubrir茅:

    • Iniciar una instancia EC2
    • Detener una instancia EC2
    • Terminar una instancia EC2
    • Hacer una copia de seguridad de una instancia EC2 creando una imagen
    • Crear una instancia EC2 a partir de una imagen
    • Programaci贸n de copias de seguridad y limpieza mediante cron en un servidor y AWS Lambda

    Configuraci贸n de dependencias y entorno

    Para comenzar, necesitar茅 crear un usuario en mi cuenta de AWS que tenga acceso program谩tico a las API REST. Para simplificar, otorgar茅 derechos de administrador a este usuario, pero tenga en cuenta que es solo para simplificar la creaci贸n de este tutorial. Si est谩 siguiendo, debe consultar las pol铆ticas de seguridad de TI de su organizaci贸n antes de utilizar este usuario en un entorno de producci贸n.

    Paso 1: En mi consola de AWS, debo ir a la secci贸n IAM en el men煤 de servicios, luego hacer clic en el enlace Usuarios y finalmente hacer clic en el bot贸n Agregar usuario que me lleva a la pantalla que se muestra a continuaci贸n. En esta pantalla le doy al usuario el nombre “boto3-user” y marco la casilla de Acceso program谩tico antes de hacer clic en el bot贸n siguiente.

    Paso 2: En la pantalla de permisos, hago clic en el mosaico Adjuntar pol铆ticas existentes directamente y luego selecciono la casilla de verificaci贸n Acceso del administrador antes de hacer clic en Siguiente, como se muestra a continuaci贸n.

    Paso 3: haga clic en el siguiente, ya que no agregar茅 etiquetas opcionales.

    Paso 4: Reviso el usuario que se va a crear y luego hago clic en Crear usuario.

    Paso 5: Finalmente, descargo las credenciales como un archivo CSV y las guardo.

    A continuaci贸n, necesito instalar las bibliotecas necesarias de Python 3 localmente dentro de un entorno virtual, as铆:

    $ python -m venv venv
    $ source venv/bin/activate
    (venv)$ pip install boto3 pprint awscli
    

    Por 煤ltimo, configuro las credenciales para la biblioteca boto3 usando la biblioteca awscli asegur谩ndome de agregar las credenciales para la clave de acceso y la clave secreta que descargu茅 en el paso 5 anterior.

    $ aws configure
    AWS Access Key ID [****************3XRQ]: **************
    AWS Secret Access Key [****************UKjF]: ****************
    Default region name [None]:
    Default output format [None]:
    

    Creaci贸n de una instancia EC2 para trabajar

    En esta secci贸n, voy a repasar c贸mo crear una sesi贸n boto3 espec铆fica de la regi贸n de AWS y c贸mo crear una instancia de un cliente EC2 utilizando el objeto de sesi贸n activa. Luego, utilizando ese cliente EC2 boto3, interactuar茅 con las instancias EC2 de esa regi贸n que administran el inicio, el apagado y la terminaci贸n.

    Para crear una instancia EC2 para este art铆culo, realizo los siguientes pasos:

    Paso 1: Hago clic en el enlace EC2 dentro del men煤 Servicios para abrir el Panel de EC2 y luego hago clic en el bot贸n Iniciar instancia en el medio de la pantalla.

    Paso 2: En la p谩gina Choose Amazon Machine Image (AMI), hago clic en el bot贸n Seleccionar junto a la AMI de Amazon Linux.

    Paso 3: Acepte el tipo de instancia t2.micro predeterminado y haga clic en el bot贸n Revisar y lanzar.

    Paso 4: En la p谩gina de revisi贸n, ampl铆o la secci贸n Etiquetas y hago clic en Editar etiquetas para agregar etiquetas para el nombre y la copia de seguridad, luego hago clic en Iniciar revisi贸n y volver a iniciar para volver a la p谩gina de revisi贸n antes de finalmente hacer clic en el bot贸n Iniciar para iniciar la instancia.

    Ahora tengo una instancia EC2 en ejecuci贸n, como se muestra a continuaci贸n.

    Sesi贸n y cliente de Boto3

    隆Por fin puedo empezar a escribir c贸digo! Comienzo creando un archivo vac铆o, un m贸dulo de Python, llamado awsutils.py y en la parte superior importo la biblioteca y boto3luego defino una funci贸n que crear谩 un objeto de sesi贸n espec铆fico de la regi贸n .

    # awsutils
    
    import boto3
    
    def get_session(region):
        return boto3.session.Session(region_name=region)
    

    Si enciendo mi int茅rprete de Python e importo el m贸dulo que acabo de crear, puedo usar la nueva get_sessionfunci贸n para crear una sesi贸n en la misma regi贸n que mi instancia EC2, luego crear una instancia de un objeto EC2.Client desde ella, as铆:

    >>> import awsutils
    >>> session = awsutils.get_session('us-east-1')
    >>> client = session.client('ec2')
    

    Luego puedo usar este objeto de cliente EC2 para obtener una descripci贸n detallada de la instancia que se usa pprintpara hacer las cosas un poco m谩s f谩ciles de ver el resultado de llamar describe_instancesal clientobjeto.

    >>> import pprint
    >>> pprint.pprint(client.describe_instances())
    ...
    

    Estoy omitiendo el resultado porque es bastante detallado, pero s茅 que contiene un diccionario con una Reservationsentrada, que es una lista de datos que describen las instancias EC2 en esa regi贸n y ResponseMetadatasobre la solicitud que se acaba de realizar a la API REST de AWS.

    Recuperar detalles de la instancia EC2

    Tambi茅n puedo usar este mismo describe_instancesm茅todo junto con un Filterpar谩metro para filtrar la selecci贸n por valores de etiqueta. Por ejemplo, si quiero obtener mi instancia creada recientemente con la etiqueta Name con un valor de ‘demo-instance’, se ver铆a as铆:

    >>> demo = client.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': ['demo-instance']}])
    >>> pprint.pprint(demo)
    ...
    

    Hay muchas formas de filtrar la salida de describe_instancesy los remito a los documentos oficiales para obtener m谩s detalles.

    Inicio y detenci贸n de una instancia EC2

    Para detener la instancia de demostraci贸n, utilizo el stop_instancesm茅todo del clientobjeto, que he instanciado previamente, proporcion谩ndole el ID de la instancia como un par谩metro de lista de entrada 煤nica para el InstanceIdsargumento como se muestra a continuaci贸n:

    >>> instance_id = demo['Reservations'][0]['Instances'][0]['InstanceId']
    >>> instance_id
    'i-0c462c48bc396bdbb'
    >>> pprint.pprint(client.stop_instances(InstanceIds=[instance_id]))
    {'ResponseMetadata': {'HTTPHeaders': {'content-length': '579',
                                          'content-type': 'text/xml;charset=UTF-8',
                                          'date': 'Sat, 22 Dec 2018 19:26:30 GMT',
                                          'server': 'AmazonEC2'},
                          'HTTPStatusCode': 200,
                          'RequestId': 'e04a4a64-74e4-442f-8293-261f2ca9433d',
                          'RetryAttempts': 0},
     'StoppingInstances': [{'CurrentState': {'Code': 64, 'Name': 'stopping'},
                            'InstanceId': 'i-0c462c48bc396bdbb',
                            'PreviousState': {'Code': 16, 'Name': 'running'}}]
    

    La salida del 煤ltimo comando indica que la llamada al m茅todo est谩 deteniendo la instancia. Si vuelvo a recuperar la instancia de demostraci贸n e imprimo el State, ahora veo que est谩 detenido.

    >>> demo = client.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': ['demo-instance']}])
    >>> demo['Reservations'][0]['Instances'][0]['State']
    {'Code': 80, 'Name': 'stopped'}
    

    Para iniciar la copia de seguridad de la misma instancia, hay un m茅todo de complemento llamado start_instancesque funciona de manera similar al stop_instancesm茅todo que demuestro a continuaci贸n.

    >>> pprint.pprint(client.start_instances(InstanceIds=[instance_id]))
    {'ResponseMetadata': {'HTTPHeaders': {'content-length': '579',
                                          'content-type': 'text/xml;charset=UTF-8',
                                          'date': 'Sat, 22 Dec 2018 19:37:02 GMT',
                                          'server': 'AmazonEC2'},
                          'HTTPStatusCode': 200,
                          'RequestId': '21c65902-6665-4137-9023-43ac89f731d9',
                          'RetryAttempts': 0},
     'StartingInstances': [{'CurrentState': {'Code': 0, 'Name': 'pending'},
                            'InstanceId': 'i-0c462c48bc396bdbb',
                            'PreviousState': {'Code': 80, 'Name': 'stopped'}}]}
    

    El resultado inmediato del comando es que est谩 pendiente de inicio. Ahora, cuando vuelvo a buscar la instancia e imprimo su estado, muestra que se est谩 ejecutando nuevamente.

    >>> demo = client.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': ['demo-instance']}])
    >>> demo['Reservations'][0]['Instances'][0]['State']
    {'Code': 16, 'Name': 'running'}
    

    Enfoque alternativo para recuperar, iniciar y detener

    Adem谩s de la EC2.Clientclase con la que he estado trabajando hasta ahora, tambi茅n hay una clase EC2.Instance que es 煤til en casos como este en el que solo necesito preocuparme por una instancia a la vez.

    A continuaci贸n, utilizo el sessionobjeto generado previamente para obtener un objeto de recurso EC2, que luego puedo usar para recuperar y crear una instancia de un Instanceobjeto para mi instancia de demostraci贸n.

    >>> ec2 = session.resource('ec2')
    >>> instance = ec2.Instance(instance_id)
    

    En mi opini贸n, un beneficio importante de usar la Instanceclase es que luego est谩 trabajando con objetos reales en lugar de una representaci贸n de diccionario de un punto en el tiempo de la instancia, pero pierde el poder de poder realizar acciones en m煤ltiples instancias a la vez que el EC2.Clientla clase proporciona.

    Por ejemplo, para ver el estado de la instancia de demostraci贸n que acabo de crear anteriormente, es tan simple como esto:

    >>> instance.state
    {'Code': 16, 'Name': 'running'}
    

    La Instanceclase tiene muchos m茅todos 煤tiles, dos de los cuales son starty stopque usar茅 para iniciar y detener mis instancias, as铆:

    >>> pprint.pprint(instance.stop())
    {'ResponseMetadata': {'HTTPHeaders': {'content-length': '579',
                                          'content-type': 'text/xml;charset=UTF-8',
                                          'date': 'Sat, 22 Dec 2018 19:58:25 GMT',
                                          'server': 'AmazonEC2'},
                          'HTTPStatusCode': 200,
                          'RequestId': 'a2f76028-cbd2-4727-be3e-ae832b12e1ff',
                          'RetryAttempts': 0},
     'StoppingInstances': [{'CurrentState': {'Code': 64, 'Name': 'stopping'},
                            'InstanceId': 'i-0c462c48bc396bdbb',
                            'PreviousState': {'Code': 16, 'Name': 'running'}}]}
    

    Despu茅s de esperar aproximadamente un minuto para que se detenga por completo … luego verifico el estado nuevamente:

    >>> instance.state
    {'Code': 80, 'Name': 'stopped'}
    

    Ahora puedo iniciarlo de nuevo.

    >>> pprint.pprint(instance.start())
    {'ResponseMetadata': {'HTTPHeaders': {'content-length': '579',
                                          'content-type': 'text/xml;charset=UTF-8',
                                          'date': 'Sat, 22 Dec 2018 20:01:01 GMT',
                                          'server': 'AmazonEC2'},
                          'HTTPStatusCode': 200,
                          'RequestId': '3cfc6061-5d64-4e52-9961-5eb2fefab2d8',
                          'RetryAttempts': 0},
     'StartingInstances': [{'CurrentState': {'Code': 0, 'Name': 'pending'},
                            'InstanceId': 'i-0c462c48bc396bdbb',
                            'PreviousState': {'Code': 80, 'Name': 'stopped'}}]}
    

    Luego revisando el estado nuevamente despu茅s de un rato …

    >>> instance.state
    {'Code': 16, 'Name': 'running'}
    

    Creaci贸n de una imagen de respaldo de una instalaci贸n EC2.

    Un tema importante en la administraci贸n de servidores es la creaci贸n de copias de seguridad a las que recurrir en caso de que un servidor se da帽e. En esta secci贸n, voy a demostrar c贸mo crear una copia de seguridad de la imagen de m谩quina de Amazon (AMI) de mi instancia de demostraci贸n, que luego AWS almacenar谩 en su Simple Storage Service (S3). Esto se puede usar m谩s tarde para recrear esa instancia EC2, al igual que us茅 la AMI inicial para crear la instancia de demostraci贸n.

    Para comenzar, mostrar茅 c贸mo usar la EC2.Clientclase y su create_imagem茅todo para crear una imagen AMI de la instancia de demostraci贸n proporcionando el ID de la instancia y un nombre descriptivo para la instancia.

    >>> import datetime
    >>> date = datetime.datetime.utcnow().strftime('%Y%m%d')
    >>> date
    '20181221'
    >>> name = f"InstanceID_{instance_id}_Image_Backup_{date}"
    >>> name
    'InstanceID_i-0c462c48bc396bdbb_Image_Backup_20181221'
    >>> name = f"InstanceID_{instance_id}_Backup_Image_{date}"
    >>> name
    'InstanceID_i-0c462c48bc396bdbb_Backup_Image_20181221'
    >>> pprint.pprint(client.create_image(InstanceId=instance_id, Name=name))
    {'ImageId': 'ami-00d7c04e2b3b28e2d',
     'ResponseMetadata': {'HTTPHeaders': {'content-length': '242',
                                          'content-type': 'text/xml;charset=UTF-8',
                                          'date': 'Sat, 22 Dec 2018 20:13:55 GMT',
                                          'server': 'AmazonEC2'},
                          'HTTPStatusCode': 200,
                          'RequestId': '7ccccb1e-91ff-4753-8fc4-b27cf43bb8cf',
                          'RetryAttempts': 0}}
    

    De manera similar, puedo usar el m茅todo de la Instanceclase create_imagepara realizar la misma tarea, que devuelve una instancia de una EC2.Imageclase que es similar a la EC2.Instanceclase.

    >>> image = instance.create_image(Name=name + '_2')
    

    Etiquetado de im谩genes e instancias EC2

    Una caracter铆stica muy poderosa, pero extremadamente simple, de las instancias EC2 y las im谩genes AMI es la capacidad de agregar etiquetas personalizadas. Puede agregar etiquetas tanto a trav茅s de la consola de administraci贸n de AWS, como mostr茅 al crear la instancia de demostraci贸n con las etiquetas Name y BackUp, como tambi茅n mediante programaci贸n con boto3 y la API REST de AWS.

    Como EC2.Instancetodav铆a tengo un objeto flotando en la memoria de mi int茅rprete de Python, lo usar茅 para mostrar las etiquetas de la instancia de demostraci贸n.

    >>> instance.tags
    [{'Key': 'BackUp', 'Value': ''}, {'Key': 'Name', 'Value': 'demo-instance'}]
    

    Tanto el EC2.Instancey las EC2.Imageclases tienen un conjunto id茅ntico funcionamiento de create_tagsm茅todos para agregar etiquetas a sus recursos representados. A continuaci贸n, demuestro c贸mo agregar una etiqueta RemoveOn a la imagen creada anteriormente, que se combina con una fecha en la que debe eliminarse. El formato de fecha utilizado es “AAAAMMDD”.

    >>> image.create_tags(Tags=[{'Key': 'RemoveOn', 'Value': remove_on}])
    [ec2.Tag(resource_id='ami-081c72fa60c8e2d58', key='RemoveOn', value="20181222")]
    

    Nuevamente, se puede lograr lo mismo con la EC2.Clientclase al proporcionar una lista de ID de recursos, pero con el cliente puede etiquetar tanto las im谩genes como las instancias EC2 al mismo tiempo si lo desea especificando sus ID en el par谩metro Resource de la create_tagsfunci贸n, as铆 :

    >>> pprint.pprint(client.create_tags(Resources=['ami-00d7c04e2b3b28e2d'], Tags=[{'Key': 'RemoveOn', 'Value': remove_on}]))
    {'ResponseMetadata': {'HTTPHeaders': {'content-length': '221',
                                          'content-type': 'text/xml;charset=UTF-8',
                                          'date': 'Sat, 22 Dec 2018 20:52:39 GMT',
                                          'server': 'AmazonEC2'},
                          'HTTPStatusCode': 200,
                          'RequestId': '645b733a-138c-42a1-9966-5c2eb0ca3ba3',
                          'RetryAttempts': 0}}
    

    Creaci贸n de una instancia EC2 a partir de una imagen de respaldo

    Me gustar铆a comenzar esta secci贸n d谩ndoles algo en qu茅 pensar. P贸ngase en la mentalidad inc贸moda de un administrador de sistemas, o peor a煤n, un desarrollador que finge ser un administrador de sistemas porque el producto en el que est谩n trabajando no tiene uno (advertencia … ese soy yo), y uno de sus servidores EC2 tiene se corrompe.

    隆Eeek! Es hora de revolver … ahora necesita averiguar qu茅 tipo de sistema operativo, tama帽o y servicios se estaban ejecutando en el servidor inactivo … buscar a tientas la configuraci贸n e instalaci贸n del servidor base, m谩s cualquier aplicaci贸n que pertenezca a 茅l, y rezar todo aparece correctamente.

    隆Uf! Tome un respiro y rel谩jese porque estoy a punto de mostrarle c贸mo volver a ponerse en marcha r谩pidamente, adem谩s … alerta de spoiler … Voy a extraer estos comandos 煤nicos del int茅rprete de Python en un conjunto de scripts que funcione en el final para que lo modifiques m谩s y lo uses.

    Bien, con ese ejercicio mental fuera del camino, d茅jame volver al trabajo. Para crear una instancia EC2 a partir de una ID de imagen, utilizo el m茅todo de la EC2.Clientclase run_instancesy especifico la cantidad de instancias que se iniciar谩n y el tipo de instancia que se ejecutar谩.

    >>> pprint.pprint(client.run_instances(ImageId='ami-081c72fa60c8e2d58', MinCount=1, MaxCount=1, InstanceType="t2.micro"))
    ...
    

    Estoy omitiendo la salida nuevamente debido a su verbosidad. Eche un vistazo a los documentos oficiales del m茅todo run_instances , ya que hay muchos par谩metros entre los que elegir para personalizar exactamente c贸mo ejecutar la instancia.

    Eliminar im谩genes de respaldo

    Idealmente, estar铆a haciendo copias de seguridad en un intervalo bastante frecuente (es decir, diariamente como m铆nimo) y junto con todas estas copias de seguridad vienen tres cosas, una de las cuales es bastante buena y las otras dos son algo problem谩ticas. En el lado bueno de las cosas, estoy haciendo instant谩neas de los estados conocidos de mi servidor EC2, lo que me da un punto en el tiempo al que recurrir si las cosas van mal. Sin embargo, en el lado malo, estoy creando desorden en mis dep贸sitos S3 y acumulando cargos con cada copia de seguridad adicional que guardo en el almacenamiento.

    Una forma de mitigar las desventajas del desorden y el aumento de los costos de almacenamiento es eliminar las im谩genes de respaldo despu茅s de que haya transcurrido un tiempo predeterminado y ah铆 es donde las etiquetas que cre茅 anteriormente me salvar谩n. Puedo consultar mis im谩genes de respaldo de EC2 y ubicar las que tienen una etiqueta RemoveOn particular y luego eliminarlas.

    Puedo comenzar usando el describe_imagesm茅todo en la EC2.Clientinstancia de la clase junto con un filtro para la etiqueta ‘RemoveOn’ para obtener todas las im谩genes que etiquet茅 para eliminar en una fecha determinada.

    >>> remove_on = '201812022'
    >>> images = client.describe_images(Filters=[{'Name': 'tag:RemoveOn', 'Values': [remove_on]}])
    

    A continuaci贸n, repito todas las im谩genes y llamo al m茅todo del cliente deregister_imagepas谩ndole el ID de la imagen iterada y listo, no m谩s imagen.

    >>> remove_on = '201812022'
    >>> for img in images['Images']:
    ...     client.deregister_image(ImageId=img['ImageId'])
    

    Terminaci贸n de una instancia EC2

    Bueno, habiendo cubierto c贸mo iniciar, detener, crear y eliminar im谩genes de respaldo y lanzar una instancia EC2 desde una imagen de respaldo, me estoy acercando al final de este tutorial. Ahora todo lo que queda por hacer es limpiar mis instancias de demostraci贸n llamando a la EC2.Clientclase terminate_instancesy pasando los ID de instancia para terminar. Nuevamente, usar茅 describe_instancesun filtro para el nombre de la instancia de demostraci贸n para obtener los detalles y obtener su ID de instancia. Entonces puedo usarlo terminate_instancespara deshacerme de 茅l para siempre.

    Nota : S铆, esto es algo para siempre, as铆 que tenga mucho cuidado con este m茅todo.

    >>> demo = client.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': ['demo-instance']}])
    >>> pprint.pprint(client.terminate_instances(InstanceIds=[instance_id]))
    {'ResponseMetadata': {'HTTPHeaders': {'content-type': 'text/xml;charset=UTF-8',
                                          'date': 'Sat, 22 Dec 2018 22:14:20 GMT',
                                          'server': 'AmazonEC2',
                                          'transfer-encoding': 'chunked',
                                          'vary': 'Accept-Encoding'},
                          'HTTPStatusCode': 200,
                          'RequestId': '78881a08-0240-47df-b502-61a706bfb3ab',
                          'RetryAttempts': 0},
     'TerminatingInstances': [{'CurrentState': {'Code': 32,
                                                'Name': 'shutting-down'},
                               'InstanceId': 'i-0c462c48bc396bdbb',
                               'PreviousState': {'Code': 16, 'Name': 'running'}}]}
    

    Uniendo las cosas para un script de automatizaci贸n

    Ahora que he analizado estas funcionalidades emitiendo comandos uno por uno usando el int茅rprete de shell de Python (que recomiendo encarecidamente a los lectores que hagan al menos una vez por su cuenta para experimentar con las cosas), reunir茅 todo en dos scripts separados llamados ec2backup. .py y amicleanup.py.

    El script ec2backup.py simplemente consultar谩 todas las instancias EC2 disponibles que tienen la etiqueta BackUp y luego crear谩 una imagen AMI de respaldo para cada una mientras las etiqueta con una etiqueta RemoveOn con un valor de 3 d铆as en el futuro.

    # ec2backup.py
    
    from datetime import datetime, timedelta
    import awsutils
    
    def backup(region_id='us-east-1'):
        '''This method searches for all EC2 instances with a tag of BackUp
           and creates a backup images of them then tags the images with a
           RemoveOn tag of a YYYYMMDD value of three UTC days from now
        '''
        created_on = datetime.utcnow().strftime('%Y%m%d')
        remove_on = (datetime.utcnow() + timedelta(days=3)).strftime('%Y%m%d')
        session = awsutils.get_session(region_id)
        client = session.client('ec2')
        resource = session.resource('ec2')
        reservations = client.describe_instances(Filters=[{'Name': 'tag-key', 'Values': ['BackUp']}])
        for reservation in reservations['Reservations']:
            for instance_description in reservation['Instances']:
                instance_id = instance_description['InstanceId']
                name = f"InstanceId({instance_id})_CreatedOn({created_on})_RemoveOn({remove_on})"
                print(f"Creating Backup: {name}")
                image_description = client.create_image(InstanceId=instance_id, Name=name)
                images.append(image_description['ImageId'])
                image = resource.Image(image_description['ImageId'])
                image.create_tags(Tags=[{'Key': 'RemoveOn', 'Value': remove_on}, {'Key': 'Name', 'Value': name}])
    
    if __name__ == '__main__':
        backup()
    

    El siguiente es el script amicleanup.py que consulta todas las im谩genes AMI que tienen una etiqueta RemoveOn igual a la fecha del d铆a en que se ejecut贸 en el formato “AAAAMMDD” y las elimina.

    # amicleanup.py
    
    from datetime import datetime
    import awsutils
    
    def cleanup(region_id='us-east-1'):
        '''This method searches for all AMI images with a tag of RemoveOn
           and a value of YYYYMMDD of the day its ran on then removes it
        '''
        today = datetime.utcnow().strftime('%Y%m%d')
        session = awsutils.get_session(region_id)
        client = session.client('ec2')
        resource = session.resource('ec2')
        images = client.describe_images(Filters=[{'Name': 'tag:RemoveOn', 'Values': [today]}])
        for image_data in images['Images']:
            image = resource.Image(image_data['ImageId'])
            name_tag = [tag['Value'] for tag in image.tags if tag['Key'] == 'Name']
            if name_tag:
                print(f"Deregistering {name_tag[0]}")
            image.deregister()
    
    if __name__ == '__main__':
        cleanup()
    

    Implementaci贸n Cron

    Una forma relativamente sencilla de implementar la funcionalidad de estos dos scripts ser铆a programar dos tareas cron en un servidor Linux para ejecutarlas. En un ejemplo a continuaci贸n, he configurado una tarea cron para que se ejecute todos los d铆as a las 11 p.m. para ejecutar el script ec2backup.py y luego otra a las 11:30 p.m. para ejecutar el script amicleanup.py.

    0 23 * * * /path/to/venv/bin/python /path/to/ec2backup.py
    30 23 * * * /path/to/venv/bin/python /path/to/amicleanup.py
    

    Implementaci贸n de AWS Lambda

    Una soluci贸n m谩s elegante es utilizar AWS Lambda para ejecutar los dos como un conjunto de funciones. Hay muchos beneficios de usar AWS Lambda para ejecutar c贸digo, pero para este caso de uso de ejecutar un par de funciones de Python para crear y eliminar im谩genes de respaldo, las m谩s pertinentes son la alta disponibilidad y evitar pagar por recursos inactivos. Ambos beneficios se obtienen mejor cuando se compara el uso de Lambda con la ejecuci贸n de los dos trabajos cron descritos en la 煤ltima secci贸n.

    Si tuviera que configurar mis dos trabajos cron para que se ejecuten en un servidor existente, 驴qu茅 sucede si ese servidor deja de funcionar? No solo tengo el dolor de cabeza de tener que volver a activar ese servidor, sino que tambi茅n corro la posibilidad de perder una ejecuci贸n programada de los trabajos cron que controlan el proceso de copia de seguridad y limpieza del servidor EC2. Esto no es un problema con AWS Lambda, ya que est谩 dise帽ado con redundancia para garantizar una disponibilidad extremadamente alta.

    El otro beneficio principal de no tener que pagar por recursos inactivos se comprende mejor en un ejemplo en el que puedo haber creado una instancia solo para administrar estos dos scripts que se ejecutan una vez al d铆a. Este m茅todo no solo cae dentro de la falla de disponibilidad potencial del 煤ltimo elemento, sino que ahora se ha aprovisionado una m谩quina virtual completa para ejecutar dos scripts una vez al d铆a, lo que constituye una cantidad muy peque帽a de tiempo de c贸mputo y muchos recursos desperdiciados inactivos. Este es un caso excelente para utilizar AWS Lambda para mejorar la eficiencia operativa.

    Otra eficiencia operativa resultante del uso de Lambda es no tener que perder tiempo manteniendo un servidor dedicado.

    Para crear una funci贸n de AWS Lambda para las copias de seguridad de la imagen de la instancia EC2, siga estos pasos:

    Paso 1. En el men煤 Servicio, haga clic en Lambda dentro de la secci贸n Computar.

    Paso 2. Haga clic en el bot贸n Crear funci贸n.

    Paso 3. Seleccione la opci贸n Autor desde cero, escriba “ec2backup” como nombre de funci贸n, seleccione Python 3.6 de las opciones de tiempo de ejecuci贸n, luego agregue el usuario boto3 para el rol y haga clic en Crear funci贸n como se muestra a continuaci贸n:

    Paso 4. En el dise帽ador, seleccione Eventos de CloudWatch y agregue un trabajo cron cron(0 11 * ? * *)que har谩 que la funci贸n se ejecute todos los d铆as a las 11 p.m.

    Paso 5. En el editor de c贸digo, agregue el siguiente c贸digo:

    import boto3
    import os
    from datetime import datetime, timedelta
    
    def get_session(region, access_id, secret_key):
        return boto3.session.Session(region_name=region,
                                    aws_access_key_id=access_id,
                                    aws_secret_access_key=secret_key)
    
    def lambda_handler(event, context):
        '''This method searches for all EC2 instances with a tag of BackUp
           and creates a backup images of them then tags the images with a
           RemoveOn tag of a YYYYMMDD value of three UTC days from now
        '''
        created_on = datetime.utcnow().strftime('%Y%m%d')
        remove_on = (datetime.utcnow() + timedelta(days=3)).strftime('%Y%m%d')
        session = get_session(os.getenv('REGION'),
                              os.getenv('ACCESS_KEY_ID'),
                              os.getenv('SECRET_KEY'))
        client = session.client('ec2')
        resource = session.resource('ec2')
        reservations = client.describe_instances(Filters=[{'Name': 'tag-key', 'Values': ['BackUp']}])
        for reservation in reservations['Reservations']:
            for instance_description in reservation['Instances']:
                instance_id = instance_description['InstanceId']
                name = f"InstanceId({instance_id})_CreatedOn({created_on})_RemoveOn({remove_on})"
                print(f"Creating Backup: {name}")
                image_description = client.create_image(InstanceId=instance_id, Name=name)
                image = resource.Image(image_description['ImageId'])
                image.create_tags(Tags=[{'Key': 'RemoveOn', 'Value': remove_on}, {'Key': 'Name', 'Value': name}])
    

    Paso 6. En la secci贸n debajo del editor de c贸digo, agregue algunas variables de entorno.

    • REGION con un valor de la regi贸n de las instancias EC2 para respaldar que es us-east-1 en este ejemplo
    • ACCESS_KEY_ID con el valor de la clave de acceso de la secci贸n donde se configur贸 el usuario boto3
    • SECRET_KEY con el valor de la clave secreta de la secci贸n donde se configur贸 boto3-user

    Paso 7. Haga clic en el bot贸n Guardar en la parte superior de la p谩gina.

    Para la funcionalidad de limpieza de im谩genes, siga los mismos pasos con los siguientes cambios.

    Paso 3. Le doy un nombre de “amicleanup”

    Paso 4. Utilizo una configuraci贸n de hora ligeramente diferente cron(30 11 * ? * *)para ejecutar a las 11:30 p.m.

    Paso 5. Utilice la siguiente funci贸n de limpieza:

    import boto3
    from datetime import datetime
    import os
    
    def get_session(region, access_id, secret_key):
        return boto3.session.Session(region_name=region,
                                    aws_access_key_id=access_id,
                                    aws_secret_access_key=secret_key)
    
    def lambda_handler(event, context):
        '''This method searches for all AMI images with a tag of RemoveOn
           and a value of YYYYMMDD of the day its ran on then removes it
        '''
        today = datetime.utcnow().strftime('%Y%m%d')
        session = get_session(os.getenv('REGION'),
                              os.getenv('ACCESS_KEY_ID'),
                              os.getenv('SECRET_KEY'))
        client = session.client('ec2')
        resource = session.resource('ec2')
        images = client.describe_images(Filters=[{'Name': 'tag:RemoveOn', 'Values': [today]}])
        for image_data in images['Images']:
            image = resource.Image(image_data['ImageId'])
            name_tag = [tag['Value'] for tag in image.tags if tag['Key'] == 'Name']
            if name_tag:
                print(f"Deregistering {name_tag[0]}")
            image.deregister()
    

    Conclusi贸n

    En este art铆culo, he explicado c贸mo utilizar la biblioteca Boto3 del SDK de AWS Python para interactuar con los recursos de EC2. Demuestro c贸mo automatizar las tareas de administraci贸n operativa para la creaci贸n de copias de seguridad de im谩genes AMI para instancias EC2 y la limpieza posterior de esas im谩genes de copia de seguridad mediante trabajos cron programados en un servidor dedicado o con AWS Lambda.

    Si est谩 interesado en aprender a usar Boto y AWS Simple Storage Service (S3), consulte el art铆culo de Scott Robinson aqu铆 en Pharos.sh.

    Como siempre, gracias por leer y no dude en comentar o criticar a continuaci贸n.

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *