Infraestructura como código con Terraform

Nuestro compañero João Ferreira, a través de su experiencia como Site Reliability Engineer en dareCode, comparte con nosotros un gran artículo en el que nos introducirá a la Infraestructura como código mediante el software de código libre Terraform.

En este artículo veremos desde qué es la Infraestructura como código (Infrastructure as Code) hasta los primeros pasos en el uso de Terraform.

Índice de Contenidos

En este artículo encontrarás:

¿Qué es la Infraestructura como Código?

La Infraestructura como Código es una práctica utilizada en el despliegue automático de infraestructura utilizando scripts o herramientas declarativas.

La idea de este concepto es utilizar procesos y herramientas típicamente asociadas a desarrollo de software en infraestructura, lo que incluye control de versiones, testing, etc.

Esta práctica permite automatizar las tareas recurrentes a nivel de infraestructura como el aprovisionamiento o configuración y, como el proceso es automático, se puede aplicar a una o miles de máquinas.

De esta forma podremos disminuir el riesgo y aumentar la velocidad del despliegue de infraestructura.

La Infraestructura como Código puede ser utilizada para aprovisionar y configurar todos los componentes asociados a servidores físicos (bare metal) así como máquinas virtuales.

Algunos ejemplos de herramientas de IaC son: Terraform, AWS CloudFormation o Azure Resource Manager, entre otros.

Introducción a Terraform

Terraform es una herramienta creada por HashiCorp para la creación y gestión de infraestructura.

Esta herramienta permite construir, cambiar y versionar infraestructura de una manera segura y eficiente.

Terraform fue construido con una estructura modular y, con la utilización de providers permite manejar infraestructura tanto en los diversos servicios cloud así como en soluciones in-house.

Los providers son responsables de la interacción entre la herramienta y las APIs de los servicios como AWS, Azure o OpenStack.

Utilizando código de Terraform se pueden manejar componentes low-level como VMs, storage y networking pero también componentes high-level como DNS, usuarios, etc.

El workflow típico con esta herramienta es la creación de infraestructura “nueva” utilizando los recursos definidos en el código, pero es también posible definir recursos y asociarlos a infraestructura ya existente.

Si necesitas ayuda o quieres más información no dudes en ponerte en contacto con nosotros.

Ventajas de utilizar Terraform

Algunas de las ventajas de la utilización de Terraform son:
  1. Posibilita el despliegue y destrucción de la infraestructura de una forma segura y eficiente: Resulta muy útil en ambientes efímeros o de pruebas y además nos facilita el crear automatizaciones de tipo self-service.
  2. Toda la infraestructura definida en un lugar.
  3. Permite hacer escalado de la infraestructura también de manera segura y eficiente.
  4. Posibilidad de aplicar el control de versiones a la infraestructura.
  5. Cloud agnóstica.

Muchos de los servicios cloud utilizan el modelo «pay as you go» y Terraform permite reducir los costes ya que permite el despliegue y destrucción de la infraestructura de una forma segura.

Permite también el despliegue de entornos de pruebas que solo son necesarios durante unos ejercicios de duración determinada y que después pueden ser destruidos, resultando en un coste mínimo para la ejecución de las pruebas.

Cómo comenzar a usar Terraform

A continuación te explicaremos de forma práctica: Cómo inicializar Terraform y cómo crear el ambiente de desarrollo.

Cómo inicializar Terraform

El workflow normalmente utilizado en un proyecto de Terraform es el siguiente:
  1. Init: Inicialización del proyecto.
  2. Plan: Planificación (dry-run) de los recursos definidos en el código.
  3. Apply: Despliegue de los recursos definidos en el código.
  4. Destroy: Destrucción de los recursos definidos en el código (si fuese necesario).

Un proyecto de Terraform necesita ser inicializado en su working directory, donde están los ficheros de configuración. Este proceso trata de iniciar:

  1. Backend: Servicio utilizado para almacenar ficheros de estado.
  2. Instalación de child modules.
  3. Instalación de plugins y providers.

Ejemplo de inicialización de un proyecto, utilizando el provider de AWS:

  1. Crear carpeta del proyecto
  2. Crear el fichero main.tf con el contenido:

provider "aws" {
  region = "eu-west-1"
  access_key = "my-access-key"
  secret_key = "my-secret-key"
}

3. Ejecutar terraform init.

