Cómo desplegar microservicios en Amazon

En Paradigma hemos apostado fuerte por arquitecturas basadas en contenedores que a su vez se despliegan en infraestructuras Openshift o Kubernetes.

Hoy vamos a ver los distintos servicios que ofrece AWS para este tipo de despliegues (AWS ECS, EKS y Fargate) y en concreto cómo se haría con Amazon Elastic Container Service (ECS).

Para no alargarnos demasiado, no vamos a considerar otras opciones que no sean servicios propios de AWS, como sería el caso de desplegar Kubernetes en AWS con herramientas como KOPS o incluso del despliegue de Openshift en AWS. ¡Arrancamos!

AWS Amazon Elastic Container Service (ECS)

Este servicio se anunció en el AWS re:Invent 2015 y consiste en un cluster con autoescalado, alta disponibilidad y multi zona. Permite la gestión y despliegue de contenedores Docker en él mediante la definición de Tareas y Servicios (más adelante explicaremos en qué consiste cada uno de estos conceptos).

En aquel entonces (y hasta este año) el servicio únicamente permitía una variante, que consistía en el despliegue de un cluster con un tipo de instancias específicamente tuneadas por AWS para ECS.

Estas instancias contienen, fundamentalmente, un agente de ECS que es quien comunica la parte del cluster que ve el usuario en su VPC (donde se despliegan los contenedores) con la parte que no vemos del servicio (que está en la infraestructura de AWS).

De esta forma, el usuario de este servicio pagaba por las instancias que tuviera en funcionamiento como parte del cluster.

Arquitectura del servicio AWS ECS sin Fargate.

En el último AWS re:Invent 2017 también anunciaron una variante de ECS que se llama AWS Fargate y le da una vuelta de tuerca a este servicio, eliminando la gestión del cluster de instancias.

Actualmente ECS únicamente está desplegado en una región, aunque como el resto de servicios de AWS, es de esperar que en breve esté en el resto de regiones.

La mayor ventaja que aporta Fargate respecto de ECS consiste en la forma de facturarlo a los clientes: únicamente se paga por la cantidad de vCPU y memoria que consumen los contenedores desde que se hace pull hasta que terminan. En este sentido, la teoría promete un abaratamiento de costes muy importante para este tipo de despliegues.

Por el contrario, tiene ciertas limitaciones. Por ejemplo, únicamente permite desplegar desde su servicio de Registry (ECR) y desde Docker Hub, no desde un Registry local.

A nivel de red parece (con los datos actuales y a raíz de algunos diagramas de arquitectura que se van filtrando, como el de más abajo) que con Fargate los servicios se ejecutarán fuera del VPC con una tarjeta de red del VPC.

Es una solución similar a la del servicio de AWS Lambda y, en principio, parece menos segura que ECS, donde el usuario controla los interfaces de red de las instancias que despliega, sabe que en dichas instancias sólo se despliegan servicios de su propiedad, puede elegir hosts dedicados para él, analizar posibles intrusiones a nivel de sistema operativo… en definitiva, garantizan otro nivel más de seguridad.

Arquitectura del servicio ECS con Fargate.

Por último, en este último reinvent, AWS ha intentado dar respuesta también a la principal pega de su solución de orquestación de contenedores: el vendor lock-in (no existe otro sistema similar que permita desplegar usando las definiciones de Servicios y Tareas que usa AWS).

Por esto mismo ha creado el servicio Amazon Elastic Container Service for Kubernetes (anunciado como Preview).

Por lo que se puede entender, de los comunicados de prensa y blogs de Amazon, la arquitectura de este servicio se asemeja un poco a la del ECS de 2015. Habrá una serie de nodos (Master) en la parte de AWS y otros, los workers, visibles desde la cuenta del usuario.

Así mismo, ya anuncian también que se integrará con Fargate en 2018, de forma que el usuario no tendrá que gestionar estos nodos ni pagar por ellos (únicamente por el consumo de recursos de cada contenedor, como ya mencionamos anteriormente).  

Arquitectura del servicio EKS sin Fargate.

