Usando AWS Lambda para automatizar ASG Cleanup

En esta ocasión vamos a integrar Amazon Web Services EC2 con Lambda para mantener el orden dentro de nuestra infraestructura. La idea general detrás de cualquier ambiente productivo que creemos es la de utilizar las herramientas que son provistas para simplificar el ciclo de vida de las aplicaciones.

Recent Posts

En esta ocasión vamos a integrar Amazon Web Services EC2 con Lambda para mantener el orden dentro de nuestra infraestructura.

La idea general detrás de cualquier ambiente productivo que creemos es la de utilizar las herramientas que son provistas para simplificar el ciclo de vida de las aplicaciones. Esto no solo conlleva a una mejor utilización de los recursos sino, y tal vez más importante para aquellos que administramos las plataformas, una simplificación de nuestras tareas diarias para poder concentrarnos en las mejoras que son requeridas en cualquier sistema de alta disponibilidad.

Un claro ejemplo de esto son los Auto Scaling Groups, los cuales permiten que nuestras aplicaciones escalen de forma automática en base a nuestra curva de uso de las instancias sin requerir de intervención manual.

Uno de los puntos que queda pendiente con este servicio de automatización es el cleanup necesario luego de que la plataforma haga un scale down, es decir, cuando nuestra curva de requerimiento de capacidad decremente. Teniendo en cuenta que AWS no provee las herramientas para hacer estas tareas de forma automática, cosas como hacer cleanup de instancias en Route 53 o sacarlas de sistemas de manejo de configuraciones (Puppet, Chef, Salt) quedan relegadas a hacer un inventario de las instancias que están actualmente en uso para luego hacer un cleanup manual de las mismas.

Con este fin, en esta entrega mostraremos un sistema simple que tomará información desde el sistema de Auto Scaling de AWS y utilizará Lambda para hacer el cleanup necesario.

Para demostrar esto utilizaremos una plataforma que cuando una instancia es creada, se agrega automaticamente una entrada al DNS interno utilizado por todas las instancias en el mismo ambiente. La idea es hacer luego el cleanup correspondiente: así como al levantar una instancia creamos un registro DNS, cuando la misma se termina, se debe borrar su registro DNS de la zona donde fue agregada para evitar tener registros que ya no estén en uso.

Utilizaremos 3 servicios para este fin:

  • Auto Scaling Groups: Al ser el que se encarga de escalar las instancias según la curva de demanda, será el encargado de notificar cuando una instancia sea sacada de servicio.
  • Simple Notification Service: Utilizaremos el servicio de SNS para que las notificaciones que el ASG sean recibidas por nuestra función de Lambda.
  • Lambda: Será el encargado de ejecutar el código que utilizaremos para hacer la limpieza de instancias en Route 53.

Introducción a Lambda

AWS Lambda es un servicio de procesamiento de código. Permite subir nuestro código a AWS y ejecutarlo en base a nuestra necesidad directamente desde la infraestructura de AWS sin necesitar nosotros contar con instancias de EC2 dedicadas para este propósito.

Este servicio nos abstrae de tener que manejar la infraestructura para que nuestro código se ejecute en la nube. Esto nos libera tanto de los costos asociados a la instancia dedicada a este propósito así como también del mantenimiento de la misma, permitiéndonos concentrar solamente en la funcionalidad que queremos lograr sin lidiar con la plataforma donde correrá.

Lamba permite ejecutar nuestro código en base a triggers (en nuestro caso una notificación de SNS) para que esta tarea sea ejecutada al momento de terminación de una instancia de EC2. Todo lo que necesitamos hacer es proveer el código en uno de los lenguajes soportados (Actualmente node.js, java y python).

Creación de un topic de SNS

El primer paso será configurar un SNS topic que nos permitirá asociar nuestra función de lambda a las notificaciones que el Auto Scaling Group envíe.

El siguiente código nos permitirá, en un paso, crear el topic de SNS directamente desde nuestra consola. No necesitamos agregar ningun permiso ni configuración especial en este caso dado que sólo necesitamos que el topic esté disponible para nuestro Auto Scaling Group.

$ : aws sns create-topic --name demo-topic 
{
"TopicArn": "arn:aws:sns:us-east-1:204753688193:demo-topic"
}

Configuracion de Auto Scaling Group notifications

Una vez tengamos nuestro topic de SNS creado necesitaremos una forma de notificar a lambda cuando tiene que ejecutar el código. Para esto agregaremos una notificación a nuestro Auto Scaling Group para que cuando una instancia sea terminada, la misma envíe un mensaje al topic de SNS que estará asociado a nuestra función de Lambda.