Tenemos ahora el proyecto preparado para desplegar infraestructura en AWS y podemos empezar a definir los recursos que necesiten ser creados.

Cómo crear el ambiente de desarrollo

Como ejemplo de la creación de un ambiente de desarrollo vamos a utilizar el proyecto creado en el ejemplo anterior, que ya está preparado para desplegar recursos en AWS.

El ambiente de desarrollo consiste en:

  • Una VPC.
  • Una Subnet.
  • Dos instancias EC2.
  • Una base de datos RDS.

Para la creación de la VPC (Virtual Private Cloud) vamos a utilizar el recurso aws_vpc.

resource "aws_vpc" "projectvpc" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "default"
  
  tags = {
    Name = "projectvpc"
  }
}

Para la creación de la Subnet vamos a utilizar el recurso aws_subnet.

resource "aws_subnet" "projectsubnet" {
  vpc_id     = aws_vpc.projectvpc.id
  cidr_block = "10.0.1.0/24"

  tags = {
    Name = "projectsubnet"
  }

}

Nota: Se puede utilizar información de otros recursos, en este caso el valor de “vpc_id” es extraído del recurso “projectvpc”.

Para la creación de las instancias EC2 vamos a utilizar el recurso aws_instance.

resource "aws_instance" "frontend" {
  ami           = "ami-0ac80df6eff0e70b5"
  instance_type = "t2.micro"
  subnet_id = aws_subnet.projectsubnet.id

  tags = {
    Name = "Frontend"
  }
}

resource "aws_instance" "backend" {
  ami           = "ami-0ac80df6eff0e70b5"
  instance_type = "t2.micro"
  subnet_id = aws_subnet.projectsubnet.id

  tags = {
    Name = "Backend"
  }
}

“subnet_id” asigna las instancias a la subnet que creamos previamente y que está asociada a la VPC creada por nosotros.

Por último, tenemos la creación de la base de datos en RDS, para eso vamos a utilizar el recurso aws_db_instance.

resource "aws_db_instance" "projectdb" {    
    allocated_storage    = 20
    storage_type         = "gp2"
    engine               = "mysql"
    engine_version       = "8.0.19"
    instance_class       = "db.t2.micro"
    name                 = "projectdb"
    username             = "foo"
    password             = "foobarbaz"
    parameter_group_name = "default.mysql8.0"
    skip_final_snapshot = true
} 

Ahora que tenemos los recursos definidos en main.tf, seguimos el workflow ejecutando:

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_db_instance.projectdb will be created
  + resource "aws_db_instance" "projectdb" {
      + address                               = (known after apply)
      + allocated_storage                     = 20
      + apply_immediately                     = (known after apply)
      + arn                                   = (known after apply)
      + auto_minor_version_upgrade            = true
      + availability_zone                     = (known after apply)
      + backup_retention_period               = (known after apply)
      + backup_window                         = (known after apply)
      + ca_cert_identifier                    = (known after apply)
      + character_set_name                    = (known after apply)
      + copy_tags_to_snapshot                 = false
      + db_subnet_group_name                  = (known after apply)
      + delete_automated_backups              = true
      + endpoint                              = (known after apply)
      + engine                                = "mysql"
      + engine_version                        = "8.0.19"
      + hosted_zone_id                        = (known after apply)
      + id                                    = (known after apply)
      + identifier                            = (known after apply)
      + identifier_prefix                     = (known after apply)
      + instance_class                        = "db.t2.micro"
      + kms_key_id                            = (known after apply)
      + license_model                         = (known after apply)
      + maintenance_window                    = (known after apply)
      + monitoring_interval                   = 0
      + monitoring_role_arn                   = (known after apply)
      + multi_az                              = (known after apply)
      + name                                  = "projectdb"
      + option_group_name                     = (known after apply)
      + parameter_group_name                  = "default.mysql8.0"
      + password                              = (sensitive value)
      + performance_insights_enabled          = false
      + performance_insights_kms_key_id       = (known after apply)
      + performance_insights_retention_period = (known after apply)
      + port                                  = (known after apply)
      + publicly_accessible                   = false
      + replicas                              = (known after apply)
      + resource_id                           = (known after apply)
      + skip_final_snapshot                   = false
      + status                                = (known after apply)
      + storage_type                          = "gp2"
      + timezone                              = (known after apply)
      + username                              = "foo"
      + vpc_security_group_ids                = (known after apply)
    }

  # aws_instance.backend will be created
  + resource "aws_instance" "backend" {
      + ami                          = "ami-0ac80df6eff0e70b5"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.micro"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = (known after apply)
      + network_interface_id         = (known after apply)
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "Backend"
        }
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_instance.frontend will be created
  + resource "aws_instance" "frontend" {
      + ami                          = "ami-0ac80df6eff0e70b5"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.micro"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = (known after apply)
      + network_interface_id         = (known after apply)
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "Frontend"
        }
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_subnet.projectsubnet will be created
  + resource "aws_subnet" "projectsubnet" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = (known after apply)
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.1.0/24"
      + id                              = (known after apply)
      + ipv6_cidr_block                 = (known after apply)
      + ipv6_cidr_block_association_id  = (known after apply)
      + map_public_ip_on_launch         = false
      + owner_id                        = (known after apply)
      + tags                            = {
          + "Name" = "projectsubnet"
        }
      + vpc_id                          = (known after apply)
    }

  # aws_vpc.projectvpc will be created
  + resource "aws_vpc" "projectvpc" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = (known after apply)
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "Name" = "projectvpc"
        }
    }