Despliegue de un contenedor en AWS Elastic Container Service

Lo primero que necesitamos para desplegar nuestro contenedor en AWS ECS es, en efecto, el contenedor. Como este post no se centra en la creación del contenedor en sí, vamos a usar un contenedor con un servidor de Nginx que podemos encontrar en Docker Hub.

Nos lo bajamos como sigue:

$ sudo docker pull nginx

Para comprobar que funciona podemos levantarlo en local ejecutando:

$ sudo docker run –name docker-nginx -p 80:80 nginx

En otra consola o en el mismo navegador podemos hacer un GET a nuestra máquina en el puerto 80 y, si todo ha ido bien, veríamos la siguiente respuesta:

Subiendo el contenedor a AWS Amazon Elastic Container Registry (ECR)

A continuación necesitamos crear un repositorio en AWS Amazon Elastic Container Registry (ECR) donde almacenar el contenedor. AWS ECR es un servicio autogestionado que permite el registro de contenedores de Docker.

Es decir, un repositorio donde guardar las distintas versiones de nuestros contenedores. Permite tagear cada subida al repositorio y, al ser autogestionado, el usuario no tiene que preocuparse de la infraestructura subyacente.

Para interactuar con cualquiera de los servicios de AWS se puede usar la cli de AWS, la consola Web, directamente el API con Boto3 o cualquiera de los SDK disponibles. Nosotros vamos a contar cómo se haría con las dos primeras opciones.

Para crear un repositorio nuevo podemos usar la consola de AWS con el siguiente comando (el nombre del repositorio tiene un formato específico que solo puede tener minúsculas, números, guiones, guiones bajos y barras diagonales):

$ aws ecr create-repository –repository-name subiendo.mi.repositorio

Desde la consola web de AWS del servicio ECS, en el apartado de Repositorios, es tan sencillo como pulsar el botón de “crear”.

De esta manera, nos mostrará a continuación una breve documentación sobre los siguientes pasos para subir el contenedor al repositorio:

En definitiva, tendríamos que logarnos con “docker login”, aunque no necesitamos en este caso construir la imagen pues ya la tenemos construida. Lo que sí tendríamos que hacer es etiquetarla con “docker tag” como sigue:

$ sudo docker tag nginx:latest {id_cuenta}.dkr.ecr.eu-west-1.amazonaws.com/subiendo.mi.contenedor:latest

Después, hacer “docker push” para que la suba al repositorio que hemos creado. Tras estos pasos, deberíamos tener un contenedor con un nginx subido a AWS ECR en el repositorio que acabamos de crear y etiquetado como latest.

Creando el cluster de ECS

Amazon Elastic Container Service es el servicio de AWS que nos permite crear clusters de contenedores para desplegar y gestionar tareas y servicios:

  • Tarea es el nombre que da Amazon a los contenedores desplegados en los clusters de ECS. Para definir cómo se desplegará cada tarea, se crean “definiciones de tarea” con parámetros que indican los límites de memoria y cpu, volúmenes de disco, puertos… de cada tarea.  
  • Servicio es la definición de cómo se desplegará esa tarea (o contenedor), cuántas tareas del mismo tipo queremos que estén levantadas, políticas de despliegue, antiafinidad…

Este paso vamos a hacerlo con el “Wizard” de la consola de AWS:

Dentro del servicio AWS ECS, la primera opción que tenemos que elegir a la hora de crear el cluster es si queremos que despliegue con una ami con Linux o Windows:

Una vez hayamos tomado una decisión, tendremos que configurar:

  • Nombre del cluster
  • Tipo de instancia
  • Tamaño del cluster
  • Almacenamiento
  • Configuración de red
  • Security group

Es importante que el security group tenga abiertos los puertos en los que desplegamos el servicio. Esto depende de si dejamos que sea una asignación dinámica o fijamos dicho puerto en el host/instancia.

Para este ejemplo, vamos a fijar el puerto en el que se registrará el contenedor en el 9999 y por lo tanto el security group debería permitir el acceso desde nuestra IP a ese puerto.  

