Multi Cloud Network Architecture

Building Networking Skills in the age of the Cloud

Multi Cloud Network Architecture

Terraform for Infrastructure Architects – Part 2

Share on linkedin
LinkedIn
Share on twitter
Twitter
Share on email
Email

Context

In the second part of our series, I would like to take the time to consolidate some of the things we learned in Part 1 and use that simple example to examine some of Terraforms fundamentals.

The learning objectives are:

  • How does Terraform maintain state?
  • How to get information out of Terraform concerning your environment?

Terraform State

Terraform doesn’t just build an environment and forget about it. When you define your code, there are a lot of unknowns about the infrastructure that you will create. These unknowns are only knowable as the infrastructure is instantiated.

Terraform manages this situation using “state” files. A state file is simply a JSON file that records the current state of your deployed environment allowing Terraform to safely update/modify/destroy only the scoped resources in your code or instruction.

Let’s have a look at my local directory for our previous example BEFORE I run terraform apply:

Now let’s have a look AFTER a terraform apply:

You can see that Terraform has created a new file called terraform.tfstate.

Let’s have a look at what’s going on in this file:


{
  "version": 4,
  "terraform_version": "1.0.3",
  "serial": 1,
  "lineage": "8a84ca1b-3086-aa46-992e-204746b5a311",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_instance",
      "name": "mcna_ec2",
      "provider": "provider["registry.terraform.io/hashicorp/aws"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "ami": "ami-00f22f6155d6d92c5",
            "arn": "arn:aws:ec2:eu-central-1:823499938473:instance/i-09d490123b859aabe",
            "associate_public_ip_address": true,
            "availability_zone": "eu-central-1b",
            "capacity_reservation_specification": [
              {
                "capacity_reservation_preference": "open",
                "capacity_reservation_target": []
              }
            ],
            "cpu_core_count": 1,
            "cpu_threads_per_core": 1,
            "credit_specification": [
              {
                "cpu_credits": "standard"
              }
            ],
            "disable_api_termination": false,
            "ebs_block_device": [],
            "ebs_optimized": false,
            "enclave_options": [
              {
                "enabled": false
              }
            ],
            "ephemeral_block_device": [],
            "get_password_data": false,
            "hibernation": false,
            "host_id": null,
            "iam_instance_profile": "",
            "id": "i-09d490123b859aabe",
            "instance_initiated_shutdown_behavior": "stop",
            "instance_state": "running",
            "instance_type": "t2.micro",
            "ipv6_address_count": 0,
            "ipv6_addresses": [],
            "key_name": "",
            "launch_template": [],
            "metadata_options": [
              {
                "http_endpoint": "enabled",
                "http_put_response_hop_limit": 1,
                "http_tokens": "optional"
              }
            ],
            "monitoring": false,
            "network_interface": [],
            "outpost_arn": "",
            "password_data": "",
            "placement_group": "",
            "primary_network_interface_id": "eni-0b3d71590cb076845",
            "private_dns": "ip-172-31-35-220.eu-central-1.compute.internal",
            "private_ip": "172.31.35.220",
            "public_dns": "ec2-18-185-92-249.eu-central-1.compute.amazonaws.com",
            "public_ip": "18.185.92.249",
            "root_block_device": [
              {
                "delete_on_termination": true,
                "device_name": "/dev/xvda",
                "encrypted": false,
                "iops": 100,
                "kms_key_id": "",
                "tags": {},
                "throughput": 0,
                "volume_id": "vol-0178128a9f8806c83",
                "volume_size": 8,
                "volume_type": "gp2"
              }
            ],
            "secondary_private_ips": [],
            "security_groups": [
              "default"
            ],
            "source_dest_check": true,
            "subnet_id": "subnet-01b6017d",
            "tags": null,
            "tags_all": {},
            "tenancy": "default",
            "timeouts": null,
            "user_data": null,
            "user_data_base64": null,
            "volume_tags": null,
            "vpc_security_group_ids": [
              "sg-a73b80d4"
            ]
          },
          "sensitive_attributes": [],
          "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6MTIwMDAwMDAwMDAwMCwidXBkYXRlIjo2MDAwMDAwMDAwMDB9LCJzY2hlbWFfdmVyc2lvbiI6IjEifQ=="
        }
      ]
    }
  ]
}