Plan: 5 to add, 0 to change, 0 to destroy.

Si el comando se ejecuta correctamente, podemos pasar al despliegue de la infraestructura.

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_db_instance.projectdb will be created
  + resource "aws_db_instance" "projectdb" {
      + address                               = (known after apply)
      + allocated_storage                     = 20
      + apply_immediately                     = (known after apply)
      + arn                                   = (known after apply)
      + auto_minor_version_upgrade            = true
      + availability_zone                     = (known after apply)
      + backup_retention_period               = (known after apply)
      + backup_window                         = (known after apply)
      + ca_cert_identifier                    = (known after apply)
      + character_set_name                    = (known after apply)
      + copy_tags_to_snapshot                 = false
      + db_subnet_group_name                  = (known after apply)
      + delete_automated_backups              = true
      + endpoint                              = (known after apply)
      + engine                                = "mysql"
      + engine_version                        = "8.0.19"
      + hosted_zone_id                        = (known after apply)
      + id                                    = (known after apply)
      + identifier                            = (known after apply)
      + identifier_prefix                     = (known after apply)
      + instance_class                        = "db.t2.micro"
      + kms_key_id                            = (known after apply)
      + license_model                         = (known after apply)
      + maintenance_window                    = (known after apply)
      + monitoring_interval                   = 0
      + monitoring_role_arn                   = (known after apply)
      + multi_az                              = (known after apply)
      + name                                  = "projectdb"
      + option_group_name                     = (known after apply)
      + parameter_group_name                  = "default.mysql8.0"
      + password                              = (sensitive value)
      + performance_insights_enabled          = false
      + performance_insights_kms_key_id       = (known after apply)
      + performance_insights_retention_period = (known after apply)
      + port                                  = (known after apply)
      + publicly_accessible                   = false
      + replicas                              = (known after apply)
      + resource_id                           = (known after apply)
      + skip_final_snapshot                   = false
      + status                                = (known after apply)
      + storage_type                          = "gp2"
      + timezone                              = (known after apply)
      + username                              = "foo"
      + vpc_security_group_ids                = (known after apply)
    }

  # aws_instance.backend will be created
  + resource "aws_instance" "backend" {
      + ami                          = "ami-0ac80df6eff0e70b5"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.micro"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = (known after apply)
      + network_interface_id         = (known after apply)
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "Backend"
        }
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_instance.frontend will be created
  + resource "aws_instance" "frontend" {
      + ami                          = "ami-0ac80df6eff0e70b5"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.micro"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = (known after apply)
      + network_interface_id         = (known after apply)
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "Frontend"
        }
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_subnet.projectsubnet will be created
  + resource "aws_subnet" "projectsubnet" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = (known after apply)
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.1.0/24"
      + id                              = (known after apply)
      + ipv6_cidr_block                 = (known after apply)
      + ipv6_cidr_block_association_id  = (known after apply)
      + map_public_ip_on_launch         = false
      + owner_id                        = (known after apply)
      + tags                            = {
          + "Name" = "projectsubnet"
        }
      + vpc_id                          = (known after apply)
    }

  # aws_vpc.projectvpc will be created
  + resource "aws_vpc" "projectvpc" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = (known after apply)
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "Name" = "projectvpc"
        }
    }