$ : aws autoscaling put-notification-configuration --auto-scaling-group-name demo-asg \
--topic-arn arn:aws:sns:us-east-1:204753688193:demo-topic \
--notification-types "autoscaling:EC2_INSTANCE_TERMINATE"
$ : aws autoscaling describe-notification-configurations 
{
  "NotificationConfigurations": [
    {
      "AutoScalingGroupName": "demo-asg",
      "NotificationType": "autoscaling:EC2_INSTANCE_TERMINATE",
      "TopicARN": "arn:aws:sns:us-east-1:204753688193:demo-topic"
    }
  ]
}

También tenemos que asegurarnos que el Auto Scaling Group tiene los permisos correctos para publicar mensajes al topic de SNS. Esto se puede habilitar desde las configuraciones de IAM.

Configuración de Lambda

Una vez que tenemos configurados todos los pasos anteriores, podemos proceder a trabajar con nuestra función de Lambda.

Deberemos tener en cuenta los permisos que debemos agregar, en este caso, simplemente utilizamos como base lambdabasicexecution para pushear logs a cloudwatch y agregamos los permisos para acceder a Route 53 desde nuestro script:

{
 "Version": "2012-10-17",
 "Statement": [
  {
    "Effect": "Allow",
    "Action":
     [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents"
     ],
     "Resource": "arn:aws:logs:*:*:*"
  }
 ],[
    {
     "Effect": "Allow",
     "Action": [
      "route53:GetHostedZone",
      "route53:ListResourceRecordSets",
      "route53:ChangeResourceRecordSets"
     ],
     "Resource": [
      "arn:aws:route53:::hostedzone/<hosted zone ID>"
     ]
   }
 ]
}

Estos permisos pueden ser extensibles en base a las funcionalidades que nuestra función de Lambda requiera. En nuestro ejemplo solamente agregamos los permisos básicos para poder remover registros de DNS de Route53 en la zona que nosotros requerimos.

Una vez configurado esto, el siguiente paso es crear y subir nuestra función a lambda. Podemos hacerlo desde la consola de amazon o la CLI.

Para la función de lambda, utilizaremos el siguiente código de python:

import logging, boto3, botocore.exceptions, json, urllib, re, sys from boto3.session import Session

def lambda_handler(event, context):
  logging.getLogger().setLevel(logging.INFO)
  for record in event['Records']:
    if 'aws:sns' == record['EventSource'] and record['Sns']['Message']:
      handle_sns_event(json.loads(record['Sns']['Message']),
  return True

def handle_sns_event(event, context):
  session = Session
  instance = event['EC2InstanceId']
  # Adding our heading + zone to the instance name
  record = "demo-" + instance + ".demozone.net."
  connr53 = session.client('route53')
  response = connr53.list_resource_record_sets(
    HostedZoneId=<Hosted Zone ID>,
    StartRecordName=record,
    StartRecordType='A',
    MaxItems='1'
  )
  dnsIPRecord = response['ResourceRecordSets'][0]['ResourceRecords'][0]['Value']
  logging.info('INFO: Deleting record {} ( {} ) from Route 53 ... '.format(record,dnsIPRecord))

  response = connr53.change_resource_record_sets(
    HostedZoneId='<Hosted Zone ID>',
    ChangeBatch={
      'Comment': 'Deleting no longer valid DNS entry for lab',
      'Changes': [
        {
          'Action': 'DELETE',
          'ResourceRecordSet': {
            'Name': record,
            'Type': 'A',
            'TTL': 60,
            'ResourceRecords': [
              {
                'Value': dnsIPRecord
              },
            ],
          }
        },
      ]
    }
  )

Una vez esto esté listo, el ultimo paso sera asociar nuestra función de Lambda al topic de SNS que creamos en los pasos anteriores. Con esto configuraremos el disparador que se encargará de ejecutar nuestra función de lambda cada vez que una instancia del Auto Scaling Group sea terminada.

Esto es todo lo que necesitamos en nuestra función de python para que se eliminen los registros de Lambda. El flow del proceso ahora será de la siguiente forma:

Cleanup Flow

  1. Auto Scaling Action (Terminación de instancia)
  2. Envío de notificación al topic de SNS
  3. SNS publica el mensaje a los endpoints conectados (Lambda es uno de ellos)
  4. Lambda recibe el mensaje
  5. El nuevo mensaje hace que el código se ejecute
  6. El código parsea el mensaje y remueve la entrada de DNS

Conclusiones

La idea detrás de este artículo no es dar un paso a paso sobre cómo borrar instancias de un DNS de forma automática (Aunque son más que bienvenidos a utilizarlo como tal), sino mostrar las funcionalidades que Amazon nos brinda a la hora de automatizar las tareas del día a día que los equipos técnicos tienen que realizar.

De la misma forma que utilizamos Lambda para realizar un cleanup de DNS, la solución puede ser adaptada para otros fines: desde limpieza de entornos cuando el mismo cambia, pasando por tareas de administración para el setup inicial de instancias, hasta el uso por equipos de desarrollo para pruebas de código sin tener que preocuparse por el entorno en que se ejecuta el mismo.