Ir al contenido principal

Creación en AWS de una ECS task Fargate con Terraform

En este artículo se explicará cómo con la tecnología Fargate que utiliza ECS se ejecutan contenedores. Para llevar a cabo esta tarea, se hace uso del software de infraestructura de Terraform.

Con el servicio Amazon Elastic Container Service (Amazon ECS) proporcionado por AWS se administra contenedores con gran facilidad. Esta herramienta hace que los contenedores sean escalables y rápidos facilitando su ejecución, detención y administración en un clúster. El lanzamiento Fargate es una tecnología específica de ECS la cual permite alojar el clúster en una infraestructura sin servidor, si se requiriera más control lo haríamos mediante otro tipo de lanzamiento (Amazon ECS).

Qué es Terraform y cuáles son los ficheros básicos para el lanzamiento de la task

Para crear el plan de construcción de esta infraestructura se utiliza Terraform, software de código libre que permite a partir de otro lenguaje crear dicho plan. Esto sería infraestructura como código (Infraestructura as Code). Con esta tecnología se simplificará la administración de la infraestructura a partir de una sintaxis simple y unificada. Una ventaja importante de trabajar con Terraform es que las configuraciones que se implementan pueden ser reutilizadas y compartidas en diferentes proyectos.

En este punto, antes de pasar a la definición de la ECS task de tipo Fargate, se van a definir los ficheros básicos necesarios para el lanzamiento de la task que se explicará en el siguiente apartado.

En primer lugar, se va a crear el fichero provider para Terraform. Este fichero será usado para inicializar AWS en nuestro proyecto en la versión que se requiera, el archivo tendrá el nombre de provider.tf con el consiguiente contenido:

provider "aws" {
  version = ">= 1.58.0, <= 2.0.0"
  region = var.aws_region
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
}

Después de esto, se necesita un archivo donde se encuentren las definiciones de variables. El archivo se va a llamar variables.tf y en el se va a definir tanto las variables de autenticación como las variables que necesita la aplicación:

variable "aws_access_key" {} 
variable "aws_secret_key" {} 
variable "aws_region" {}

Seguidamente, en otro archivo llamado terraform.tfvars se añade el valor de las variables definidas anteriormente. Este archivo va a ser cargado por Terraform automáticamente:

aws_access_key = "aws-access-key" 
aws_secret_key = "aws-secret-key" 
aws_region = "eu-west-1"

El siguiente paso sería la creación de todos los componentes de red necesarios: VPC, subredes y clúster donde se va a definir nuestra ECS task. Por brevedad, se va a suponer que todas son subredes públicas. El archivo se va a llamar network.tf:

resource "aws_ecs_cluster" "cluster" {
  name = "cluster-name"
}
resource "aws_vpc" "aws-vpc" { 
  cidr_block = "10.0.0.0/16" 
  enable_dns_hostnames = true
}

Y por último, para el correcto lanzamiento de la ECS task hace falta definir las políticas y roles en función de cuáles son los requerimientos que le hace falta a nuestra tarea. En un archivo llamado iam.tf se añade este código con la definición de los dos roles y políticas que va a necesitar nuestra tarea:

resource "aws_iam_role" "ecs_task_execution_role" {
  name = "role-name"
 
  assume_role_policy = <<EOF
{
 "Version": "2012-10-17",
 "Statement": [
   {
     "Action": "sts:AssumeRole",
     "Principal": {
       "Service": "ecs-tasks.amazonaws.com"
     },
     "Effect": "Allow",
     "Sid": ""
   }
 ]
}
EOF
}

resource "aws_iam_role" "ecs_task_role" {
  name = "role-name-task"
 
  assume_role_policy = <<EOF
{
 "Version": "2012-10-17",
 "Statement": [
   {
     "Action": "sts:AssumeRole",
     "Principal": {
       "Service": "ecs-tasks.amazonaws.com"
     },
     "Effect": "Allow",
     "Sid": ""
   }
 ]
}
EOF
}
 