Plan: 5 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_vpc.projectvpc: Creating...
aws_db_instance.projectdb: Creating...
aws_vpc.projectvpc: Creation complete after 8s [id=vpc-0b8ea7d89d6957812]
aws_subnet.projectsubnet: Creating...
aws_db_instance.projectdb: Still creating... [10s elapsed]
aws_subnet.projectsubnet: Creation complete after 3s [id=subnet-0f186878587a12e9d]
aws_instance.frontend: Creating...
aws_instance.backend: Creating...
aws_db_instance.projectdb: Still creating... [20s elapsed]
aws_instance.frontend: Still creating... [10s elapsed]
aws_instance.backend: Still creating... [10s elapsed]
aws_db_instance.projectdb: Still creating... [30s elapsed]
aws_instance.frontend: Still creating... [20s elapsed]
aws_instance.backend: Still creating... [20s elapsed]
aws_instance.frontend: Creation complete after 28s [id=i-089e95f099ec2cc14]
aws_instance.backend: Creation complete after 28s [id=i-00987e09c1aa34fa6]
aws_db_instance.projectdb: Still creating... [40s elapsed]
aws_db_instance.projectdb: Still creating... [50s elapsed]
aws_db_instance.projectdb: Still creating... [1m0s elapsed]
aws_db_instance.projectdb: Still creating... [1m10s elapsed]
aws_db_instance.projectdb: Still creating... [1m20s elapsed]
aws_db_instance.projectdb: Still creating... [1m30s elapsed]
aws_db_instance.projectdb: Still creating... [1m40s elapsed]
aws_db_instance.projectdb: Still creating... [1m50s elapsed]
aws_db_instance.projectdb: Still creating... [2m0s elapsed]
aws_db_instance.projectdb: Still creating... [2m10s elapsed]
aws_db_instance.projectdb: Still creating... [2m20s elapsed]
aws_db_instance.projectdb: Still creating... [2m30s elapsed]
aws_db_instance.projectdb: Still creating... [2m40s elapsed]
aws_db_instance.projectdb: Still creating... [2m50s elapsed]
aws_db_instance.projectdb: Still creating... [3m0s elapsed]
aws_db_instance.projectdb: Still creating... [3m10s elapsed]
aws_db_instance.projectdb: Creation complete after 3m16s [id=terraform-20200715090236286000000001]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

Siguiendo este workflow, creamos todos los recursos necesarios para el ambiente de desarrollo y nuestros desarrolladores pueden ahora utilizar este ambiente para su trabajo.

Después de terminadas las pruebas o mismo la jornada laboral, Terraform permite destruir la infraestructura y volver a levantarla cuando sea necesario.