De esta forma ya tendríamos nuestro cluster creado:

Creando una “definición de tarea”

Como ya hemos comentado más arriba, cada contenedor que queremos subir al cluster de ECS necesita tener una “Task Definition”, donde se definen los parámetros de lanzamiento del o de los contenedores (se pueden incluir varios contenedores en una misma definición de tarea).

Algunos de los parámetros que se pueden configurar en la definición de tarea son: CPU y memoria, variables de entorno que recibe el contenedor al arrancar, puertos, volúmenes de disco que queremos montar…

Aquí viene una descripción de los parámetros que se pueden configurar de esta forma.

En nuestro caso, vamos a registrar esta “Task Definition” mediante el cliente de AWS. Para reducir la complejidad de la sintaxis del comando, vamos a definir un json como este:

{
  "containerDefinitions": [
    {
      "cpu": 512,
      "essential": true,
      "image": "{id_cuenta}.dkr.ecr.eu-west-1.amazonaws.com/subiendo.mi.contenedor:latest",
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "subiendo-mi-contenedor",
          "awslogs-region": "eu-west-1",
          "awslogs-stream-prefix": "nginx"
        }
      },
      "memory": 448,
      "memoryReservation": 256,
      "name": "nginx",
      "portMappings": [
        {
          "hostPort": 9999,
          "containerPort": 80,
          "protocol": "tcp"
        }
      ]
    }
  ],
  "family": "subiendo_mi_contenedor"
}

Container-definitions_nginx.json

Lo fundamental de esta definición es la elección del puerto del contenedor y el puerto del host donde se desplegará. Si dejamos el parámetro hostPort sin definir, el mismo cluster lo ubicará en el puerto que decida, que no será predecible.

Por otro lado, al definir el driver de los logs con Cloudwatch, tendremos que crear un Log Group en Cloudwatch antes de crear el servicio. Si no, al intentar crear la tarea, no encontrará dicho Log Group y dará error.

Esto es tan sencillo como ir a la consola de Cloudwatch y pinchar en Actions -> Create log group con el nombre subiendo-mi-contenedor”.

El comando a usar sería:

$ aws ecs register-task-definition –cli-input-json file:///{path_to_file}/container-definitions_nginx.json

Después de este paso deberíamos ver nuestra definición de tarea en el apartado “Task Definitions” del servicio ECS:

Creando un Servicio

Como ya comentamos anteriormente, un Servicio de ECS en AWS permite definir los parámetros del despliegue de ese contenedor/tarea, no a nivel de tarea, sino a nivel de despliegue del mismo en el cluster.

Es decir, número de contenedores del mismo tipo que queremos que gestione el servicio, si queremos que los distribuya en distintas instancias aplicando antiafinidad, los roles que deben asumir para tener ciertas capacidades…

Como en el caso anterior, vamos a usar el cliente de AWS para crearlo, y necesitamos para ello un json parecido al siguiente:

{
  "serviceName": "subiendo_mi_contenedor",
  "cluster" : "subiendo-mi-contenedor",
  "taskDefinition": "subiendo_mi_contenedor",
  "deploymentConfiguration":{
    "maximumPercent": 200,
    "minimumHealthyPercent": 0
  },
  "desiredCount": 1
 }

service_nginx.json

Este ejemplo es bastante sencillo y no tiene, por ejemplo, la opción de acceder al contenedor mediante un balanceador, cosa que sí haremos cuando lo despleguemos con Cloudformation.

El comando a ejecutar desde el cliente de AWS sería:

aws ecs create-service –cli-input-json file:///{path_to_file}/service_nginx.json

A continuación deberíamos ver la tarea desplegada en el cluster (1 servicio y una tarea en ejecución, tal y como lo hemos definido):

Si no apareciera la tarea Running, podemos ver qué ha pasado pinchando sobre el servicio en el apartado “Events” o “Deployment”.

Por otro lado, una vez desplegado, si pinchamos en el Servicio, veríamos el contenedor. En el apartado de “Network bindings”, veremos el enlace que tenemos que usar para acceder desde el navegador al contenedor.

