Codementor Events

Terraform Part II

Published Feb 03, 2020

Deploying Multiple different instances and attaching an elasticIP
Let's create another instance in a different availability zone, and attach an elastic IP to both instances. We will be using the syntax is count.FIELD. For example, ${count.index} will interpolate the current index in a multi-count resource, but before doing that we need to set how many instances we need by using count to specify the number of resources we will be creating, then we set up our availability zone, we can either create a variables.tf or append it into the resources.tf file
Update resources.tf to contain the following code if you want to use your custom variable.

variable "avaialability_zones" {
  description = "Run the EC2 Instances in these Availability Zones"
  type = "list"
  default = ["us-east-1c", "us-east-1b", "us-east-1a"]
}

or make Terraform retrieve all the availability zone from cloud provider(we are using aws) by using datasource(Data sources allow data to be fetched from the provider, or computed for use elsewhere in Terraform configuration). It doesn't create new resources but instead retrieves dynamic data from your cloud provider.
Update resources.tf to contain the following code if you will be using datasource to pull all the availability zones in your region.
data "aws_availability_zones" "all" {}
Since we choose to use our custom availability zone. resources.tffile will look like the below.

resource "aws_instance" "server" {
  count = 2
  ami = "ami-2d39803a"
  instance_type = "t2.micro"
  availability_zone = "${element(var.avaialability_zones, count.index)}"
  tags {
      Name = "server-${count.index}"
      Environment = "Production ${count.index}"
      App = "ecommerce"
  }
}

Explaining the above in pseudo-code.

##This is just pseudo code. It won't actually work in Terraform. 
# Create two ec2 instances
count = 2
for i = 0; i < count; i++ {
  resource "aws_instance" "server" {
    ami = "ami-2d39803a"
    instance_type = "t2.micro" 
    ## set them into different availability zone
    availability_zone = "${avaialability_zones[i]}" 
    ## number each instances created
    tags {
      Name = "server-${i}"
      Environment = "Production ${i}"
      App = "ecommerce"
    }
  }  
}

Test

$ terraform plan
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.server[0]: Refreshing state... (ID: i-052a34a0680983ae0)
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
  ~ update in-place
Terraform will perform the following actions:
~ aws_instance.server[0]
      tags.Environment:             "Production" => "Production 0"
      tags.Name:                    "server-one" => "server-0"
+ aws_instance.server[1]
      id:                           <computed>
      ami:                          "ami-2d39803a"
      arn:                          <computed>
      associate_public_ip_address:  <computed>
      availability_zone:            "us-east-1b"
      cpu_core_count:               <computed>
      cpu_threads_per_core:         <computed>
      ebs_block_device.#:           <computed>
      ephemeral_block_device.#:     <computed>
      get_password_data:            "false"
      host_id:                      <computed>
      instance_state:               <computed>
      instance_type:                "t2.micro"
      ipv6_address_count:           <computed>
      ipv6_addresses.#:             <computed>
      key_name:                     <computed>
      network_interface.#:          <computed>
      network_interface_id:         <computed>
      password_data:                <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:                    <computed>
      tags.%:                       "3"
      tags.App:                     "ecommerce"
      tags.Environment:             "Production 1"
      tags.Name:                    "server-1"
      tenancy:                      <computed>
      volume_tags.%:                <computed>
      vpc_security_group_ids.#:     <computed>
Plan: 1 to add, 1 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

We can see that the initial ec2 instance we spun up ~ aws_instance.server[0] will be modified, and another instance + aws_instance.server[1] will be added with a different Tag Name, environment name, and both in a different availability zone.
Let apply the change:

$ terraform apply -auto-approve
aws_instance.server[0]: Refreshing state... (ID: i-052a34a0680983ae0)
aws_instance.server[0]: Modifying... (ID: i-052a34a0680983ae0)
  tags.Environment: "Production" => "Production 0"
  tags.Name:        "server-one" => "server-0"
aws_instance.server[1]: Creating...
  ami:                          "" => "ami-2d39803a"
  arn:                          "" => "<computed>"
  associate_public_ip_address:  "" => "<computed>"
  availability_zone:            "" => "us-east-1b"
  cpu_core_count:               "" => "<computed>"
  cpu_threads_per_core:         "" => "<computed>"
  ebs_block_device.#:           "" => "<computed>"
  ephemeral_block_device.#:     "" => "<computed>"
  get_password_data:            "" => "false"
  host_id:                      "" => "<computed>"
  instance_state:               "" => "<computed>"
  instance_type:                "" => "t2.micro"
  ipv6_address_count:           "" => "<computed>"
  ipv6_addresses.#:             "" => "<computed>"
  key_name:                     "" => "<computed>"
  network_interface.#:          "" => "<computed>"
  network_interface_id:         "" => "<computed>"
  password_data:                "" => "<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:                    "" => "<computed>"
  tags.%:                       "" => "3"
  tags.App:                     "" => "ecommerce"
  tags.Environment:             "" => "Production 1"
  tags.Name:                    "" => "server-1"
  tenancy:                      "" => "<computed>"
  volume_tags.%:                "" => "<computed>"
  vpc_security_group_ids.#:     "" => "<computed>"
aws_instance.server[0]: Modifications complete after 4s (ID: i-052a34a0680983ae0)
aws_instance.server.1: Still creating... (10s elapsed)
aws_instance.server.1: Still creating... (20s elapsed)
aws_instance.server.1: Still creating... (30s elapsed)
aws_instance.server[1]: Creation complete after 36s (ID: i-04b7b0760c2ca8bee)
Apply complete! Resources: 1 added, 1 changed, 0 destroyed