aws_vpc.projectvpc: Refreshing state... [id=vpc-0fc5503590456595b]
aws_db_instance.projectdb: Refreshing state... [id=terraform-20200715092226965600000001]
aws_subnet.projectsubnet: Refreshing state... [id=subnet-07e75ec5bd2f89076]
aws_instance.frontend: Refreshing state... [id=i-0c2476d500414e610]
aws_instance.backend: Refreshing state... [id=i-0a7471de2e4f4b7ac]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_db_instance.projectdb will be destroyed
  - resource "aws_db_instance" "projectdb" {
      - address                               = "terraform-20200715092226965600000001.c7dj50k0zglh.us-east-1.rds.amazonaws.com" -> null
      - allocated_storage                     = 20 -> null
      - arn                                   = "arn:aws:rds:us-east-1:292166814392:db:terraform-20200715092226965600000001" -> null
      - auto_minor_version_upgrade            = true -> null
      - availability_zone                     = "us-east-1c" -> null
      - backup_retention_period               = 0 -> null
      - backup_window                         = "05:25-05:55" -> null
      - ca_cert_identifier                    = "rds-ca-2019" -> null
      - copy_tags_to_snapshot                 = false -> null
      - db_subnet_group_name                  = "default" -> null
      - delete_automated_backups              = true -> null
      - deletion_protection                   = false -> null
      - enabled_cloudwatch_logs_exports       = [] -> null
      - endpoint                              = "terraform-20200715092226965600000001.c7dj50k0zglh.us-east-1.rds.amazonaws.com:3306" -> null
      - engine                                = "mysql" -> null
      - engine_version                        = "8.0.19" -> null
      - hosted_zone_id                        = "Z2R2ITUGPM61AM" -> null
      - iam_database_authentication_enabled   = false -> null
      - id                                    = "terraform-20200715092226965600000001" -> null
      - identifier                            = "terraform-20200715092226965600000001" -> null
      - instance_class                        = "db.t2.micro" -> null
      - iops                                  = 0 -> null
      - license_model                         = "general-public-license" -> null
      - maintenance_window                    = "thu:06:05-thu:06:35" -> null
      - max_allocated_storage                 = 0 -> null
      - monitoring_interval                   = 0 -> null
      - multi_az                              = false -> null
      - name                                  = "projectdb" -> null
      - option_group_name                     = "default:mysql-8-0" -> null
      - parameter_group_name                  = "default.mysql8.0" -> null
      - password                              = (sensitive value)
      - performance_insights_enabled          = false -> null
      - performance_insights_retention_period = 0 -> null
      - port                                  = 3306 -> null
      - publicly_accessible                   = false -> null
      - replicas                              = [] -> null
      - resource_id                           = "db-EUIQ6EQNHYXTYO24C2KSMB5W3Q" -> null
      - security_group_names                  = [] -> null
      - skip_final_snapshot                   = true -> null
      - status                                = "available" -> null
      - storage_encrypted                     = false -> null
      - storage_type                          = "gp2" -> null
      - tags                                  = {} -> null
      - username                              = "foo" -> null
      - vpc_security_group_ids                = [
          - "sg-022fa6d103ca16838",
        ] -> null
    }

  # aws_instance.backend will be destroyed
  - resource "aws_instance" "backend" {
      - ami                          = "ami-0ac80df6eff0e70b5" -> null
      - arn                          = "arn:aws:ec2:us-east-1:292166814392:instance/i-0a7471de2e4f4b7ac" -> null
      - associate_public_ip_address  = false -> null
      - availability_zone            = "us-east-1d" -> null
      - cpu_core_count               = 1 -> null
      - cpu_threads_per_core         = 1 -> null
      - disable_api_termination      = false -> null
      - ebs_optimized                = false -> null
      - get_password_data            = false -> null
      - hibernation                  = false -> null
      - id                           = "i-0a7471de2e4f4b7ac" -> null
      - instance_state               = "running" -> null
      - instance_type                = "t2.micro" -> null
      - ipv6_address_count           = 0 -> null
      - ipv6_addresses               = [] -> null
      - monitoring                   = false -> null
      - primary_network_interface_id = "eni-038e5c773aaf871f7" -> null
      - private_dns                  = "ip-10-0-1-92.ec2.internal" -> null
      - private_ip                   = "10.0.1.92" -> null
      - security_groups              = [] -> null
      - source_dest_check            = true -> null
      - subnet_id                    = "subnet-07e75ec5bd2f89076" -> null
      - tags                         = {
          - "Name" = "Backend"
        } -> null
      - tenancy                      = "default" -> null
      - volume_tags                  = {} -> null
      - vpc_security_group_ids       = [
          - "sg-0edf8381a017c82ce",
        ] -> null

      - credit_specification {
          - cpu_credits = "standard" -> null
        }

      - metadata_options {
          - http_endpoint               = "enabled" -> null
          - http_put_response_hop_limit = 1 -> null
          - http_tokens                 = "optional" -> null
        }

      - root_block_device {
          - delete_on_termination = true -> null
          - device_name           = "/dev/sda1" -> null
          - encrypted             = false -> null
          - iops                  = 100 -> null
          - volume_id             = "vol-0e1c9bf045a144e98" -> null
          - volume_size           = 8 -> null
          - volume_type           = "gp2" -> null
        }
    }

  # aws_instance.frontend will be destroyed
  - resource "aws_instance" "frontend" {
      - ami                          = "ami-0ac80df6eff0e70b5" -> null
      - arn                          = "arn:aws:ec2:us-east-1:292166814392:instance/i-0c2476d500414e610" -> null
      - associate_public_ip_address  = false -> null
      - availability_zone            = "us-east-1d" -> null
      - cpu_core_count               = 1 -> null
      - cpu_threads_per_core         = 1 -> null
      - disable_api_termination      = false -> null
      - ebs_optimized                = false -> null
      - get_password_data            = false -> null
      - hibernation                  = false -> null
      - id                           = "i-0c2476d500414e610" -> null
      - instance_state               = "running" -> null
      - instance_type                = "t2.micro" -> null
      - ipv6_address_count           = 0 -> null
      - ipv6_addresses               = [] -> null
      - monitoring                   = false -> null
      - primary_network_interface_id = "eni-0f67d6eefad2496ff" -> null
      - private_dns                  = "ip-10-0-1-207.ec2.internal" -> null
      - private_ip                   = "10.0.1.207" -> null
      - security_groups              = [] -> null
      - source_dest_check            = true -> null
      - subnet_id                    = "subnet-07e75ec5bd2f89076" -> null
      - tags                         = {
          - "Name" = "Frontend"
        } -> null
      - tenancy                      = "default" -> null
      - volume_tags                  = {} -> null
      - vpc_security_group_ids       = [
          - "sg-0edf8381a017c82ce",
        ] -> null

      - credit_specification {
          - cpu_credits = "standard" -> null
        }

      - metadata_options {
          - http_endpoint               = "enabled" -> null
          - http_put_response_hop_limit = 1 -> null
          - http_tokens                 = "optional" -> null
        }

      - root_block_device {
          - delete_on_termination = true -> null
          - device_name           = "/dev/sda1" -> null
          - encrypted             = false -> null
          - iops                  = 100 -> null
          - volume_id             = "vol-06c5f9a46c2a99807" -> null
          - volume_size           = 8 -> null
          - volume_type           = "gp2" -> null
        }
    }

  # aws_subnet.projectsubnet will be destroyed
  - resource "aws_subnet" "projectsubnet" {
      - arn                             = "arn:aws:ec2:us-east-1:292166814392:subnet/subnet-07e75ec5bd2f89076" -> null
      - assign_ipv6_address_on_creation = false -> null
      - availability_zone               = "us-east-1d" -> null
      - availability_zone_id            = "use1-az4" -> null
      - cidr_block                      = "10.0.1.0/24" -> null
      - id                              = "subnet-07e75ec5bd2f89076" -> null
      - map_public_ip_on_launch         = false -> null
      - owner_id                        = "292166814392" -> null
      - tags                            = {
          - "Name" = "projectsubnet"
        } -> null
      - vpc_id                          = "vpc-0fc5503590456595b" -> null
    }

  # aws_vpc.projectvpc will be destroyed
  - resource "aws_vpc" "projectvpc" {
      - arn                              = "arn:aws:ec2:us-east-1:292166814392:vpc/vpc-0fc5503590456595b" -> null
      - assign_generated_ipv6_cidr_block = false -> null
      - cidr_block                       = "10.0.0.0/16" -> null
      - default_network_acl_id           = "acl-0e48e1bcf97f3f75e" -> null
      - default_route_table_id           = "rtb-02300cd1856fb7f00" -> null
      - default_security_group_id        = "sg-0edf8381a017c82ce" -> null
      - dhcp_options_id                  = "dopt-0bd7c0dac1cab69a3" -> null
      - enable_classiclink               = false -> null
      - enable_classiclink_dns_support   = false -> null
      - enable_dns_hostnames             = false -> null
      - enable_dns_support               = true -> null
      - id                               = "vpc-0fc5503590456595b" -> null
      - instance_tenancy                 = "default" -> null
      - main_route_table_id              = "rtb-02300cd1856fb7f00" -> null
      - owner_id                         = "292166814392" -> null
      - tags                             = {
          - "Name" = "projectvpc"
        } -> null
    }