resource "aws_iam_role_policy_attachment" "ecs-task-execution-role-policy-attachment" {
  role       = aws_iam_role.ecs_task_execution_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

resource "aws_iam_role_policy_attachment" "task_s3" {
  role       = "${aws_iam_role.ecs_task_role.name}"
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}

Creación ECS Task de tipo Fargate

AWS Fargate es un motor que trabaja sin servidor y nos puede ayudar tanto con ECS como con Amazon Elastic Kubernetes Service (EKS). Con esta tecnología no tenemos la necesidad de administrar ni aprovisionar ningún servidor. Así nos permitirá hacer uso solamente de los recursos que nuestra aplicación necesite y mejoraremos la seguridad aislando las aplicaciones.

Para poder trabajar con AWS Fargate se tiene que verificar si está disponible en la región donde estamos trabajando ya que no todas las regiones tienen la posibilidad de utilizar AWS Fargate, para esto recomiendo ir a la documentación de AWS. En nuestro artículo se va a utilizar como ejemplo la región Europa (Irlanda): eu-west-1.

aws

Haciendo uso de Terraform se va a implementar la definición de la tarea de ECS para poder ejecutar contenedores Docker:

resource "aws_ecs_task_definition" "definition" {}

Para la definición de una tarea de ECS hay una serie de parámetros de los cuáles se va a hacer uso, algunos son obligados y también hay otros tantos opcionales pero útiles para nuestro caso:

  • family: es una parámetro obligado y de tipo string. Este parámetro es el nombre para la definición de tarea, a la cual AWS le asignará también un número de revisión.

  • taskRoleArn: es un parámetro opcional y de tipo string. Al definir una tarea, con este parámetro se le puede proporcionar un rol de IAM permitiendo de esta forma que los contenedores tengan los permisos necesarios para llamar a otros servicios de AWS.

  • executionRoleArn: parámetro opcional y de tipo string. Con este parámetro se puede proporcionar un rol de ejecución de tareas para permitir a los contenedores que puedan extraer las imágenes y publicar registros en CloudWatch en su nombre.

  • networkMode: parámetro no requerido de tipo string. Este parámetro es el modo de red de Docker que se va a utilizar en los contenedores de la tarea. Hay diferentes tipos, en nuestro caso se va a hacer uso de awsvpc. Con este tipo, se le asigna una interfaz de red elástica.

  • cpu: parámetro no requerido y de tipo número entero. Con esto se define el número de unidades cpu que Amazon ECS reservará para el contenedor.

  • memory: es de tipo número entero y opcional. Se define la cantidad (en MiB) de memoria que se reserva para el contenedor.

Para especificar el tipo de lanzamiento que se va a utilizar en la definición de tarea, utilizamos el parámetro:

  • requiresCompatibilities: en nuestro caso llevará el nombre de FARGATE. Es un parámetro no requerido y de tipo array de string. Con esto se permite garantizar que todos los parámetros usados en la definición están cumpliendo los requisitos de su tipo de lanzamiento.

resource "aws_ecs_task_definition" "definition" {
  family                   = "task_definition_name"
  task_role_arn            = "${var.ecs_task_role}"
  execution_role_arn       = "${var.ecs_task_execution_role}"
  network_mode             = "awsvpc"
  cpu                      = "256"
  memory                   = "1024"
  requires_compatibilities = ["FARGATE"]
  }

Una vez que ya están definidos todos los requerimientos y parámetros opcionales de los que se quiere hacer uso, pasamos a definir los contenedores que que va a ejecutar nuestra tarea. Es un tipo de parámetro obligatorio y de tipo lista proporcionada como un único documento JSON.

Se va a especificar una serie de parámetros permitidos en una definición de contenedor, los dos requerimientos de obligada definición y más importantes serían:

  • name: de tipo string. Es el nombre de un contenedor y se admiten hasta un total de 255 caracteres

  • image: de tipo string. Va a definir la imagen que se utiliza para iniciar el contenedor. En nuestro caso se especifica la url de una imagen del repositorio de Amazon ECR subida previamente y su versión.

La imagen de la que hace uso el contenedor se ha creado a partir de un Dockerfile. Esta imagen se almacena en el registro de contenedores de docker que nos proporciona Amazon con Amazon Elastic Container Registry.

docker

Otro parámetro opcional pero muy útil para cualquier definición de contenedor es:

  • logConfiguration: de tipo objeto LogConfiguration. Para configurar el registro de los contenedores tenemos que tener en cuenta que para las tareas con lanzamiento del tipo Fargate se tiene que instalar un software adicional fuera de la tarea. En nuestro caso se hace uso del controlador de registro awslogs

  • environment: de tipo array de objetos. Con este parámetro transferimos las variables de entorno a nuestro contenedor.

  • secrets: tipo array de objetos: name y valueFrom. El parámetro secrets junto con valueFrom permite transferir un valor al contenedor proporcionando un ARN completo permitiendo hacer uso de AWS Secrets Manager en nuestro ejemplo

Aquí se puede ver una definición completa de un contenedor como documento JSON

container_definitions = <<DEFINITION
[
  {
    "image": "${var.account}.dkr.ecr.eu-west-1.amazonaws.com/project:latest",
    "name": "project-container",
    "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-region" : "eu-west-1",
                    "awslogs-group" : "stream-to-log-fluentd",
                    "awslogs-stream-prefix" : "project"
                }
            },
    "secrets": [{
        "name": "secret_variable_name",
        "valueFrom": "arn:aws:ssm:region:acount:parameter/parameter_name"
    }],           
    "environment": [
            {
                "name": "bucketName",
                "value": "${var.bucket_name}"
            },
            {
                "name": "folder",
                "value": "${var.folder}"
            }
        ]
    }
  
]
DEFINITION
}

Ejecución de Terraform y lanzamiento de la ECS task

Para el lanzamiento de la ECS task se siguen los siguientes pasos:

  1. Nos situamos donde se encuentra nuestro código y ejecutamos el comando terraform init desde el terminal:

  2. Luego se puede ejecutar el comando terraform plan y obtendremos como resultado el plan de ejecución. Esto es muy útil para verificar si cumple con tus expectativas sin llegar a realizar los cambios.

  3. Para terminar, ejecutamos el comando terraform apply en la línea de comandos y construimos de este modo toda la infraestructura:

Conclusión

Como se ha podido apreciar, gracias a AWS Fargate junto con la potencia que nos proporciona Terraform se puede levantar, administrar y lanzar una aplicación dentro de un contenedor con gran rapidez y facilidad. Esto es solo un pequeño ejemplo dentro de todas las utilidades que proporciona AWS.

Referencias

https://docs.aws.amazon.com/es_es/AmazonECS/latest/developerguide/getting-started-fargate.html

https://docs.aws.amazon.com/es_es/AmazonECS/latest/developerguide/create-task-definition.html

https://docs.aws.amazon.com/es_es/AmazonECS/latest/developerguide/task_definition_parameters.html

https://www.terraform.io/docs/providers/aws/r/ecs_task_definition.html