Automatización de la administración de AWS EC2 con Python y Boto3

En este artículo, demostraré el uso de Python junto con el kit de desarrollo de software (SDK) de Amazon Web Services (AWS) de Boto3, que permite a la gente conocer...

Introducción

En este artículo, demostraré el uso de Python junto con el Boto3 Kit de desarrollo de software de Amazon Web Services (AWS) ( SDK) que permite a las personas con conocimientos en programación de 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
  • Copia de seguridad de una instancia EC2 mediante la creación de una imagen
  • Creación de 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 entornos

Para comenzar, tendré que crear un usuario en mi cuenta de AWS que tenga acceso programático a las API de 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 usar 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 siguiente botón.

{.img-responsive}

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 AdministratorAccess antes de hacer clic en siguiente, como se muestra a continuación.

{.img-responsive}

Paso 3: haga clic en el siguiente ya que no estoy agregando ninguna etiqueta opcional.

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

{.img-responsive}

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

{.img-responsive}

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

1
2
3
$ 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 I descargado en el paso 5 anterior.

1
2
3
4
5
$ 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 en la que trabajar

En esta sección, voy a repasar cómo crear una sesión de boto3 específica de la región de AWS, así como crear una instancia de un cliente EC2 utilizando el objeto de sesión activa. Luego, usando ese cliente EC2 boto3, interactuaré con las instancias EC2 de esa región administrando el inicio, el apagado y la finalización.

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

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

Paso 2: en la página Elegir imagen de máquina de Amazon (AMI), hago clic en el botón Seleccionar junto a la AMI de Amazon Linux.

{.img-responsive}

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

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 Nombre y Copia de seguridad, luego hago clic en Iniciar revisión y Iniciar nuevamente para volver a la página de revisión anterior. finalmente haciendo clic en el botón Iniciar para iniciar la instancia.

{.img-responsive}

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

{.img-responsive}

Sesión y cliente de Boto3