Plan: 0 to add, 0 to change, 5 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_db_instance.projectdb: Destroying... [id=terraform-20200715092226965600000001]
aws_instance.backend: Destroying... [id=i-0a7471de2e4f4b7ac]
aws_instance.frontend: Destroying... [id=i-0c2476d500414e610]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 10s elapsed]
aws_instance.frontend: Still destroying... [id=i-0c2476d500414e610, 10s elapsed]
aws_instance.backend: Still destroying... [id=i-0a7471de2e4f4b7ac, 10s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 20s elapsed]
aws_instance.frontend: Still destroying... [id=i-0c2476d500414e610, 20s elapsed]
aws_instance.backend: Still destroying... [id=i-0a7471de2e4f4b7ac, 20s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 30s elapsed]
aws_instance.backend: Still destroying... [id=i-0a7471de2e4f4b7ac, 30s elapsed]
aws_instance.frontend: Still destroying... [id=i-0c2476d500414e610, 30s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 40s elapsed]
aws_instance.frontend: Still destroying... [id=i-0c2476d500414e610, 40s elapsed]
aws_instance.backend: Still destroying... [id=i-0a7471de2e4f4b7ac, 40s elapsed]
aws_instance.backend: Destruction complete after 42s
aws_instance.frontend: Destruction complete after 42s
aws_subnet.projectsubnet: Destroying... [id=subnet-07e75ec5bd2f89076]
aws_subnet.projectsubnet: Destruction complete after 2s
aws_vpc.projectvpc: Destroying... [id=vpc-0fc5503590456595b]
aws_vpc.projectvpc: Destruction complete after 1s
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 50s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 1m0s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 1m10s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 1m20s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 1m30s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 1m40s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 1m50s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 2m0s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 2m10s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 2m20s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 2m30s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 2m40s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 2m50s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 3m0s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 3m10s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 3m20s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 3m30s elapsed]
aws_db_instance.projectdb: Still destroying... [id=terraform-20200715092226965600000001, 3m40s elapsed]
aws_db_instance.projectdb: Destruction complete after 3m43s

Destroy complete! Resources: 5 destroyed.

La infraestructura fue destruida correctamente y el ambiente de desarrollo eliminado, en este momento no hay costes de infraestructura y somos capaces de levantarla de nuevo solamente ejecutando terraform apply.

Casos de Éxito

Por si aún no te has decidido a utilizar Terraform como herramienta para la Infraestructura como Código, te dejamos algunos casos de éxito reales. En breve te mostraremos nuestro caso de éxito con Terraform.

Ahorrando cientos de horas de ingeniería, el caso Mozilla.

Mozilla Corporation, empresa perteneciente a la fundación Mozilla y responsable, entre otras aplicaciones, del navegador web Mozilla Firefox y el cliente de correo Mozillla Thunderbird, se creó el 3 de agosto de 2005 sin ánimo de lucro con un modelo de negocio de código abierto.

El equipo de desarrollo comenzó con su propia solución de infraestructura para Continuous Integration pero muy pronto se dieron cuenta de que tenía grandes limitaciones además de implicar riesgo y demasiadas horas de trabajo manual por parte de sus ingenieros de plataforma.

Por tanto, decidieron implementar una solución con Terraform utilizando adicionalmente Atlas para resolver los problemas de state file y evitar inconsistencias.

Actualmente su modelo de trabajo está totalmente adaptado a las mejores prácticas tanto en el lado de operaciones como en el de desarrollo, llevando a cabo peer reviews y versionado de toda su infraestructura.

Resultados y KPIs obtenidos:

Reducción del 99% de operaciones manuales, ahorro de 500 horas de ingeniería, reducción del “tiempo de setup” a un solo día.

Menta Network y el escalado para “el buen fin”

Menta Network es una compañía de monitorización y calidad de aplicaciones con sede en la ciudad de Mexico. En este contexto es habitual la necesidad de provisionar, escalar y destruir entornos cloud para testing E2E y pruebas de carga.

En Mexico existe “El buen fin” que es algo así como un “Black Friday” local, con unas ventas e-commerce de alrededor de 22,6 Billones de dólares.

El reto era poder simular ese comportamiento con valles y grandes picos, para lo que combinaron Pulumi (lo que les permitía desarrollar IaC usando los lenguajes con los que eran familiares) y AWS, utilizando us pipelines CI/CD con GitLab.

Resultados y KPIs:

Comparado con su anterior plataforma de testing obtuvieron una reducción de coste del 60% y redujeron los tiempos de entrega sensiblemente.

Otros casos de éxito

En el bloque de “Referencias de casos de éxito” hemos recopilado una serie de casos de éxito y study cases relacionados con soluciones de infrastructure as code por si el lector desea bucear más sobre el reflejo en el mundo real de estas tecnologías.
  • Tata: Caso de Estudio con Terraform, el cual podéis consultar aquí.
  • DBI: Caso de Estudio con Terraform, el cual podéis consultar aquí.
  • Tabulate: Su caso de éxito.
  • Cloudkinesys: Migración de cloud pública a privada.

Suscríbete

¿Te gustarían más artículos como esta que nos ha traído nuestro compañero? Rellena este formulario y suscríbete a nuestra newsletter.