/ DevOps

Deploy an EC2 Instance with Terraform

Today we will test out Terraform and do a Basic Deployment by Launching an EC2 Instance on Amazon Web Services.

What is Terraform:

As described from their website:

"Terraform enables you to safely and predictably create, change, and improve production infrastructure. It is an open source tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned."

Download Terraform Binary:

Find the latest version here: https://www.terraform.io/downloads.html

$ wget -O terraform_0.9.9_linux_amd64.zip https://releases.hashicorp.com/terraform/0.9.9/terraform_0.9.9_linux_amd64.zip?_ga=2.180616622.843755498.1498543547-632150628.1498543547
$ unzip terraform_0.9.9_linux_amd64.zip
$ mv terraform /usr/local/bin/
$ terraform --version
Terraform v0.9.9
Create a Terraform Configuration:

In my basic example, I will be launching an t2.micro EC2 instance in my Default VPC in Dublin.

/opt/disk/terraform/terraform-ec2.tf
provider "aws" {
  region = "eu-west-1"
  alias = "dublin"
}

resource "aws_instance" "debug_instance" {

  provider = "aws.dublin"
  ami = "ami-d7b9a2b1"
  instance_type = "t2.micro"
  key_name = "MyKey"
  vpc_security_group_ids = ["sg-1234abcde"]
  subnet_id = "subnet-1234abcde"

  tags {
    Name = "Terraform Test with EC2"
  }
}

Execution Plan: Determine the outcome of your Deployment:

If you are in the same directory you do not have to pass the path to the *.tf file, but since I am in my home directory, I will pass the path to my terraform-ec2.tf file

$ terraform plan /opt/disk/terraform/

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.

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_instance.debug_instance
    ami:                               "ami-d7b9a2b1"
    associate_public_ip_address:       "<computed>"
    availability_zone:                 "<computed>"
    ebs_block_device.#:                "<computed>"
    ephemeral_block_device.#:          "<computed>"
    instance_state:                    "<computed>"
    instance_type:                     "t2.micro"
    ipv6_address_count:                "<computed>"
    ipv6_addresses.#:                  "<computed>"
    key_name:                          "RuanBekker-EU"
    network_interface.#:               "<computed>"
    network_interface_id:              "<computed>"
    placement_group:                   "<computed>"
    primary_network_interface_id:      "<computed>"
    private_dns:                       "<computed>"
    private_ip:                        "<computed>"
    public_dns:                        "<computed>"
    public_ip:                         "<computed>"
    root_block_device.#:               "<computed>"
    security_groups.#:                 "<computed>"
    source_dest_check:                 "true"
    subnet_id:                         "subnet-abcd1234"
    tags.%:                            "1"
    tags.Name:                         "Terraform Test with EC2"
    tenancy:                           "<computed>"
    volume_tags.%:                     "<computed>"
    vpc_security_group_ids.#:          "1"
    vpc_security_group_ids.4159630609: "sg-7e210318"


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

Deploy: Apply your Deployment:

$ terraform apply /opt/disk/terraform/

aws_instance.debug_instance: Creating...
  ami:                               "" => "ami-d7b9a2b1"
  associate_public_ip_address:       "" => "<computed>"
  availability_zone:                 "" => "<computed>"
  ebs_block_device.#:                "" => "<computed>"
  ephemeral_block_device.#:          "" => "<computed>"
  instance_state:                    "" => "<computed>"
  instance_type:                     "" => "t2.micro"
  ipv6_address_count:                "" => "<computed>"
  ipv6_addresses.#:                  "" => "<computed>"
  key_name:                          "" => "MyKey"
  network_interface.#:               "" => "<computed>"
  network_interface_id:              "" => "<computed>"
  placement_group:                   "" => "<computed>"
  primary_network_interface_id:      "" => "<computed>"
  private_dns:                       "" => "<computed>"
  private_ip:                        "" => "<computed>"
  public_dns:                        "" => "<computed>"
  public_ip:                         "" => "<computed>"
  root_block_device.#:               "" => "<computed>"
  security_groups.#:                 "" => "<computed>"
  source_dest_check:                 "" => "true"
  subnet_id:                         "" => "subnet-1234abcd"
  tags.%:                            "" => "1"
  tags.Name:                         "" => "Terraform Test with EC2"
  tenancy:                           "" => "<computed>"
  volume_tags.%:                     "" => "<computed>"
  vpc_security_group_ids.#:          "" => "1"
  vpc_security_group_ids.4159630609: "" => "sg-1234abcd"
aws_instance.debug_instance: Still creating... (10s elapsed)
aws_instance.debug_instance: Still creating... (20s elapsed)
aws_instance.debug_instance: Creation complete (ID: i-0039436a761dbb650)

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

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path:

From our working directory we will have a state file:

$ ls
terraform.tfstate

The data will be stored as json, lets read the data from the state file:

