Infrastructure as Code with Terraform

Our colleague João Ferreira, through his experience as Site Reliability Engineer at dareCode, shares with us a great article in which he will introduce us to the Infrastructure as code using the open source software 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.

Table of Contents

In this article you will find:

What is Infrastructure as Code?

Infrastructure as Code is a practice used in automatic infrastructure deployment using scripts or declarative tools.

The idea of this concept is to use processes and tools typically associated with software development in infrastructure, which includes version control, testing, etc.

This practice allows the automation of recurrent tasks at the infrastructure level such as provisioning or configuration and, as the process is automatic, it can be applied to one or thousands of machines.

In this way we can decrease the risk and increase the speed of infrastructure deployment.

Infrastructure as Code can be used to provision and configure all components associated with physical servers (bare metal) as well as virtual machines.

Some examples of IaC tools are: Terraform, AWS CloudFormation or Azure Resource Manager, among others.

Introduction to Terraform

Terraform is a tool created by HashiCorp for the creation and management of infrastructure.

This tool allows to build, change and version infrastructure in a safe and efficient way.

Terraform was built with a modular structure and, with the use of providers allows to manage infrastructure both in the various cloud services as well as in in-house solutions.

Providers are responsible for the interaction between the tool and the APIs of services such as AWS, Azure or OpenStack.

Using Terraform code you can handle low-level components such as VMs, storage and networking but also high-level components such as DNS, users, etc.

The typical workflow with this tool is the creation of “new” infrastructure using the resources defined in the code, but it is also possible to define resources and associate them with existing infrastructure.

If you need help or want more information do not hesitate to contact us.

Advantages of using Terraform

Some of the advantages of using Terraform are:
  1. It enables the deployment and destruction of infrastructure in a safe and efficient manner: It is very useful in ephemeral or test environments and also makes it easier for us to create self-service type automations.
  2. All the infrastructure defined in one place.
  3. It allows the scaling of the infrastructure also in a safe and efficient way.
  4. Possibility of applying version control to the infrastructure.
  5. Cloud agnostic.

Many of the cloud services use the ‘pay as you go’ model and Terraform allows costs to be reduced as it enables the deployment and destruction of the infrastructure in a secure manner.

It also allows the deployment of test environments that are only needed for a few exercises of a certain duration and can then be destroyed, resulting in a minimum cost for the execution of the tests.

How to start using Terraform

Here’s a practical explanation: How to initialize Terraform and how to create the development environment.

How to initialize Terraform

The workflow normally used in a Terraform project is as follows:
  1. Init: Project initialization.
  2. Plan: Planning (dry-run) of the resources defined in the code.
  3. Apply: Deployment of the resources defined in the code.
  4. Destroy: Destruction of the resources defined in the code (if necessary)

A Terraform project needs to be initialized in your working directory, where the configuration files are. This process tries to start:

  1. Backend: Service used to store status files.
  2. Installation of child modules.
  3. Installation of plugins and providers.

Example of initialization of a project, using the AWS provider:

  1. Create project folder
  2. Create the main.tf file with the contents:

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

3. Run terraform init.

We now have the project ready to deploy infrastructure in AWS and we can start defining the resources that need to be created.

How to create the development environment.

As an example of the creation of a development environment we will use the project created in the previous example, which is already prepared to deploy resources in AWS.

The development environment is made by:

  • A SVC.
  • A Subnet.
  • Two instances EC2.
  • One RDS database.

For the creation of the VPC (Virtual Private Cloud) we will use the resource aws_vpc.

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

To create the Subnet we’ll use the aws_subnet resource.

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

  tags = {
    Name = "projectsubnet"
  }

}

Note: Information from other resources can be used, in this case the value of ‘vpc_id’ is extracted from the resource ‘projectvpc’.

To create the EC2 instances we will use the aws_instance resource.

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’ assigns the instances to the subnet we previously created and which is associated with the VPC we created.

Finally, we have the creation of the database in RDS, for that we are going to use the resource 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
} 

Now that we have the resources defined in main.tf, we continue the workflow running:

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.

If the command is executed correctly, we can move on to the deployment of the infrastructure.

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.

Following this workflow, we created all the necessary resources for the development environment and our developers can now use this environment for their work.

After the tests or even the working day is over, Terraform allows for the destruction of the infrastructure and its rebuilding when necessary.

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.

The infrastructure was properly destroyed and the development environment eliminated, at this moment there are no infrastructure costs and we are able to raise it again only by running terraform apply.

Successful Cases with Terraform

In case you haven’t decided yet to use Terraform as a tool for Infrastructure as Code, here are some real success stories. Soon we will show you our success story with Terraform.

Saving hundreds of hours of engineering, the Mozilla case.

Mozilla Corporation, a company belonging to the Mozilla Foundation and responsible for, among other applications, the Mozilla Firefox web browser and the Mozillla Thunderbird email client, was created on August 3, 2005 as a non-profit organization with an open source business model.

The development team started with their own infrastructure solution for Continuous Integration but soon realized that it had major limitations as well as involving risk and too many hours of manual work by their platform engineers.

Therefore, they decided to implement a solution with Terraform using additionally Atlas to solve the state file problems and avoid inconsistencies.

Currently its working model is fully adapted to best practices in both the operations and development sides, carrying out peer reviews and versioning of all its infrastructure.

Results and KPIs obtained:

99% reduction in manual operations, savings of 500 engineering hours, reduction of ‘setup time’ to a single day

Menta Network and climbing for 'the good end'

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.

In Mexico there is ‘El buen fin’ which is something like a local ‘Black Friday’, with e-commerce sales of around 22.6 billion dollars.

The challenge was to be able to simulate that behavior with valleys and large peaks, so they combined Pulumi (which allowed them to develop IaC using the languages they were familiar with) and AWS, using our CI/CD pipelines with GitLab.

Results and KPIs:

Compared to their previous testing platform they obtained a 60% cost reduction and reduced delivery times significantly.

Other success stories

We have compiled a series of success stories and study cases related to infrastructure as code solutions in case the reader wants to dive deeper into the real world reflection of these technologies.
  • Tata:Case Study with Terraform, which you can consult here.
  • DBI: Case Study with Terraform, which you can consult here.
  • Tabulate: Their success story.
  • Cloudkinesys: Migration from public to private cloud.

Subscribe

Would you like more articles like this that our colleague has brought us? Fill out this form and subscribe to our newsletter.