Never edit this file. Terraform is in charge of maintaining it.


As you can see, Terraform learns and records the real resource information after instantiation. This is how Terraform knows which resource(s) to delete specifically when you run a terraform destroy

.

Let’s take a concrete example. Before instantiating the EC2 instance, we did not know what its public DNS name would be. However, Terraform learned this and stored it in the state file:

"public_dns": "ec2-18-185-92-249.eu-central-1.compute.amazonaws.com"

 

Terraform Output

We can of course create the instance and then go to the AWS console and check for this information. However, since Terraform finds it out, he could just tell us. We’ll have a look at that below.

Let’s build on the example from last time. Let’s keep the code the same, but we’ll add an additional command:


# Terraform for Infrastructure Architects - Part 1

#define the terraform providers

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}


#define the AWS region

provider "aws" {
    region = "eu-central-1"
}


#define AWS resources


resource "aws_instance" "mcna_ec2" {
    ami = "ami-00f22f6155d6d92c5"
    instance_type = "t2.micro"

}

Let’s add the code to present us the output:


output "publicdns" {
    value = aws_instance.mcna_ec2.public_dns
}

Now let’s see what terraform apply gives us:


pauly@Pauls-iMac blog_2 % terraform apply

Terraform used the selected providers to generate the following execution plan. 
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.mcna_ec2 will be created
  + resource "aws_instance" "mcna_ec2" {
      + ami                                  = "ami-00f22f6155d6d92c5"
      + 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)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (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)
      + monitoring                           = (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)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = (known after apply)
      + tags_all                             = (known after apply)
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + vpc_security_group_ids               = (known after apply)

      + capacity_reservation_specification {
          + capacity_reservation_preference = (known after apply)

          + capacity_reservation_target {
              + capacity_reservation_id = (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)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + enclave_options {
          + enabled = (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)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

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

Changes to Outputs:
  + publicdns = (known after apply)

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_instance.mcna_ec2: Creating... 
aws_instance.mcna_ec2: Still creating... [10s elapsed]
aws_instance.mcna_ec2: Still creating... [20s elapsed]
aws_instance.mcna_ec2: Creation complete after 22s [id=i-0ccdbe1f2aa119285]

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

Outputs:

publicdns = "ec2-52-28-71-121.eu-central-1.compute.amazonaws.com"
pauly@Pauls-iMac blog_2 %



Checkpoint

By this stage you should be aware of:

  • the notion of state: why and where Terraform maintains this state
  • how to get output from your Terraform code

Understanding the basics of how and why Terraform maintains state is critical to building complex infrastructure and collaborating across teams. As you have seen, the state file is currently local on your machine, which would make cross-team collaboration and testing tricky. We’ll look later at how to manage state for collaboration.

Likewise, getting output out of your code is crucial to your workflow. Imagine after instantiating the EC2 instance you want to connect to it in order to test, but you need to go to the AWS console to get the DNS name. Not practical.

Next Steps

In the next session, we will build a web server on an EC2 instance and create a Security Group to protect it. We’ll also look at some additional Terraform commands which will be useful as we move on to more complex projects.

Share if you liked it
Share on linkedin
LinkedIn
Share on twitter
Twitter
Share on email
Email
0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

20 year veteran of the networking industry currently specialising in Cloud Networking and Security.

CCIE #16661 (R&S, SP)

Disclaimer

I am currently an employee of Aviatrix. All opinions, views and statements are my own and do not reflect that of my employer. Any errors are mine and mine alone. Any ignorance is mine, though I do believe my parents and the public school system should shoulder some of that blame. 

Recent Posts

Archives