$ cat terraform.tfstate
{
    "version": 3,
    "terraform_version": "0.9.9",
    "serial": 0,
    "lineage": "f410e287-50e1-4e56-b929-c2d000f0a08b",
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
                "aws_instance.debug_instance": {
                    "type": "aws_instance",
                    "depends_on": [],
                    "primary": {
                        "id": "i-0039436a761dbb650",
                        "attributes": {
                            "ami": "ami-d7b9a2b1",
                            "associate_public_ip_address": "true",
                            "availability_zone": "eu-west-1c",
                            "disable_api_termination": "false",
                            "ebs_block_device.#": "0",
                            "ebs_optimized": "false",
                            "ephemeral_block_device.#": "0",
                            "iam_instance_profile": "",
                            "id": "i-0039436a761dbb650",
                            "instance_state": "running",
                            "instance_type": "t2.micro",
                            "ipv6_addresses.#": "0",
                            "key_name": "MyKey",
                            "monitoring": "false",
                            "network_interface.#": "0",
                            "network_interface_id": "eni-1234abcd",
                            "primary_network_interface_id": "eni-1234abcd",
                            "private_dns": "ip-172-31-26-80.eu-west-1.compute.internal",
                            "private_ip": "172.31.26.80",
                            "public_dns": "ec2-34-253-6-43.eu-west-1.compute.amazonaws.com",
                            "public_ip": "34.253.6.43",
                            "root_block_device.#": "1",
                            "root_block_device.0.delete_on_termination": "true",
                            "root_block_device.0.iops": "100",
                            "root_block_device.0.volume_size": "8",
                            "root_block_device.0.volume_type": "gp2",
                            "security_groups.#": "0",
                            "source_dest_check": "true",
                            "subnet_id": "subnet-1234abcd",
                            "tags.%": "1",
                            "tags.Name": "Terraform Test with EC2",
                            "tenancy": "default",
                            "volume_tags.%": "0",
                            "vpc_security_group_ids.#": "1",
                            "vpc_security_group_ids.4159630609": "sg-1234abcd"
                        },
                        "meta": {
                            "e2bfb730-ecaa-11e6-8f88-34363bc7c4c0": {
                                "create": 600000000000,
                                "delete": 600000000000,
                                "update": 600000000000
                            },
                            "schema_version": "1"
                        },
                        "tainted": false
                    },
                    "deposed": [],
                    "provider": "aws.dublin"
                }
            },
            "depends_on": []
        }
    ]
}

Show: Using show to parse the state file to human readable output:

$ terraform show
aws_instance.debug_instance:
  id = i-0039436a761dbb650
  ami = ami-d7b9a2b1
  associate_public_ip_address = true
  availability_zone = eu-west-1c
  disable_api_termination = false
  ebs_block_device.# = 0
  ebs_optimized = false
  ephemeral_block_device.# = 0
  iam_instance_profile =
  instance_state = running
  instance_type = t2.micro
  ipv6_addresses.# = 0
  key_name = MyKey
  monitoring = false
  network_interface.# = 0
  network_interface_id = eni-1234abcd
  primary_network_interface_id = eni-1234abcd
  private_dns = ip-172-31-26-80.eu-west-1.compute.internal
  private_ip = 172.31.26.80
  public_dns = ec2-34-253-6-43.eu-west-1.compute.amazonaws.com
  public_ip = 34.253.6.43
  root_block_device.# = 1
  root_block_device.0.delete_on_termination = true
  root_block_device.0.iops = 100
  root_block_device.0.volume_size = 8
  root_block_device.0.volume_type = gp2
  security_groups.# = 0
  source_dest_check = true
  subnet_id = subnet-8077d8e5
  tags.% = 1
  tags.Name = Terraform Test with EC2
  tenancy = default
  volume_tags.% = 0
  vpc_security_group_ids.# = 1
  vpc_security_group_ids.4159630609 = sg-1234abcd

Planning on Terminating the Deployment:

To check what will be terminated, we can use the -destroy argument on the plan command:

$ terraform plan -destroy /opt/disk/terraform/

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.

aws_instance.debug_instance: Refreshing state... (ID: i-0039436a761dbb650)
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

- aws_instance.debug_instance


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

Terminating the Deployment:

You can use the -force argument, no confirmations will be shown.

$ terraform destroy /opt/disk/terraform/
Do you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_instance.debug_instance: Refreshing state... (ID: i-0039436a761dbb650)
aws_instance.debug_instance: Destroying... (ID: i-0039436a761dbb650)
aws_instance.debug_instance: Still destroying... (ID: i-0039436a761dbb650, 10s elapsed)
aws_instance.debug_instance: Still destroying... (ID: i-0039436a761dbb650, 20s elapsed)
aws_instance.debug_instance: Still destroying... (ID: i-0039436a761dbb650, 30s elapsed)
aws_instance.debug_instance: Still destroying... (ID: i-0039436a761dbb650, 40s elapsed)
aws_instance.debug_instance: Still destroying... (ID: i-0039436a761dbb650, 50s elapsed)
aws_instance.debug_instance: Still destroying... (ID: i-0039436a761dbb650, 1m0s elapsed)
aws_instance.debug_instance: Still destroying... (ID: i-0039436a761dbb650, 1m10s elapsed)
aws_instance.debug_instance: Destruction complete

Destroy complete! Resources: 1 destroyed.

Resources: