Despliegue y prueba de servicios creados con Terraform en AWS

cecamilo

camilo cabrales

Posted on August 16, 2022

Despliegue y prueba de servicios creados con Terraform en AWS

Terraform es una herramienta de IaC multicloud, basado en el lenguaje de configuración Hansicorp que es un tipo de JSON.

Las plantillas de Terraform generalmente se distribuyen de la siguiente forma:

Template Structure

Provider: Se define la nube en la cual vamos a trabajar.
DataSources-Parameters: Se definen los parámetros o variables que vamos a utilizar dentro de nuestra plantilla.
Resources: Definimos los recursos a desplegar.

La siguiente arquitectura muestra los recursos que vamos a desplegar en AWS con Terraform.

Arquitectura

plantilla

# PROVIDER

provider "aws" {
    region = "${var.aws_regions[0]}"
    #Se definen los tags comunes a todos los resources
    default_tags {
        tags = {
            despliegue = "Terraform AWS"
        }
    }
}

#DATA - PARAMETERS - VARIABLES


locals {
  tags = {
    despliegue = "Terraform AWS"
  }
}
variable "amiinstance" {
    default = "ami-090fa75af13c156b4"
    type = string  
}

variable "aws_regions" {
    default = [
        "us-east-1",
        "us-east-2"
    ]
    type = list
}

variable "aws_instance_size" {
    type = map
    default= {
        small = "t2.micro",
        medium = "t2.medium",
        large = "t2.large"
    }
}

variable "aws_instancedb_size" {
    type = map
    default= {
        small = "db.t2.micro",
        medium = "db.t2.small"
    }
}

variable "tag" {
    default = "template_terraform"
    type = string
}

variable "cidr_subnets" {
    default = [
        "10.0.1.0/24",#public
        "10.0.2.0/28",#private
        "10.0.3.0/28"#private
    ]
    type = list
}

#RESOURCES

resource "aws_vpc" "vpcterraform" {
    cidr_block = "10.0.0.0/16"
    enable_dns_hostnames = true
    tags = {
        Name = "${var.tag}"
    }
}

resource "aws_internet_gateway" "igwterraform" {
    vpc_id = "${aws_vpc.vpcterraform.id}"
    tags = {
        Name = "routeTableTFPublic_${var.tag}"
    }
}

resource "aws_route_table" "routeTableTFPublic" {
    vpc_id = "${aws_vpc.vpcterraform.id}"
    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = "${aws_internet_gateway.igwterraform.id}"
    }
    tags = {
        Name = "routeTableTFPublic_${var.tag}"
    }
}

resource "aws_route_table" "routeTablerivate" {
    vpc_id = "${aws_vpc.vpcterraform.id}"
    tags = {
        Name = "routeTableTFPrivate_${var.tag}"
    }

}

resource "aws_subnet" "publicsubnet1" {
    vpc_id = "${aws_vpc.vpcterraform.id}"
    cidr_block = var.cidr_subnets[0]
    map_public_ip_on_launch = true
    availability_zone = "us-east-1a"
    tags = {
        Name = "publicsubnet1_${var.tag}"
    }
}

resource "aws_route_table_association" "routeTableAssociationTF" {
    subnet_id = "${aws_subnet.publicsubnet1.id}"
    route_table_id = "${aws_route_table.routeTableTFPublic.id}"
}

resource "aws_subnet" "privatesubnet1" {
    vpc_id = "${aws_vpc.vpcterraform.id}"
    cidr_block = var.cidr_subnets[1]
    availability_zone = "us-east-1b"
    tags = {
        Name = "privatesubnet1_${var.tag}"
    }
}

resource "aws_subnet" "privatesubnet2" {
    vpc_id = "${aws_vpc.vpcterraform.id}"
    cidr_block = var.cidr_subnets[2]
    availability_zone = "us-east-1c"
    tags = {
        Name = "privatesubnet2_${var.tag}"
    }
}

resource "aws_db_subnet_group" "dbprivatesubnetgroup"{
    name = "dbprivatesubnetgroup"
    subnet_ids = ["${aws_subnet.privatesubnet1.id}","${aws_subnet.privatesubnet2.id}"]
    tags = {
        Name = "dbprivatesubnetgroup_${var.tag}"
    }
}

resource "aws_iam_role" "instance_role" {
    assume_role_policy = jsonencode({
        "Version": "2012-10-17",
        "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Principal": {
                "Service": [
                    "ec2.amazonaws.com"
                ]
            }
        }
        ]
    })

    tags = {
        tag-key = "role_${var.tag}"
    }
}

resource "aws_iam_policy" "policy"{
    policy = jsonencode({
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "cloudwatch:PutMetricData",
                    "ds:CreateComputer",
                    "ds:DescribeDirectories",
                    "ec2:DescribeInstanceStatus",
                    "logs:*",
                    "ssm:*",
                    "ec2messages:*"
                ],
                "Resource": "*"
            },
            {
                "Effect": "Allow",
                "Action": "iam:CreateServiceLinkedRole",
                "Resource": "arn:aws:iam::*:role/aws-service-role/ssm.amazonaws.com/AWSServiceRoleForAmazonSSM*",
                "Condition": {
                    "StringLike": {
                        "iam:AWSServiceName": "ssm.amazonaws.com"
                    }
                }
            },
            {
                "Effect": "Allow",
                "Action": [
                    "iam:DeleteServiceLinkedRole",
                    "iam:GetServiceLinkedRoleDeletionStatus"
                ],
                "Resource": "arn:aws:iam::*:role/aws-service-role/ssm.amazonaws.com/AWSServiceRoleForAmazonSSM*"
            },
            {
                "Effect": "Allow",
                "Action": [
                    "ssmmessages:CreateControlChannel",
                    "ssmmessages:CreateDataChannel",
                    "ssmmessages:OpenControlChannel",
                    "ssmmessages:OpenDataChannel"
                ],
                "Resource": "*"
            }
        ]
    })
    tags = {
        tag-key = "policy_${var.tag}"
    }
}

resource "aws_iam_role_policy_attachment" "policy_attachment" {
    role = "${aws_iam_role.instance_role.name}"
    policy_arn = "${aws_iam_policy.policy.arn}"
}

resource "aws_iam_instance_profile" "instance_profile" {
    role  = "${aws_iam_role.instance_role.name}"
}

resource "aws_security_group" "sg_instance" {
    vpc_id = "${aws_vpc.vpcterraform.id}"
    ingress {
        from_port = 22
        to_port = 22
        protocol ="tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }
    ingress {
        from_port   = 80
        to_port     = 80
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }
    egress {
        from_port   = 0
        to_port     = 0
        protocol    = "-1"
        cidr_blocks = ["0.0.0.0/0"]
    }
    tags = {
        Name = "sg_instance_${var.tag}"
    }
}

resource "aws_instance" "instancia" {
    ami= "${var.amiinstance}"
    instance_type = "${var.aws_instance_size.small}"
    subnet_id = "${aws_subnet.publicsubnet1.id}"
    vpc_security_group_ids = [ "${aws_security_group.sg_instance.id}" ]
    tags = {
        Name = "instancia_${var.tag}"
    }
    iam_instance_profile = "${aws_iam_instance_profile.instance_profile.id}"
    depends_on = [aws_internet_gateway.igwterraform]
    user_data = <<EOF
    #!/bin/bash
    yum update -y
    yum install httpd -y
    service httpd start
    chkconfig httpd on
    cd /var/www/html
    echo "<html><body><h1>Terraform Instancia: $(hostname -f)</h1></body></html>" > index.html
    EOF

}

resource "aws_security_group" "sgbd" {
    name = "sg"
    vpc_id = "${aws_vpc.vpcterraform.id}"
    ingress {
        from_port = 3306
        to_port = 3306
        protocol ="tcp"
        cidr_blocks = ["${aws_instance.instancia.private_ip}/32"]
    }
    tags = {
        Name = "sgbd_${var.tag}"
    }
}

resource "aws_db_instance" "dbinstance" {
    db_subnet_group_name = "${aws_db_subnet_group.dbprivatesubnetgroup.name}"
    engine = "mysql"
    db_name = "dbterraform"
    engine_version = "5.7.28"
    instance_class = "${var.aws_instancedb_size.small}"
    username = "admin"
    password = "admin12345678"
    allocated_storage = 10
    skip_final_snapshot = true
    vpc_security_group_ids =  [ "${aws_security_group.sgbd.id}" ]
    tags = {
        Name = "dbinstance_${var.tag}"
    }
}

output "endpoint" {
    value = "${aws_instance.instancia.public_ip}"
}

Enter fullscreen mode Exit fullscreen mode

Terraform tiene comandos para iniciar, validar, desplegar y destruir los recursos definidos en nuestras plantillas (Antes de usar los siguientes comandos recuerden configurar sus access keys con el comando aws configure).

1.terraform init: Configura el directorio donde se encuentra nuestra plantilla para que pueda ser usado con Terraform.

Init

2.terraform validate: Valida que la plantilla no tenga errores de sintaxis o de lógica.

Validate

3.terraform plan: Nos muestra una descripción de los recursos que van a ser desplegados, valida si hay cambios en nuestros recursos (en caso de actualización) y valida que nuestra plantilla no tenga errores.

Plan

4.terraform apply: Nos va a mostrar la infraestructura que se va a desplegar y nos va a preguntar que sí estamos de acuerdo por lo que debemos escribir yes para que inicie el despliegue de nuestros servicios.

Terraform apply

Al final nos va a mostrar la cantidad de recursos que se crearon y los outputs que tenemos definidos en la plantilla.

Terraform apply

Terraform al desplegar nuestra infraestructura crea el archivo
terraform.tfstate donde se guarda la información de los recursos desplegados.
Podemos ver qué recursos se desplegaron con el comando: terraform state list y podemos ver la información del recurso con el comando terraform state show nombre_del_recurso.

Una vez que se han creado todos nuestros recursos, vamos a verlos en la consola de AWS. Para esto buscamos resources groups y le damos click.

Resources Groups

Ya en la pantalla principal damos click en Tag Editor, seleccionamos todas las regiones, todos los recursos, en los tags escribimos despliegue - Terraform AWS y damos click en le botón Search Resources. Esto nos va a traer todos los recursos que tiene este tag.

Tag Editor

El resultado de la búsqueda nos va a mostrar los recursos que cumplen las condiciones anteriores.

Search Tag Editor

Este resultado lo obtuvimos ya que a todos los recursos de la plantilla les asignamos el tag: Name: despliegue - Value: Terraform AWS, en el siguiente fragmento de la plantilla:

provider "aws" {
    region = "${var.aws_regions[0]}"
    #Se definen los tags comunes a todos los resources
    default_tags {
        tags = {
            despliegue = "Terraform AWS"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Ahora vamos a validar el funcionamiento de nuestra aplicación. En la pantalla de resultados damos click en la instancia de EC2 que creamos.

Click Instance

Los que nos lleva a la pantalla de EC2 y copiamos la Ip publica de la instancia y la colocamos en una nueva pestaña de nuestro navegador.

Click Instance

lo que nos va a mostrar lo siguiente:

Click Instance

Este resultado lo obtuvimos ya que en nuestra definición de la instancia dentro dentro de la propiedad user data le pedimos que instalara apache y creara una pagina web.

resource "aws_instance" "instancia" {
    ami= "${var.amiinstance}"
    instance_type = "${var.aws_instance_size.small}"
    subnet_id = "${aws_subnet.publicsubnet1.id}"
    vpc_security_group_ids = [ "${aws_security_group.sg_instance.id}" ]
    tags = {
        Name = "instancia_${var.tag}"
    }
    iam_instance_profile = "${aws_iam_instance_profile.instance_profile.id}"
    user_data = <<EOF
    #!/bin/bash
    yum update -y
    yum install httpd -y
    service httpd start
    chkconfig httpd on
    cd /var/www/html
    echo "<html><body><h1>Terraform Instancia: $(hostname -f)</h1></body></html>" > index.html
    yum install mysql
    EOF

}
Enter fullscreen mode Exit fullscreen mode

De acuerdo a la anterior definición de nuestra instancia, no definimos un access key para poder ingresar a ella desde la terminal.
Para acceder a la instancia y validar nuestra base de datos vamos a buscar el servicio System Manager.

System Manager

En la pagina principal damos click Session Manager, después en Start Session.

Session Manager

Ahora seleccionamos la instancia que creamos y damos click en el botón Start Session

Start Session

Lo que nos va abrir una terminal como la siguiente.

Terminal

Donde podemos ejecutar los comandos para acceder a la base de datos creada con la plantilla:

Instalar mysql (para usar como cliente):

sudo su
sudo yum install mysql
Enter fullscreen mode Exit fullscreen mode

Conectarse a la base de datos:

mysql -h reemplezar_por_endpoint_basededatos -u admin -p database
Enter fullscreen mode Exit fullscreen mode

Para lograr abrir la terminal desde System Manager se crearon los siguientes recursos en la plantilla:

aws_iam_role
aws_iam_policy
aws_iam_role_policy_attachment
aws_iam_instance_profile
Enter fullscreen mode Exit fullscreen mode

para asociar el recurso aws_iam_instance_profile a nuestra instancia.

5.terraform destroy: al ejecutar el comando nos muestra los recursos que se van a eliminar, después nos solicita que autoricemos la eliminación por lo que debemos escribir yes.

Destroy

Con la ejecución de estos comando terminamos con el despliegue y la liberación de los recursos desplegados con Terraform.

Terraform me parece practico ya que su sintaxis se me asemeja al desarrollo (he trabajado como desarrollador). Una de las cosas que creo mas importantes para trabajar con herramientas IAC es la documentación la cual me pareció sencilla pero practica.

Para mejorar la plantilla pueden crear archivos separados para las variables, para los outputs y realizar la misma ejecución de comandos, esto con el fin que nuestra plantilla quede más organizada y legible.

Camilo Cabrales

Referencias

Documentación

Instalación

Comando Init

Comando Plan

Comando Destroy

💖 💪 🙅 🚩
cecamilo
camilo cabrales

Posted on August 16, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related