Terraform refresh the state of the resources it manages before starting the deployment and applies the changes appropriately in 36s

Adding Elastic IP

Add elastic IP to all the created instance. Since we only created two instances, we will be adding an elastic IP(EIP) address to the created instances. To avoid repetition, we will be using a variable counter and interpolating it into all the resources that need the elastic ip.
Create a variable counter either in a variable.tf file or append the resources.tf

variable "counter" {
  description = "Specify the number of resources"
  default = 2
}

Create a resource for the eip and parsing the counter variable. Append your resources.tf.

### This is just pseudo code. It won't actually work in Terraform.
for i = 0; i < 2; i++ {
  resource "aws_eip" "server" {
    instance = "${aws_instance.server.[i].id}"
  }
}
### This is a real code. It actually work in Terraform.
resource "aws_eip" "server" {
  count = "${var.counter}"
  instance = "${element(aws_instance.server.*.id, count.index)}"
}

Notice, we used aws_instance.server.*.id to loop through the instance id. From this example, that is the same as aws_instance.server.0.id which will have an instance id of i-052a34a0680983a30 and aws_instance.server.1.id which will have an instance id of i-04b7b0760c2ca8bee
Apply terraform plan to see what will be created:

$ terraform plan
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.server[0]: Refreshing state... (ID: i-052a34a0680983ae0)
aws_instance.server[1]: Refreshing state... (ID: i-04b7b0760c2ca8bee)
------------------------------------------------------------------------
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_eip.server[0]
      id:                <computed>
      allocation_id:     <computed>
      association_id:    <computed>
      domain:            <computed>
      instance:          "i-052a34a0680983ae0"
      network_interface: <computed>
      private_ip:        <computed>
      public_ip:         <computed>
      public_ipv4_pool:  <computed>
      vpc:               <computed>
+ aws_eip.server[1]
      id:                <computed>
      allocation_id:     <computed>
      association_id:    <computed>
      domain:            <computed>
      instance:          "i-04b7b0760c2ca8bee"
      network_interface: <computed>
      private_ip:        <computed>
      public_ip:         <computed>
      public_ipv4_pool:  <computed>
      vpc:               <computed>
Plan: 2 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

The apply output is too verbose, let limit the output to only print out the public IP address. To do this, create output.tf file by including the below code:

## Limit the output to print out only the 
output "IP Address: " {
  value = ["${aws_instance.server.*.public_ip}"]
}
## pseudo code - DO NOT INCLUDE THE BELOW IN YOUR OUTPUT.TF
count = n
for i=0; i<count; i++{
     print ${aws_instance.server.[i].public_ip}
}

Now let's apply

$ terraform apply -auto-approve
aws_instance.server[0]: Refreshing state... (ID: i-052a34a0680983ae0)
aws_instance.server[1]: Refreshing state... (ID: i-04b7b0760c2ca8bee)
aws_eip.server[0]: Creating...
  allocation_id:     "" => "<computed>"
  association_id:    "" => "<computed>"
  domain:            "" => "<computed>"
  instance:          "" => "i-052a34a0680983ae0"
  network_interface: "" => "<computed>"
  private_ip:        "" => "<computed>"
  public_ip:         "" => "<computed>"
  public_ipv4_pool:  "" => "<computed>"
  vpc:               "" => "<computed>"
aws_eip.server[1]: Creating...
  allocation_id:     "" => "<computed>"
  association_id:    "" => "<computed>"
  domain:            "" => "<computed>"
  instance:          "" => "i-04b7b0760c2ca8bee"
  network_interface: "" => "<computed>"
  private_ip:        "" => "<computed>"
  public_ip:         "" => "<computed>"
  public_ipv4_pool:  "" => "<computed>"
  vpc:               "" => "<computed>"
aws_eip.server[0]: Creation complete after 2s (ID: eipalloc-0c07ddabca4380095)
aws_eip.server[1]: Creation complete after 2s (ID: eipalloc-030dbe801558ada64)
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
IP Address:  = [
    18.213.188.193,
    54.83.98.206
]

Now that we have attached elastic IP to the created instances. You can streamline your output. Try this by updating the output.tf file:

output "IP Address - Instance Name: " {
  value = ["${aws_instance.server.*.public_ip}", "-", "${aws_instance.server.*.tags.Name}"]
}
## pseudo code - DO NOT INCLUDE THE BELOW IN YOUR OUTPUT.TF
count = n
for i=0; i<count; i++{
     print ${aws_instance.server.[i].public_ip} - ${aws_instance.server.[i].tags.Name}
}

Apply:

$ terraform apply -auto-approve
aws_instance.server[0]: Refreshing state... (ID: i-052a34a0680983ae0)
aws_instance.server[1]: Refreshing state... (ID: i-04b7b0760c2ca8bee)
aws_eip.server[1]: Refreshing state... (ID: eipalloc-030dbe801558ada64)
aws_eip.server[0]: Refreshing state... (ID: eipalloc-0c07ddabca4380095)
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
IP Address - Instance Name:  = [
    18.213.188.193,
    54.83.98.206,
    -,
    server-0,
    server-1
]

NEXT: Attaching Route53 and ElasticLoadBalancer

Discover and read more posts from Bolatito Kabir
get started
post commentsBe the first to share your opinion
Show more replies