Como se puede ver, al haber forzado el puerto del host al 9999 podremos acceder desde la IP pública del host en el que ha sido desplegado, en esa IP.

Parte de la configuración del servicio.

¿Preparado para producción?

Si has llegado hasta este punto, habrás podido comprobar lo fácil que resulta desplegar un contenedor y un cluster de ECS en AWS, además de ser fácilmente automatizable.

El ejemplo anterior no sería válido, eso sí, para un entorno productivo por varios motivos, los más evidentes son:

  • Para un entorno productivo el “DesiredCount” de cada servicio deberían ser por lo menos dos para que se garantice la alta disponibilidad del servicio.
  • Habría que desplegarlo con la opción de “DistinctInstance”, de forma que garanticemos la antiafinidad en los despliegues. Es decir, que no vayan todos los contenedores de un mismo servicio al mismo nodo (si no, si se apagara ese nodo, aunque tuvieramos 3 contenedores del mismo tipo, se perdería el servicio).
  • Al haber varios servicios, en caso de necesitar exponer el servicio públicamente, necesitaríamos crear un Application Load Balancer que distribuya la carga entre los distintos contenedores del mismo servicio. En este caso, deberíamos crear un “Listener” en el balanceador en el puerto deseado, así como una regla determinada que chequee un “Target Group” (una especie de Health Check del balanceador aplicado a un servicio). Así mismo, en la creación del servicio, deberíamos añadir el parámetro siguiente para establecer la relación entre el Target Group y el Servicio.
  "loadBalancers": [
    {
      "containerName": "nginx",
      "containerPort": 80,
      "targetGroupArn": "{target_group_arn}"
    }
  ]

Por supuesto, recomendamos usar Cloudformation o alguna herramienta similar en la creación del balanceador, listener y reglas y target groups, de forma que estos elementos más estáticos estén controlados mediante código.

  • Si queremos que nuestro servicio sea escalable habría que definir las políticas de escalado (que no son las de las instancias del cluster).

Estos son algunos de los puntos que tendríamos que tener en cuenta para que el despliegue sirva para un entorno productivo, independientemente del resto de configuraciones de seguridad que puede requerir el proyecto  (redes privadas sin acceso desde internet, Security Groups, opciones de autoescalado distintas a las de CPU…).

Conclusión

En definitiva, en este post hemos visto que desplegar un servicio en Amazon es sencillo y fácil de automatizar. Otras ventajas son:

  • La sencillez del despliegue de un cluster en alta disponibilidad y con Multi AZ.
  • La integración con el resto de servicios de Amazon (balanceo, DNS, almacenamiento…), que permite dotar a la solución de una mayor robustez y potencia.
  • Es una solución de infraestructura que se basa en el escalado de Amazon y, por lo tanto,  permite empezar con un coste muy bajo y crecer rápidamente (tanto en tamaño como en cantidad) en situaciones de alta demanda.

Como contrapartida:

  • La sintaxis necesaria para desplegar un servicio (a nivel de definición de tarea y servicio) en Amazon Container Service es distinta de la implantada por Docker y Kubernetes (lo que limita las posibilidades de migrar nuestro proyecto a otra plataforma en un momento determinado).
  • Por otro lado, en este servicio se echa en falta una mayor automatización de algunas funcionalidades que Kubernetes ofrece de forma bastante natural (integración de DNS, balanceo, rolling update con rollback…). Es cierto que Amazon ha ido dando soluciones a todas estas funcionalidades adicionales, pero la integración acaba resultando un poco artificial.
  • Otro punto a tener en cuenta es que esta solución se basa en Docker, y que las últimas versiones que se van publicando en Docker tardan un cierto tiempo en materializarse en las instancias de Amazon de ECS.

Por último cabe mencionar lo esperanzador de las nuevas soluciones que ha anunciado Amazon para la gestión de contenedores (Fargate y EKS), que probablemente no sólo resuelvan los contras arriba mencionados, sino que le permitan entrar pisando fuerte en la competencia de las plataformas de despliegue y gestión de contenedores.

Escribe un comentario