¡Por fin puedo ponerme a escribir algo de código! Comienzo creando un archivo vacío, un módulo de Python, llamado awsutils.py y en la parte superior importo la biblioteca boto3 y luego defino una función que creará una [Sesión] específica de la región (https://boto3.amazonaws. com/v1/documentation/api/latest/guide/session.html) objeto.

1
2
3
4
5
6
# 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 recién creado arriba, puedo usar la nueva función get_session para crear una sesión en la misma región que mi instancia EC2, luego instanciar un [EC2.Cliente](https://boto3 .amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#client) desde él, así:

1
2
3
>>> 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 usando pprint para hacer las cosas un poco más fáciles de ver el resultado de llamar a describe_instances en el objeto cliente.

1
2
3
>>> import pprint
>>> pprint.pprint(client.describe_instances())
...

Estoy omitiendo el resultado porque es bastante detallado, pero tenga en cuenta que contiene un diccionario con una entrada “Reservaciones”, que es una lista de datos que describen las instancias EC2 en esa región y “ResponseMetadata” sobre la solicitud que se acaba de hacer a la API REST de AWS.

Recuperación de los detalles de la instancia EC2

También puedo usar este mismo método describe_instances junto con un parámetro Filter para filtrar la selección por valores de etiqueta. Por ejemplo, si quiero obtener mi instancia creada recientemente con la etiqueta Nombre con un valor de 'instancia de demostración', se vería así:

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

Hay muchas formas de filtrar la salida de describe_instances y lo remito a los [documentos oficiales](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2 .Client.describe_instances) para conocer los detalles.

Iniciar y detener una instancia EC2

Para detener la instancia de demostración, uso el método stop_instances del objeto cliente, que instanciamos previamente, proporcionándole el ID de la instancia como un parámetro de lista de entrada única para el argumento InstanceIds como se muestra a continuación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>>> 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'}}]

El resultado del último comando indica que la llamada al método detiene la instancia. Si vuelvo a recuperar la instancia de demostración e imprimo el ‘Estado’, ahora veo que está detenido.

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

Para volver a iniciar la misma instancia, hay un método complementario llamado start_instances que funciona de manera similar al método stop_instances que demuestro a continuación.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>> 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 recuperar la instancia e imprimo su estado, muestra que se está ejecutando nuevamente.

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

Enfoque alternativo para obtener, iniciar y detener {#enfoque alternativo para obtener, iniciar y detener}

Además de la clase EC2.Client con la que he estado trabajando hasta ahora, también hay una [EC2.Instancia](https://boto3.amazonaws.com/v1/documentation/api/latest/reference /services/ec2.html#instance) que es útil en casos como este en el que solo necesito preocuparme por una instancia a la vez.

A continuación, uso el objeto ‘sesión’ generado previamente para obtener un objeto de recurso EC2, que luego puedo usar para recuperar e instanciar un objeto ‘Instancia’ para mi instancia de demostración.

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

En mi opinión, una de las principales ventajas de usar la clase Instancia es que se trabaja con objetos reales en lugar de con una representación de diccionario de un punto en el tiempo de la instancia, pero se pierde la posibilidad de realizar acciones en varias instancias al mismo tiempo. una vez que la clase EC2.Client proporciona.

Por ejemplo, para ver el estado de la instancia de demostración que acabo de instanciar arriba, es tan simple como esto:

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

La clase Instance tiene muchos métodos útiles, dos de los cuales son start y stop que usaré para iniciar y detener mis instancias, así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>> 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 alrededor de un minuto para que se detenga por completo... luego verifico el estado nuevamente:

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

Ahora puedo ponerlo en marcha de nuevo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>> 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 corto tiempo...

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

Creación de una imagen de copia de seguridad de una instancia EC2

Un tema importante en la administración del servidor 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, demostraré cómo crear una copia de seguridad de Amazon Machine Image (AMI) de mi instancia de demostración, que luego AWS almacenará en su Servicio de almacenamiento simple (S3). Esto se puede usar más tarde para recrear esa instancia EC2, tal como usé la AMI inicial para crear la instancia de demostración.

Para comenzar, mostraré cómo usar la clase EC2.Client y su método create_image para crear una imagen AMI de una instancia de demostración proporcionando el ID de la instancia y un nombre descriptivo para la instancia.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
>>> 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 create_image de la clase Instance para realizar la misma tarea, que devuelve una instancia de una clase EC2.Image que es similar a la clase EC2.Instance.

1
>>> 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 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, así como mediante programación con boto3 y la API REST de AWS.

Dado que todavía tengo un objeto EC2.Instance flotando en la memoria en mi intérprete de Python, lo usaré para mostrar las etiquetas de instancia de demostración.

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

Tanto las clases EC2.Instance como EC2.Image tienen un conjunto de métodos create_tags que funcionan de manera idéntica para agregar etiquetas a sus recursos representados. A continuación, muestro la adición de 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".

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

Una vez más, se puede lograr lo mismo con la clase EC2.Client proporcionando una lista de ID de recursos, pero con el cliente puede etiquetar tanto imágenes como instancias EC2 al mismo tiempo si lo desea especificando sus ID en el parámetro Recurso de la función create_tags, así:

1
2
3
4
5
6
7
8
>>> 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 copia de seguridad

Me gustaría comenzar esta sección dándoles algo en lo que pensar. Póngase en la mentalidad incómoda de un administrador de sistemas, o peor aún, de un desarrollador que pretende 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 se ha dañado.

¡Eeek! Es tiempo de codificación... ahora necesita averiguar qué tipo de sistema operativo, tamaño y servicios se estaban ejecutando en el servidor inactivo... busque a tientas la configuración e instalación del servidor base, además de cualquier aplicación que pertenezca a él, y ruega que todo salga correctamente.

¡Uf! Tome un respiro y relájese porque estoy a punto de mostrarle cómo volver a funcionar rápidamente, además... alerta de spoiler... Voy a incorporar estos comandos únicos del intérprete de Python en un conjunto viable de guiones al final para que pueda modificarlos y ponerlos en uso.

Ok, con ese ejercicio mental fuera del camino, déjame volver al trabajo. Para crear una instancia EC2 a partir de un ID de imagen, utilizo el método run_instances de la clase EC2.Client y especifico la cantidad de instancias que se iniciarán y el tipo de instancia que se ejecutará.

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

Estoy omitiendo la salida nuevamente debido a su verbosidad. Consulte los documentos oficiales del método ejecutar_instancias, ya que existen Hay muchos parámetros para elegir para personalizar exactamente cómo ejecutar la instancia.

Eliminación de imágenes de copia de seguridad

Idealmente, haría copias de seguridad de imágenes 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 cubos S3 y acumulando cargos con cada copia de seguridad adicional que guardo.

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 período de 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 aquellas que tienen una etiqueta RemoveOn particular y luego eliminarlas.

Puedo comenzar usando el método describe_images en la instancia de la clase EC2.Client junto con un filtro para la etiqueta 'RemoveOn' para obtener todas las imágenes que etiqueté para eliminar en una fecha determinada.

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

A continuación, itero sobre todas las imágenes y llamo al método del cliente deregister_image pasándole el ID de la imagen iterada y listo, no más imágenes.

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

Finalización de una instancia EC2

Bueno, después de haber cubierto el inicio, la detención, la creación y la eliminación de imágenes de copia de seguridad y el lanzamiento de una instancia EC2 desde una imagen de copia de seguridad, me acerco al final de este tutorial. Ahora todo lo que queda por hacer es limpiar mis instancias de demostración llamando a terminate_instances de la clase EC2.Client y pasando los ID de instancia para terminar. Nuevamente, usaré describe_instances con un filtro para el nombre de la instancia de demostración para obtener los detalles y obtener su ID de instancia. Luego puedo usarlo con terminate_instances para deshacerme de él para siempre.

Nota: Sí, esto es para siempre, así que tenga mucho cuidado con este método.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>>> 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 una secuencia de comandos de automatización {#uniendo las cosas para una secuencia de comandos de automatización}

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

El script ec2backup.py simplemente consultará todas las instancias EC2 disponibles que tengan 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 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 de AMI que tienen una etiqueta RemoveOn igual a la fecha del día en que se ejecutó en el formulario "YYYYMMDD" y las elimina.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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 de Cron

Una forma relativamente sencilla de implementar la funcionalidad de estos dos scripts sería programar dos tareas cron en un servidor Linux para ejecutarlos. En un ejemplo a continuación, configuré 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.

1
2
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 usar AWS Lambda para ejecutar los dos como un conjunto de funciones. El uso de AWS Lambda para ejecutar código tiene muchos beneficios, pero para este caso de uso de ejecutar un par de funciones de Python para crear y eliminar imágenes de copia de seguridad, las más pertinentes son la alta disponibilidad y evitar el pago de recursos inactivos. Ambos beneficios se obtienen mejor cuando 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 los recursos inactivos se comprende mejor en un ejemplo en el que pude haber activado una instancia solo para administrar estos dos scripts que se ejecutan una vez al día. Este método no solo cae bajo la posible falla de disponibilidad 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 usar AWS Lambda para mejorar la eficiencia operativa.

Otra eficiencia operativa que resulta del uso de Lambda es no tener que dedicar tiempo a mantener un servidor dedicado.

Para crear una función AWS Lambda para las copias de seguridad de imágenes de la instancia EC2, siga estos pasos:

Paso 1. En el menú Servicio, haga clic en Lambda dentro de la sección Calcular.

Paso 2. Haz 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 abajo:

{.img-responsive}

Paso 4. En el diseñador, seleccione CloudWatch Events y agregue un trabajo cron de cron(0 11 * ? * *) que hará que la función se ejecute todos los días a las 11 p.m.

{.img-responsive}

Paso 5. En el editor de código agrega el siguiente código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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ó el usuario boto3

Paso 7. Haz 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 el nombre de "amicleanup"

Paso 4. Uso una configuración de tiempo ligeramente diferente de cron(30 11 * ? * *) para ejecutar a las 11:30 p. m.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 cubierto cómo usar la biblioteca Boto3 de AWS Python SDK 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 posterior limpieza de esas imágenes de copia de seguridad utilizando trabajos cron programados en un servidor dedicado o usando AWS Lambda.

Si está interesado en aprender a utilizar Boto y AWS Simple Storage Service (S3), consulte el artículo de Scott Robinson aquí en wikihtp.

Como siempre, gracias por leer y no se avergüence de comentar o criticar a continuación.