21 Days of AWS using Terraform – Day 3- Creating AWS EC2 Instance using Terraform

Welcome to Day 3 of 21 Days of AWS using Terraform,  Let continue our journey, yesterday I discussed how to build AWS VPC using terraform.

In order to deploy EC2 instance we need a bunch of resources

  • AMI
  • Key Pair
  • EBS Volumes Creation
  • User data

The first step in deploying EC2 instance is choosing correct AMI and in terraform, there are various ways to do that

  • We can hardcore the value of AMI
  • We can use data resource(similar to what we used for Availability Zone in VPC section) to query and filter AWS and get the latest AMI based on the region, as the AMI id is different in a different region.
data "aws_ami" "centos" {
  owners      = ["679593333241"]
  most_recent = true

  filter {
    name   = "name"
    values = ["CentOS Linux 7 x86_64 HVM EBS *"]
  }

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }
}

NOTE: Use of data resource is not ideal and each and every used case, eg: In the case of Production we might want to use a specific version of CentOS.

  • The above code will help us to get the latest Centos AMI, the code is self-explanatory but one important parameter we used is owners
  • owners – Limit search to specific AMI owners. Valid items are the numeric account ID, amazon, or self.
  • most_recent – If more than one result is returned, use the most recent AMI.This is to get the latest Centos AMI as per our use case.

https://www.terraform.io/docs/providers/aws/d/ami.html

  • Other ways to find the AMI ID
Go to https://us-west-2.console.aws.amazon.com/ec2 --> Instances --> Launch Instances --> Search for centos 
  • Same thing you can do using AWS CLI
$ aws --region us-west-2 ec2 describe-images --owners aws-marketplace --filters Name=product-code,Values=aw0evgkw8e5c1q413zgy5pjce
 {
     "Images": [
         {
             "Architecture": "x86_64",
             "CreationDate": "2019-01-30T23:43:37.000Z",
             "ImageId": "ami-01ed306a12b7d1c96",
             "ImageLocation": "aws-marketplace/CentOS Linux 7 x86_64 HVM EBS ENA 1901_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-05713873c6794f575.4",
             "ImageType": "machine",
             "Public": true,
             "OwnerId": "679593333241",
             "ProductCodes": [
                 {
                     "ProductCodeId": "aw0evgkw8e5c1q413zgy5pjce",
                     "ProductCodeType": "marketplace"
                 }
             ],
             "State": "available",
             "BlockDeviceMappings": [
                 {
                     "DeviceName": "/dev/sda1",
                     "Ebs": {
                         "Encrypted": false,
                         "DeleteOnTermination": false,
                         "SnapshotId": "snap-040d21883a90fad29",
                         "VolumeSize": 8,
                         "VolumeType": "gp2"
                     }
                 }
             ],
             "Description": "CentOS Linux 7 x86_64 HVM EBS ENA 1901_01",
             "EnaSupport": true,
             "Hypervisor": "xen",
             "ImageOwnerAlias": "aws-marketplace",
             "Name": "CentOS Linux 7 x86_64 HVM EBS ENA 1901_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-05713873c6794f575.4",
             "RootDeviceName": "/dev/sda1",
             "RootDeviceType": "ebs",
             "SriovNetSupport": "simple",
             "VirtualizationType": "hvm"
         },
         {
             "Architecture": "x86_64",
             "CreationDate": "2018-04-04T00:11:39.000Z",
             "ImageId": "ami-0ebdd976",
             "ImageLocation": "aws-marketplace/CentOS Linux 7 x86_64 HVM EBS ENA 1803_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-8274d6ff.4",
             "ImageType": "machine",
             "Public": true,
             "OwnerId": "679593333241",
             "ProductCodes": [
                 {
                     "ProductCodeId": "aw0evgkw8e5c1q413zgy5pjce",
                     "ProductCodeType": "marketplace"
                 }
             ],
             "State": "available",
             "BlockDeviceMappings": [
                 {
                     "DeviceName": "/dev/sda1",
                     "Ebs": {
                         "Encrypted": false,
                         "DeleteOnTermination": false,
                         "SnapshotId": "snap-0b665edcc96bbb410",
                         "VolumeSize": 8,
                         "VolumeType": "gp2"
                     }
                 }
             ],
             "Description": "CentOS Linux 7 x86_64 HVM EBS ENA 1803_01",
             "EnaSupport": true,
             "Hypervisor": "xen",
             "ImageOwnerAlias": "aws-marketplace",
             "Name": "CentOS Linux 7 x86_64 HVM EBS ENA 1803_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-8274d6ff.4",
             "RootDeviceName": "/dev/sda1",
             "RootDeviceType": "ebs",
             "SriovNetSupport": "simple",
             "VirtualizationType": "hvm"
         },
         {
             "Architecture": "x86_64",
             "CreationDate": "2018-06-13T15:58:14.000Z",
             "ImageId": "ami-3ecc8f46",
             "ImageLocation": "aws-marketplace/CentOS Linux 7 x86_64 HVM EBS ENA 1805_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-77ec9308.4",
             "ImageType": "machine",
             "Public": true,
             "OwnerId": "679593333241",
             "ProductCodes": [
                 {
                     "ProductCodeId": "aw0evgkw8e5c1q413zgy5pjce",
                     "ProductCodeType": "marketplace"
                 }
             ],
             "State": "available",
             "BlockDeviceMappings": [
                 {
                     "DeviceName": "/dev/sda1",
                     "Ebs": {
                         "Encrypted": false,
                         "DeleteOnTermination": false,
                         "SnapshotId": "snap-0313e2ec7fa27f2e9",
                         "VolumeSize": 8,
                         "VolumeType": "gp2"
                     }
                 }
             ],
             "Description": "CentOS Linux 7 x86_64 HVM EBS ENA 1805_01",
             "EnaSupport": true,
             "Hypervisor": "xen",
             "ImageOwnerAlias": "aws-marketplace",
             "Name": "CentOS Linux 7 x86_64 HVM EBS ENA 1805_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-77ec9308.4",
             "RootDeviceName": "/dev/sda1",
             "RootDeviceType": "ebs",
             "SriovNetSupport": "simple",
             "VirtualizationType": "hvm"
         },
         {
             "Architecture": "x86_64",
             "CreationDate": "2018-05-17T09:30:44.000Z",
             "ImageId": "ami-5490ed2c",
             "ImageLocation": "aws-marketplace/CentOS Linux 7 x86_64 HVM EBS ENA 1804_2-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-55a2322a.4",
             "ImageType": "machine",
             "Public": true,
             "OwnerId": "679593333241",
             "ProductCodes": [
                 {
                     "ProductCodeId": "aw0evgkw8e5c1q413zgy5pjce",
                     "ProductCodeType": "marketplace"
                 }
             ],
             "State": "available",
             "BlockDeviceMappings": [
                 {
                     "DeviceName": "/dev/sda1",
                     "Ebs": {
                         "Encrypted": false,
                         "DeleteOnTermination": false,
                         "SnapshotId": "snap-012ad984270e9fede",
                         "VolumeSize": 8,
                         "VolumeType": "gp2"
                     }
                 }
             ],
             "Description": "CentOS Linux 7 x86_64 HVM EBS ENA 1804_2",
             "EnaSupport": true,
             "Hypervisor": "xen",
             "ImageOwnerAlias": "aws-marketplace",
             "Name": "CentOS Linux 7 x86_64 HVM EBS ENA 1804_2-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-55a2322a.4",
             "RootDeviceName": "/dev/sda1",
             "RootDeviceType": "ebs",
             "SriovNetSupport": "simple",
             "VirtualizationType": "hvm"
         },
         {
             "Architecture": "x86_64",
             "CreationDate": "2017-12-05T14:49:18.000Z",
             "ImageId": "ami-b63ae0ce",
             "ImageLocation": "aws-marketplace/CentOS Linux 7 x86_64 HVM EBS 1708_11.01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-95096eef.4",
             "ImageType": "machine",
             "Public": true,
             "OwnerId": "679593333241",
             "ProductCodes": [
                 {
                     "ProductCodeId": "aw0evgkw8e5c1q413zgy5pjce",
                     "ProductCodeType": "marketplace"
                 }
             ],
             "State": "available",
             "BlockDeviceMappings": [
                 {
                     "DeviceName": "/dev/sda1",
                     "Ebs": {
                         "Encrypted": false,
                         "DeleteOnTermination": false,
                         "SnapshotId": "snap-045714dfd4f364480",
                         "VolumeSize": 8,
                         "VolumeType": "standard"
                     }
                 }
             ],
             "Description": "CentOS Linux 7 x86_64 HVM EBS 1708_11.01",
             "EnaSupport": true,
             "Hypervisor": "xen",
             "ImageOwnerAlias": "aws-marketplace",
             "Name": "CentOS Linux 7 x86_64 HVM EBS 1708_11.01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-95096eef.4",
             "RootDeviceName": "/dev/sda1",
             "RootDeviceType": "ebs",
             "SriovNetSupport": "simple",
             "VirtualizationType": "hvm"
         }
     ]
 }
  • OR if you want output in tabular format
$ aws ec2 describe-images \
>       --owners aws-marketplace \
>       --filters Name=product-code,Values=aw0evgkw8e5c1q413zgy5pjce \
>       --query 'Images[*].[CreationDate,Name,ImageId]' \
>       --filters "Name=name,Values=CentOS Linux 7*" \
>       --region us-west-2 \
>       --output table \
>   | sort -r
|  2019-01-30T23:43:37.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1901_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-05713873c6794f575.4  |  ami-01ed306a12b7d1c96  |
|  2018-06-13T15:58:14.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1805_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-77ec9308.4           |  ami-3ecc8f46           |
|  2018-05-17T09:30:44.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1804_2-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-55a2322a.4            |  ami-5490ed2c           |
|  2018-04-04T00:11:39.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1803_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-8274d6ff.4           |  ami-0ebdd976           |
|  2017-12-05T14:49:18.000Z|  CentOS Linux 7 x86_64 HVM EBS 1708_11.01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-95096eef.4            |  ami-b63ae0ce           |
|                                                                        DescribeImages                                                                         |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
+--------------------------+----------------------------------------------------------------------------------------------------------+-------------------------+
+--------------------------+----------------------------------------------------------------------------------------------------------+-------------------------+
  • If you look at the Description field
"Description": "CentOS Linux 7 x86_64 HVM EBS ENA 1901_01",

and then check the Terraform code in of the filter we use “CentOS Linux 7 x86_64 HVM EBS *” and that is one of the reasons of using that

filter {
name = "name"
values = [
"CentOS Linux 7 x86_64 HVM EBS *"]
}

https://wiki.centos.org/Cloud/AWS

  • As we are able to figure out the AMI part, the next step is to create and use the key pair
  • Either we can hardcode the value of key pair or generate a new key via command line and then refer to this file.
$ ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/home/prashant/.ssh/id_rsa): /tmp/id_rsa
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /tmp/id_rsa.
Your public key has been saved in /tmp/id_rsa.pub.
The key fingerprint is:
SHA256:A7lYe+KDAVoUFEDsC14wnb+NePzCntjZruJdJf0+apM prashant@prashant-ThinkPad-T440s
The key's randomart image is:
+---[RSA 2048]----+
|++*o.            |
| = o   .         |
|. = . +          |
|.+ o + +.        |
|+ o = B.So       |
| o . O +o..      |
|    + +.  ..     |
|   .+o=o E..     |
|  .ooB+o..o..    |
+----[SHA256]-----+
resource "aws_key_pair" "mytest-key" {
  key_name = "my-test-terraform-key"
  public_key = "${file(var.my_public_key)}"
}
* In var.my_public_key set the location as /tmp/id_rsa.pub
* To refer to this file, we need to use the file function

https://www.terraform.io/docs/configuration/functions/file.html

  • There is one more resource, I want to use here called template_file. The template_file data source renders a template from a template string, which is usually loaded from an external file. This you can use with user_data resource to execute any script during instance boot time
data "template_file" "init" {
  template = "${file("${path.module}/userdata.tpl")}"
}
  • userdata.tpl file will be like
#!/bin/bash
yum -y install httpd
echo "hello from terraform" >> /var/www/html/index.html
service httpd start
chkconfig httpd on
  • After AMI and Keys out of our way, let start building EC2 instance
resource "aws_instance" "my-test-instance" {
  count = 2
  ami = "${data.aws_ami.centos.id}"
  instance_type = "${var.instance_type}"
  key_name = "${aws_key_pair.mytest-key.id}"
  vpc_security_group_ids = ["${var.security_group}"]
  subnet_id = "${element(var.subnets, count.index )}"
  user_data = "${data.template_file.init.rendered}"
  tags = {
    Name = "my-instance-${count.index + 1}"
  }
}
Most of these parameters I already discussed in the first section, but let's quickly review it and check the new one

* count: The number of instance, we want to create
* ami: This time we are pulling ami using data resource
* instance_type: Important parameter in AWS Realm, the type of instance we want to create
* key_name: Resource we create earlier and we are just referencing it here
* tags: Tags are always helpful to assign label to your resources.
Below ones are special(vpc_security_group_ids & subnet_id), because both of these resource we created during the vpc section, so now what we need to do is to output it during VPC module and use there output as the input to this module.
  • If you notice the above code, one thing which is interesting here is vpc_security_group_ids and subnet_id
  • The interesting part, we already created these as a part of VPC code, so we just need to call in our EC2 terraform and the way to do it using outputs.tf.
output "public_subnets" {
  value = "${aws_subnet.public_subnet.*.id}"
}

output "security_group" {
  value = "${aws_security_group.test_sg.id}"
}
  • After calling these values here, we just need to define as the part of main module and the syntax of doing that is
security_group = "${module.vpc.security_group}"
subnets = "${module.vpc.public_subnets}"
  • Final module code for EC2 instance look like this
module "ec2" {
source = "./ec2"
my_public_key = "/tmp/id_rsa.pub"
instance_type = "t2.micro"
security_group = "${module.vpc.security_group}"
subnets = "${module.vpc.public_subnets}"
}
  • Let’s create two EBS volumes and attach it to two EC2 instances we created earlier
resource "aws_ebs_volume" "my-test-ebs" {
  count = 2
  availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
  size = 1
  type = "gp2"
}


resource "aws_volume_attachment" "my-vol-attach" {
  count = 2
  device_name = "/dev/xvdh"
  instance_id = "${aws_instance.my-test-instance.*.id[count.index]}"
  volume_id = "${aws_ebs_volume.my-test-ebs.*.id[count.index]}"
}
* To create EBS Volumes, I am using ebs_volume resource and to attach it use aws_volume_attachment
* We are creating two Volumes here
* As Volume is specific to Availibility Zone, I am using aws_availibility_zone data resource
* Size of the Volume is 1GB
* Type is gp2(other available options "standard", "gp2", "io1", "sc1" or "st1" (Default: "standard"))

https://www.terraform.io/docs/providers/aws/r/ebs_volume.html

https://www.terraform.io/docs/providers/aws/r/volume_attachment.html

  • As this time we are creating ebs volume, let’s modify our userdata.tpl script and format the partition
#!/bin/bash
mkfs.ext4 /dev/xvdh
mount /dev/xvdh /mnt
echo /dev/xvdh /mnt defaults,nofail 0 2 >> /etc/fstab

Github Link

https://github.com/100daysofdevops/21_days_of_aws_using_terraform

  • Looking forward for you guys to join this journey

In addition to that, I am going to host 5 meetups whose aim is to build the below architecture.

  • Meetup: https://www.meetup.com/100daysofdevops
  • Day1(Nov 10): Introduction to Terraform
https://www.meetup.com/100daysofdevops/events/266192294/
  • Day 2(Nov 16): Building VPC using Terraform
https://www.meetup.com/100daysofdevops/events/266400556/
  • Day 3(Nov 17): Creating EC2 Instance inside this VPC using Terraform
https://www.meetup.com/100daysofdevops/events/266400580/
  • Day 4(Nov 23): Adding Application Load Balancer and Auto-Scaling to the EC2 instance created on Day 3
  • Day5(Nov 24): Add Backend MySQL Database and CloudWatch Alarm using Terraform

21 Days of AWS using Terraform – Day 2- Building AWS VPC using Terraform

Welcome to Day 2 of 21 Days of AWS using Terraform,  Let continue our journey, yesterday I discussed terraform, today let’s build VPC using terraform

What is VPC?

Without going to all the nitty-gritty details of VPC, first, let’s try to understand VPC in the simplest term. Before the cloud era, we use to have datacenters where we deploy all of our infrastructures.

You can think of VPC as your datacentre in a cloud but rather than spending months or weeks to set up that datacenter it’s now just a matter of minutes(API calls). It’s the place where you define your network which closely resembles your own traditional data centers with the benefits of using the scalable infrastructure provided by AWS.

  • Today we are going to build the first half of the equation i.e VPC
  • Once we create the VPC using AWS Console, these things created for us by-default
* Network Access Control List(NACL)
* Security Group
* Route Table
  • We need to take care of
* Internet Gateways
* Subnets
* Custom Route Table

But the bad news is as we are creating this via terraform we need to create all these things manually but this is just one time task, later on, if we need to build one more VPC we just need to call this module with some minor changes(eg: Changes in CIDR Range, Subnet) true Infrastructure as a Code(IAAC)

  • So the first step is to create a data resource, what data resource did is to query/list all the AWS available Availablity zone in a given region and then allow terraform to use those resource.
data "aws_availability_zones" "available" {}

https://www.terraform.io/docs/providers/aws/d/availability_zones.html

  • Now it’s time to create VPC
resource "aws_vpc" "main" {
  cidr_block           = "${var.vpc_cidr}"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "my-new-test-vpc"
  }
}
  • cidr_block – The CIDR block for the VPC.
  • enable_dns_support – (Optional) A boolean flag to enable/disable DNS support in the VPC. Defaults true. Amazon provided DNS server(AmazonProvidedDNS) can resolve Amazon provided private DNS hostnames, that we specify in a private hosted zones in Route53.
  • enable_dns_hostnames – (Optional) A boolean flag to enable/disable DNS hostnames in the VPC. Defaults false. This will ensure that instances that are launched into our VPC receive a DNS hostname.

https://www.terraform.io/docs/providers/aws/r/vpc.html

  • Next step is to create an Internet Gateway(IGW)
* Internet gateway is a horizontally scaled, redundant and highly avilable VPC component.
* Internet gateway serves one more purpose, it performs NAT for instances that have been assigned public IPv4 addresses.
resource "aws_internet_gateway" "gw" {
  vpc_id = "${aws_vpc.main.id}"

  tags = {
    Name = "my-test-igw"
  }
}

https://www.terraform.io/docs/providers/aws/r/internet_gateway.html

  • Next step is to create Public Route Table
  • Route Table: Contains a set of rules, called routes, that are used to determine where network traffic is directed.
resource "aws_route_table" "public_route" {
  vpc_id = "${aws_vpc.main.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.gw.id}"
  }

  tags = {
    Name = "my-test-public-route"
  }
}

https://www.terraform.io/docs/providers/aws/r/route_table.html

  • Now it’s time to create Private Route Table. If the subnet is not associated with any route by default it will be associated with Private Route table
resource "aws_default_route_table" "private_route" {
  default_route_table_id = "${aws_vpc.main.default_route_table_id}"

  tags = {   
    Name = "my-private-route-table"
  } 
}   
  • Next step is to create Public Subnet
resource "aws_subnet" "public_subnet" { 
  count                   = 2
  cidr_block              = "${var.public_cidrs[count.index]}"
  vpc_id                  = "${aws_vpc.main.id}"
  map_public_ip_on_launch = true
  availability_zone       = "${data.aws_availability_zones.available.names[count.index]}"

  tags = {
    Name = "my-test-public-subnet.${count.index + 1}"
  }
}
  • Private Subnet
resource "aws_subnet" "private_subnet" {
  count             = 2
  cidr_block        = "${var.private_cidrs[count.index]}"
  vpc_id            = "${aws_vpc.main.id}"
  availability_zone = "${data.aws_availability_zones.available.names[count.index]}"

  tags = {
    Name = "my-test-private-subnet.${count.index + 1}"
  }
}

https://www.terraform.io/docs/providers/aws/r/subnet.html

  • Next step is to create a route table association
# Associate Public Subnet with Public Route Table
resource "aws_route_table_association" "public_subnet_assoc" {
  count          = 2
  route_table_id = "${aws_route_table.public_route.id}"
  subnet_id      = "${aws_subnet.public_subnet.*.id[count.index]}"
  depends_on     = ["aws_route_table.public_route", "aws_subnet.public_subnet"]
}

# Associate Private Subnet with Private Route Table
resource "aws_route_table_association" "private_subnet_assoc" {
  count          = 2
  route_table_id = "${aws_default_route_table.private_route.id}"
  subnet_id      = "${aws_subnet.private_subnet.*.id[count.index]}"
  depends_on     = ["aws_default_route_table.private_route", "aws_subnet.private_subnet"]
}

https://www.terraform.io/docs/providers/aws/r/route_table_association.html

  • Network Access Control List(NACL) A network access control list (ACL) is an optional layer of security for your VPC that acts as a firewall for controlling traffic in and out of one or more subnets.
  • Security Group acts as a virtual firewall and is used to control the traffic for its associated instances.
  • Difference between NACL and Security Group
resource "aws_security_group" "my-test-sg" {
  name   = "my-test-sg"
  vpc_id = "${aws_vpc.my-test-vpc.id}"
}

resource "aws_security_group_rule" "allow-ssh" {
  from_port         = 22
  protocol          = "tcp"
  security_group_id = "${aws_security_group.my-test-sg.id}"
  to_port           = 22
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "allow-outbound" {
  from_port         = 0
  protocol          = "-1"
  security_group_id = "${aws_security_group.my-test-sg.id}"
  to_port           = 0
  type              = "egress"
  cidr_blocks       = ["0.0.0.0/0"]
}

https://www.terraform.io/docs/providers/aws/r/security_group.html

  • This is how our variables file look like
  • Now let’s test it
  • Initialize a Terraform working directory
$ terraform  init
Initializing modules...
- vpc in vpc

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.35.0...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 2.35"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
  • Execute terraform plan
  • Generate and show an execution plan
$ 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.

module.vpc.data.aws_availability_zones.available: Refreshing state...

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

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:

  # module.vpc.aws_default_route_table.private_route will be created
  + resource "aws_default_route_table" "private_route" {
      + default_route_table_id = (known after apply)
      + id                     = (known after apply)
      + owner_id               = (known after apply)
      + route                  = (known after apply)
      + tags                   = {
          + "Name" = "my-private-route-table"
        }
      + vpc_id                 = (known after apply)
    }

  # module.vpc.aws_internet_gateway.gw will be created
  + resource "aws_internet_gateway" "gw" {
      + id       = (known after apply)
      + owner_id = (known after apply)
      + tags     = {
          + "Name" = "my-test-igw"
        }
      + vpc_id   = (known after apply)
    }

  # module.vpc.aws_route_table.public_route will be created
  + resource "aws_route_table" "public_route" {
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + cidr_block                = "0.0.0.0/0"
              + egress_only_gateway_id    = ""
              + gateway_id                = (known after apply)
              + instance_id               = ""
              + ipv6_cidr_block           = ""
              + nat_gateway_id            = ""
              + network_interface_id      = ""
              + transit_gateway_id        = ""
              + vpc_peering_connection_id = ""
            },
        ]
      + tags             = {
          + "Name" = "my-test-public-route"
        }
      + vpc_id           = (known after apply)
    }

  # module.vpc.aws_route_table_association.private_subnet_assoc[0] will be created
  + resource "aws_route_table_association" "private_subnet_assoc" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.vpc.aws_route_table_association.private_subnet_assoc[1] will be created
  + resource "aws_route_table_association" "private_subnet_assoc" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.vpc.aws_route_table_association.public_subnet_assoc[0] will be created
  + resource "aws_route_table_association" "public_subnet_assoc" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.vpc.aws_route_table_association.public_subnet_assoc[1] will be created
  + resource "aws_route_table_association" "public_subnet_assoc" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.vpc.aws_security_group.test_sg will be created
  + resource "aws_security_group" "test_sg" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = (known after apply)
      + name                   = "my-test-sg"
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + vpc_id                 = (known after apply)
    }

  # module.vpc.aws_security_group_rule.all_outbound_access will be created
  + resource "aws_security_group_rule" "all_outbound_access" {
      + cidr_blocks              = [
          + "0.0.0.0/0",
        ]
      + from_port                = 0
      + id                       = (known after apply)
      + protocol                 = "-1"
      + security_group_id        = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 0
      + type                     = "egress"
    }

  # module.vpc.aws_security_group_rule.ssh_inbound_access will be created
  + resource "aws_security_group_rule" "ssh_inbound_access" {
      + cidr_blocks              = [
          + "0.0.0.0/0",
        ]
      + from_port                = 22
      + id                       = (known after apply)
      + protocol                 = "tcp"
      + security_group_id        = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 22
      + type                     = "ingress"
    }

  # module.vpc.aws_subnet.private_subnet[0] will be created
  + resource "aws_subnet" "private_subnet" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "us-west-2a"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.3.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" = "my-test-private-subnet.1"
        }
      + vpc_id                          = (known after apply)
    }

  # module.vpc.aws_subnet.private_subnet[1] will be created
  + resource "aws_subnet" "private_subnet" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "us-west-2b"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.4.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" = "my-test-private-subnet.2"
        }
      + vpc_id                          = (known after apply)
    }

  # module.vpc.aws_subnet.public_subnet[0] will be created
  + resource "aws_subnet" "public_subnet" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "us-west-2a"
      + 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         = true
      + owner_id                        = (known after apply)
      + tags                            = {
          + "Name" = "my-test-public-subnet.1"
        }
      + vpc_id                          = (known after apply)
    }

  # module.vpc.aws_subnet.public_subnet[1] will be created
  + resource "aws_subnet" "public_subnet" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "us-west-2b"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.2.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         = true
      + owner_id                        = (known after apply)
      + tags                            = {
          + "Name" = "my-test-public-subnet.2"
        }
      + vpc_id                          = (known after apply)
    }

  # module.vpc.aws_vpc.main will be created
  + resource "aws_vpc" "main" {
      + 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             = true
      + 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" = "my-new-test-vpc"
        }
    }

Plan: 15 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.
  • Final step terraform apply
  • Builds or changes the infrastructure
$ terraform  apply
module.vpc.data.aws_availability_zones.available: Refreshing state...

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:

  # module.vpc.aws_default_route_table.private_route will be created
  + resource "aws_default_route_table" "private_route" {
      + default_route_table_id = (known after apply)
      + id                     = (known after apply)
      + owner_id               = (known after apply)
      + route                  = (known after apply)
      + tags                   = {
          + "Name" = "my-private-route-table"
        }
      + vpc_id                 = (known after apply)
    }

  # module.vpc.aws_internet_gateway.gw will be created
  + resource "aws_internet_gateway" "gw" {
      + id       = (known after apply)
      + owner_id = (known after apply)
      + tags     = {
          + "Name" = "my-test-igw"
        }
      + vpc_id   = (known after apply)
    }

  # module.vpc.aws_route_table.public_route will be created
  + resource "aws_route_table" "public_route" {
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + cidr_block                = "0.0.0.0/0"
              + egress_only_gateway_id    = ""
              + gateway_id                = (known after apply)
              + instance_id               = ""
              + ipv6_cidr_block           = ""
              + nat_gateway_id            = ""
              + network_interface_id      = ""
              + transit_gateway_id        = ""
              + vpc_peering_connection_id = ""
            },
        ]
      + tags             = {
          + "Name" = "my-test-public-route"
        }
      + vpc_id           = (known after apply)
    }

  # module.vpc.aws_route_table_association.private_subnet_assoc[0] will be created
  + resource "aws_route_table_association" "private_subnet_assoc" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.vpc.aws_route_table_association.private_subnet_assoc[1] will be created
  + resource "aws_route_table_association" "private_subnet_assoc" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.vpc.aws_route_table_association.public_subnet_assoc[0] will be created
  + resource "aws_route_table_association" "public_subnet_assoc" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.vpc.aws_route_table_association.public_subnet_assoc[1] will be created
  + resource "aws_route_table_association" "public_subnet_assoc" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.vpc.aws_security_group.test_sg will be created
  + resource "aws_security_group" "test_sg" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = (known after apply)
      + name                   = "my-test-sg"
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + vpc_id                 = (known after apply)
    }

  # module.vpc.aws_security_group_rule.all_outbound_access will be created
  + resource "aws_security_group_rule" "all_outbound_access" {
      + cidr_blocks              = [
          + "0.0.0.0/0",
        ]
      + from_port                = 0
      + id                       = (known after apply)
      + protocol                 = "-1"
      + security_group_id        = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 0
      + type                     = "egress"
    }

  # module.vpc.aws_security_group_rule.ssh_inbound_access will be created
  + resource "aws_security_group_rule" "ssh_inbound_access" {
      + cidr_blocks              = [
          + "0.0.0.0/0",
        ]
      + from_port                = 22
      + id                       = (known after apply)
      + protocol                 = "tcp"
      + security_group_id        = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 22
      + type                     = "ingress"
    }

  # module.vpc.aws_subnet.private_subnet[0] will be created
  + resource "aws_subnet" "private_subnet" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "us-west-2a"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.3.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" = "my-test-private-subnet.1"
        }
      + vpc_id                          = (known after apply)
    }

  # module.vpc.aws_subnet.private_subnet[1] will be created
  + resource "aws_subnet" "private_subnet" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "us-west-2b"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.4.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" = "my-test-private-subnet.2"
        }
      + vpc_id                          = (known after apply)
    }

  # module.vpc.aws_subnet.public_subnet[0] will be created
  + resource "aws_subnet" "public_subnet" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "us-west-2a"
      + 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         = true
      + owner_id                        = (known after apply)
      + tags                            = {
          + "Name" = "my-test-public-subnet.1"
        }
      + vpc_id                          = (known after apply)
    }

  # module.vpc.aws_subnet.public_subnet[1] will be created
  + resource "aws_subnet" "public_subnet" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "us-west-2b"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.2.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         = true
      + owner_id                        = (known after apply)
      + tags                            = {
          + "Name" = "my-test-public-subnet.2"
        }
      + vpc_id                          = (known after apply)
    }

  # module.vpc.aws_vpc.main will be created
  + resource "aws_vpc" "main" {
      + 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             = true
      + 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" = "my-new-test-vpc"
        }
    }

Plan: 15 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

module.vpc.aws_vpc.main: Creating...
module.vpc.aws_vpc.main: Creation complete after 5s [id=vpc-0b97129b9e91cac4d]
module.vpc.aws_internet_gateway.gw: Creating...
module.vpc.aws_default_route_table.private_route: Creating...
module.vpc.aws_subnet.private_subnet[0]: Creating...
module.vpc.aws_subnet.private_subnet[1]: Creating...
module.vpc.aws_security_group.test_sg: Creating...
module.vpc.aws_subnet.public_subnet[1]: Creating...
module.vpc.aws_subnet.public_subnet[0]: Creating...
module.vpc.aws_default_route_table.private_route: Creation complete after 2s [id=rtb-034ed4f91b0c6e970]
module.vpc.aws_subnet.private_subnet[0]: Creation complete after 2s [id=subnet-0fb890defa508e1bd]
module.vpc.aws_subnet.private_subnet[1]: Creation complete after 2s [id=subnet-0f210acfcdbb26b1c]
module.vpc.aws_route_table_association.private_subnet_assoc[1]: Creating...
module.vpc.aws_route_table_association.private_subnet_assoc[0]: Creating...
module.vpc.aws_subnet.public_subnet[1]: Creation complete after 2s [id=subnet-0c1da219075f58bbf]
module.vpc.aws_internet_gateway.gw: Creation complete after 2s [id=igw-048be706e974b88cb]
module.vpc.aws_subnet.public_subnet[0]: Creation complete after 2s [id=subnet-0d881fbf8d72978ab]
module.vpc.aws_route_table.public_route: Creating...
module.vpc.aws_route_table_association.private_subnet_assoc[1]: Creation complete after 0s [id=rtbassoc-08c8b58da17951c27]
module.vpc.aws_route_table_association.private_subnet_assoc[0]: Creation complete after 0s [id=rtbassoc-033c1036cbb76fbd3]
module.vpc.aws_security_group.test_sg: Creation complete after 2s [id=sg-05a2779f164a402c7]
module.vpc.aws_security_group_rule.ssh_inbound_access: Creating...
module.vpc.aws_security_group_rule.all_outbound_access: Creating...
module.vpc.aws_security_group_rule.all_outbound_access: Creation complete after 2s [id=sgrule-4135805673]
module.vpc.aws_route_table.public_route: Creation complete after 2s [id=rtb-07f3c3293741dfbaf]
module.vpc.aws_route_table_association.public_subnet_assoc[0]: Creating...
module.vpc.aws_route_table_association.public_subnet_assoc[1]: Creating...
module.vpc.aws_route_table_association.public_subnet_assoc[1]: Creation complete after 0s [id=rtbassoc-06332e5c154dae9d8]
module.vpc.aws_route_table_association.public_subnet_assoc[0]: Creation complete after 0s [id=rtbassoc-02aa98f854d51e7aa]
module.vpc.aws_security_group_rule.ssh_inbound_access: Creation complete after 3s [id=sgrule-2460989827]

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

Terraform Module

  • You can think of Terraform Module like any other language module eg: Python, it’s the same terraform file but just that after creating a module out it we can re-use that code OR Instead copy-pasting the code the same code in different places we can turn into reusable modules.

The syntax for the module

module "NAME" {
source = "SOURCE"

[CONFIG ...]
}
module "vpc" {
  source        = "./vpc"
  vpc_cidr      = "10.0.0.0/16"
  public_cidrs  = ["10.0.1.0/24", "10.0.2.0/24"]
  private_cidrs = ["10.0.3.0/24", "10.0.4.0/24"]
}
  • variables.tf file look like this
variable "vpc_cidr" {
}

variable "public_cidrs" {
  type = "list"
}

variable "private_cidrs" {
  type = "list"
}

GitHub Link

https://github.com/100daysofdevops/21_days_of_aws_using_terraform

  • Looking forward for you guys to join this journey

In addition to that, I am going to host 5 meetups whose aim is to build the below architecture.

  • Meetup: https://www.meetup.com/100daysofdevops
  • Day1(Nov 10): Introduction to Terraform https://www.meetup.com/100daysofdevops/events/266192294/
  • Day 2(Nov 16): Building VPC using Terraform
  • Day 3(Nov 17): Creating EC2 Instance inside this VPC using Terraform
  • Day 4(Nov 23): Adding Application Load Balancer and Auto-Scaling to the EC2 instance created on Day 3
  • Day5(Nov 24): Add Backend MySQL Database and CloudWatch Alarm using Terraform

21 Days of AWS using Terraform – Day 1- Introduction to Terraform

Session link

GitHub Link

https://github.com/100daysofdevops/21_days_of_aws_using_terraform

Hello everyone and welcome to Day 1 of 21 Days of AWS using Terraform, the topic for today is Introduction to Terraform. Let’s begin our AWS automation journey using Terraform.

What is terraform?

Terraform is a tool for provisioning infrastructure(or managing Infrastructure as Code). It supports multiple providers(eg, AWS, Google Cloud, Azure, OpenStack..).

https://www.terraform.io/docs/providers/index.html

Installing Terraform

Installing Terraform is pretty straightforward, download it from Terraform download page and select the appropriate package based on your operating system.

https://learn.hashicorp.com/terraform/getting-started/install.html

https://www.terraform.io/downloads.html

I am using Centos7, so these are the steps I need to follow, to install terraform on Centos7.

Step1: Download the Package
$ wget https://releases.hashicorp.com/terraform/0.12.13/terraform_0.12.13_linux_amd64.zip
--2019-11-08 18:47:29--  https://releases.hashicorp.com/terraform/0.12.13/terraform_0.12.13_linux_amd64.zip
Resolving releases.hashicorp.com (releases.hashicorp.com)... 2a04:4e42:2f::439, 151.101.201.183
Connecting to releases.hashicorp.com (releases.hashicorp.com)|2a04:4e42:2f::439|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 16341595 (16M) [application/zip]
Saving to: ‘terraform_0.12.13_linux_amd64.zip’

100%[==================================================================================================================================================================>] 16,341,595  --.-K/s   in 0.09s   

2019-11-08 18:47:29 (172 MB/s) - ‘terraform_0.12.13_linux_amd64.zip’ saved [16341595/16341595]

Step2: Unzip it
$ unzip terraform_0.12.13_linux_amd64.zip 
Archive:  terraform_0.12.13_linux_amd64.zip
  inflating: terraform      

Step3: Add the binary to PATH environment variable
sudo cp terraform /usr/local/bin/
sudo chmod +x /usr/local/bin/terraform

Step4:  Logout and log back in
  • To verify terraform is installed properly
$ terraform version
Terraform v0.12.13

As mentioned above terraform support many providers, for my use case I am using AWS.

Prerequisites1: Existing AWS Account(OR Setup a new account)
2: IAM full access(OR at least have AmazonEC2FullAccess)
3: AWS Credentials(AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY)

Once you have pre-requisites 1 and 2 done, the first step is to export Keys AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.

export AWS_ACCESS_KEY_ID="your access key id here"
export AWS_SECRET_ACCESS_KEY="your secret access key id here"

NOTE: These two variables are bound to your current shell, in case of reboot, or if open a new shell window, these changes will be lost

With all pre-requisites in place, it’s time to write your first terraform code, but before that just a brief overview about terraform language

  • Terraform code is written in the HashiCorp Configuration Language(HCL)
  • All the code ends with the extension of .tf
  • It’s a declarative language(We need to define what infrastructure we want and terraform will figure out how to create it)

In this first example I am going to build EC2 instance, but before creating EC2 instance go to AWS console and think what the different things we need to build EC2 instance are

  • Amazon Machine Image(AMI)
  • Instance Type
  • Network Information(VPC/Subnet)
  • Tags
  • Security Group
  • Key Pair

Let break each of these steps by step

  • Amazon Machine Image(AMI): It’s an Operating System Image used to run EC2 instance. For this example, I am using Centos 7 ami-01ed306a12b7d1c96. We can create our own AMI using AWS console or Packer.

https://www.packer.io/intro/

Other ways to find out the AMI id for Centos7 Image is

$ aws ec2 describe-images \
>       --owners aws-marketplace \
>       --filters Name=product-code,Values=aw0evgkw8e5c1q413zgy5pjce \
>       --query 'Images[*].[CreationDate,Name,ImageId]' \
>       --filters "Name=name,Values=CentOS Linux 7*" \
>       --region us-west-2 \
>       --output table \
>   | sort -r
|                                                                        DescribeImages                                                                         |
|  2019-01-30T23:43:37.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1901_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-05713873c6794f575.4  |  ami-01ed306a12b7d1c96  |
|  2018-06-13T15:58:14.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1805_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-77ec9308.4           |  ami-3ecc8f46           |
|  2018-05-17T09:30:44.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1804_2-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-55a2322a.4            |  ami-5490ed2c           |
|  2018-04-04T00:11:39.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1803_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-8274d6ff.4           |  ami-0ebdd976           |
|  2017-12-05T14:49:18.000Z|  CentOS Linux 7 x86_64 HVM EBS 1708_11.01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-95096eef.4            |  ami-b63ae0ce           |
+--------------------------+----------------------------------------------------------------------------------------------------------+-------------------------+
+--------------------------+----------------------------------------------------------------------------------------------------------+-------------------------+
-----------------------------------------------------------------------------------------------------------------------------------------------------------------

For more info please refer to AWS official doc

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html

  • Instance Type: Type of EC2 instance to run, as every instance type provides different capabilities(CPU, Memory, I/O). For this example, I am using t2.micro(1 Virtual CPU, 1GB Memory)

For more info please refer to Amazon EC2 instance type

https://aws.amazon.com/ec2/instance-types/

  • Network Information(VPC/Subnet Id): Virtual Private Cloud(VPC) is an isolated area of AWS account that has it’s own virtual network and IP address space. For this example, I am using default VPC which is part of a new AWS account. In case if you want to set up your instance in custom VPC, you need to add two additional parameters(vpc_security_group_ids and subnet_id) to your terraform code

For more information about VPC, please refer

https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html

Tags: Tag we can think of a label that helps to categorize AWS resources.

For more information about the tag, please refer

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html

Security Group: Security group can think like a virtual firewall, and it controls the traffic of your instance

For more information about Security Group, please refer

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-security-groups.html

Key Pair: Key Pair is used to access EC2 instance

For more info about Key Pair, please refer to

https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#key-pairs

Let review all these parameters and see what we already have and what we need to create to spun our first EC2 instance

  • Amazon Machine Image(AMI) → ami-01ed306a12b7d1c96
  • Instance Type → t2.micro
  • Network Information(VPC/Subnet) → Default VPC
  • Tags
  • Security Group
  • Key Pair

So we already have AMI, Instance Type and Network Information, we need to write terraform code for rest of the parameter to spun our first EC2 instance.

Let start with Key Pair

Go to terraform documentation and search for aws key pair

https://www.terraform.io/docs/providers/aws/r/key_pair.html

resource "aws_key_pair" "example" {
  key_name = "example-key"
  public_key = ""
}

Let try to dissect this code

  • Under resource, we specify the type of resource to create, in this case we are using aws_key_pair as a resource
  • an example is an identifier which we can use throughout the code to refer back to this resource
  • key_name: Is the name of the Key
  • public_key: Is the public portion of ssh generated Key

The same thing we need to do for Security Group, go back to the terraform documentation and search for the security group.

https://www.terraform.io/docs/providers/aws/r/security_group.html

resource "aws_security_group" "examplesg" {
  
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
  • Same thing let do with this code, first two parameter is similar to key pair
  • ingress: refer to in-bound traffic to port 22, using protocol tcp
  • cidr_block: list of cidr_block where you want to allow this traffic(This is just as an example please never use 0.0.0.0/0)

With Key_pair and Security Group in place it’s time to create first EC2 instance.

https://www.terraform.io/docs/providers/aws/d/instance.html

resource "aws_instance" "ec2_instance" {
  ami = "ami-01ed306a12b7d1c96"
  instance_type = "t2.micro"
  vpc_security_group_ids = ["${aws_security_group.examplesg.id}"]
  key_name = "${aws_key_pair.example.id}"
  tags = {
    Name = "my-first-ec2-instance"
  }
}
  • In this case we just need to check the doc and fill out all the remaining values
  • To refer back to the security group we need to use interpolation syntax by terraform

https://www.terraform.io/docs/configuration-0-11/interpolation.html

This look like

"${var_to_interpolate}

Whenever you see a $ sign and curly braces inside the double quotes, that means terraform is going to interpolate that code specially. To get the id of the security group

"${aws_security_group.examplesg.id}"

Same thing applied to key pair

"${aws_key_pair.example.id}"

Our code is ready but we are missing one thing, provider before starting any code we need to tell terraform which provider we are using(aws in this case)

provider "aws" {
region = "us-west-2"
}
  • This tells terraform that you are going to use AWS as provider and you want to deploy your infrastructure in us-west-2 region
  • AWS has datacenter all over the world, which are grouped in region and availability zones. Region is a separate geographic area(Oregon, Virginia, Sydney) and each region has multiple isolated datacenters(us-west-2a,us-west-2b..)

For more info about regions and availability zones, please refer to the below doc

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html

provider "aws"{
  region = "us-west-2"
}

resource "aws_key_pair" "example" {
  key_name = "example-key"
  public_key = "ssh public key"
}


resource "aws_security_group" "examplesg" {
  ingress {
    from_port = 22
    protocol = "tcp"
    to_port = 22
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "ec2_instance" {
  ami = "ami-01ed306a12b7d1c96"
  instance_type = "t2.micro"
  vpc_security_group_ids = ["${aws_security_group.examplesg.id}"]
  key_name = "${aws_key_pair.example.id}"
  tags = {
    Name = "my-first-ec2-instance"
  }
}

Before running terraform command to spun our first EC2 instance, run terraform fmt command which will rewrite terraform configuration files to a canonical format and style

$ terraform  fmt
main.tf
  • The first command we are going to run to setup our instance is terraform init, what this will do is going to download code for a provider(aws) that we are going to use.
  • Terraform binary contains the basic functionality for terraform but it doesn’t come with the code for any of the providers(eg: AWS, Azure and GCP), so when we are first starting to use terraform we need to run terraform init to tell terraform to scan the code and figure out which providers we are using and download the code for them.
  • By default, the provider code will be downloaded into a .terraform directory which is a scrarch directory(we may want to add it in a .gitignore).

NOTE: It’s safe to run terraform init command multiple times as it’s idempotent.

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.35.0...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 2.35"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
  • Next command we are going to run is “terraform plan”, this will tell what terraform actually do before making any changes
  • This is good way of making any sanity check before making actual changes to env
  • Output of terraform plan command looks similar to Linux diff command

1: (+ sign): Resource going to be created

2: (- sign): Resources going to be deleted

3: (~ sign): Resource going to be modified

t$ 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.


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

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_instance.ec2_instance will be created
  + resource "aws_instance" "ec2_instance" {
      + ami                          = "ami-01ed306a12b7d1c96"
      + 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)
      + 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" = "my-first-ec2-instance"
        }
      + 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)
        }

      + 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)
          + 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_key_pair.example will be created
  + resource "aws_key_pair" "example" {
      + fingerprint = (known after apply)
      + id          = (known after apply)
      + key_name    = "example-key"
      + public_key  = ""
    }

  # aws_security_group.examplesg will be created
  + resource "aws_security_group" "examplesg" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 22
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 22
            },
        ]
      + name                   = (known after apply)
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + vpc_id                 = (known after apply)
    }

Plan: 3 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.
  • To apply these changes, run terraform apply
$ terraform  apply

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_instance.ec2_instance will be created
  + resource "aws_instance" "ec2_instance" {
      + ami                          = "ami-01ed306a12b7d1c96"
      + 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)
      + 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" = "my-first-ec2-instance"
        }
      + 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)
        }

      + 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)
          + 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_key_pair.example will be created
  + resource "aws_key_pair" "example" {
      + fingerprint = (known after apply)
      + id          = (known after apply)
      + key_name    = "example-key"
      + public_key  = ""
    }

  # aws_security_group.examplesg will be created
  + resource "aws_security_group" "examplesg" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 22
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 22
            },
        ]
      + name                   = (known after apply)
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + vpc_id                 = (known after apply)
    }

Plan: 3 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_key_pair.example: Creating...
aws_security_group.examplesg: Creating...
aws_key_pair.example: Creation complete after 0s [id=example-key]
aws_security_group.examplesg: Creation complete after 2s [id=sg-0e1a943b062aa2315]
aws_instance.ec2_instance: Creating...
aws_instance.ec2_instance: Still creating... [10s elapsed]
aws_instance.ec2_instance: Still creating... [20s elapsed]
aws_instance.ec2_instance: Still creating... [30s elapsed]
aws_instance.ec2_instance: Creation complete after 33s [id=i-0d1ab0d8c1fc57f3d]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
  • What terraform is doing here is reading code and translating it to api calls to providers(aws in this case)

W00t you have deployed your first EC2 server using terraform

Go back to the EC2 console to verify your first deployed server

Let say after verification you realize that I need to give more meaningful tag to this server, so the rest of the code remain the same and you modified the tag parameter

  tags = {
    Name = "my-webserver-instance"
  }
  • Run terraform plan command again, as you can see
  ~ tags                         = {
      ~ "Name" = "my-first-ec2-instance" -> "my-webserver-instance"
    }
$ 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_key_pair.example: Refreshing state... [id=example-key]
aws_security_group.examplesg: Refreshing state... [id=sg-0e1a943b062aa2315]
aws_instance.ec2_instance: Refreshing state... [id=i-0d1ab0d8c1fc57f3d]

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

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

Terraform will perform the following actions:

  # aws_instance.ec2_instance will be updated in-place
  ~ resource "aws_instance" "ec2_instance" {
        ami                          = "ami-01ed306a12b7d1c96"
        arn                          = "arn:aws:ec2:us-west-2:355622012945:instance/i-0d1ab0d8c1fc57f3d"
        associate_public_ip_address  = true
        availability_zone            = "us-west-2a"
        cpu_core_count               = 1
        cpu_threads_per_core         = 1
        disable_api_termination      = false
        ebs_optimized                = false
        get_password_data            = false
        id                           = "i-0d1ab0d8c1fc57f3d"
        instance_state               = "running"
        instance_type                = "t2.micro"
        ipv6_address_count           = 0
        ipv6_addresses               = []
        key_name                     = "example-key"
        monitoring                   = false
        primary_network_interface_id = "eni-0e5ddbe136b7d599d"
        private_dns                  = "ip-172-31-28-131.us-west-2.compute.internal"
        private_ip                   = "172.31.28.131"
        public_dns                   = "ec2-54-185-56-146.us-west-2.compute.amazonaws.com"
        public_ip                    = "54.185.56.146"
        security_groups              = [
            "terraform-20191110052042521000000001",
        ]
        source_dest_check            = true
        subnet_id                    = "subnet-3b929b42"
      ~ tags                         = {
          ~ "Name" = "my-first-ec2-instance" -> "my-webserver-instance"
        }
        tenancy                      = "default"
        volume_tags                  = {}
        vpc_security_group_ids       = [
            "sg-0e1a943b062aa2315",
        ]

        credit_specification {
            cpu_credits = "standard"
        }

        root_block_device {
            delete_on_termination = false
            encrypted             = false
            iops                  = 100
            volume_id             = "vol-0f48e9a8f42ac6dc4"
            volume_size           = 8
            volume_type           = "gp2"
        }
    }

Plan: 0 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.

  • Now if we can think about it, how does terraform knows that there only change in the tag parameter and nothing else
  • Terraform keep track of all the resources it already created in .tfstate files, so its aware of the resources that already exist.
$ ls -la
total 28
drwxr-xr-x  4 prashant prashant 4096 Nov  9 21:25 .
drwxr-xr-x 31 prashant prashant 4096 Nov  9 21:14 ..
-rw-r--r--  1 prashant prashant 1009 Nov  9 21:24 main.tf
drwxr-xr-x  3 prashant prashant 4096 Nov  9 21:15 .terraform
-rw-rw-r--  1 prashant prashant 5348 Nov  9 21:21 terraform.tfstate
  • If you notice at the top it says “Refreshing Terraform state in-memory prior to plan…”
  • If I refresh my webbrowser after running terraform apply.
$ terraform apply
aws_key_pair.example: Refreshing state... [id=example-key]
aws_security_group.examplesg: Refreshing state... [id=sg-0e1a943b062aa2315]
aws_instance.ec2_instance: Refreshing state... [id=i-0d1ab0d8c1fc57f3d]

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

Terraform will perform the following actions:

  # aws_instance.ec2_instance will be updated in-place
  ~ resource "aws_instance" "ec2_instance" {
        ami                          = "ami-01ed306a12b7d1c96"
        arn                          = "arn:aws:ec2:us-west-2:355622012945:instance/i-0d1ab0d8c1fc57f3d"
        associate_public_ip_address  = true
        availability_zone            = "us-west-2a"
        cpu_core_count               = 1
        cpu_threads_per_core         = 1
        disable_api_termination      = false
        ebs_optimized                = false
        get_password_data            = false
        id                           = "i-0d1ab0d8c1fc57f3d"
        instance_state               = "running"
        instance_type                = "t2.micro"
        ipv6_address_count           = 0
        ipv6_addresses               = []
        key_name                     = "example-key"
        monitoring                   = false
        primary_network_interface_id = "eni-0e5ddbe136b7d599d"
        private_dns                  = "ip-172-31-28-131.us-west-2.compute.internal"
        private_ip                   = "172.31.28.131"
        public_dns                   = "ec2-54-185-56-146.us-west-2.compute.amazonaws.com"
        public_ip                    = "54.185.56.146"
        security_groups              = [
            "terraform-20191110052042521000000001",
        ]
        source_dest_check            = true
        subnet_id                    = "subnet-3b929b42"
      ~ tags                         = {
          ~ "Name" = "my-first-ec2-instance" -> "my-webserver-instance"
        }
        tenancy                      = "default"
        volume_tags                  = {}
        vpc_security_group_ids       = [
            "sg-0e1a943b062aa2315",
        ]

        credit_specification {
            cpu_credits = "standard"
        }

        root_block_device {
            delete_on_termination = false
            encrypted             = false
            iops                  = 100
            volume_id             = "vol-0f48e9a8f42ac6dc4"
            volume_size           = 8
            volume_type           = "gp2"
        }
    }

Plan: 0 to add, 1 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_instance.ec2_instance: Modifying... [id=i-0d1ab0d8c1fc57f3d]
aws_instance.ec2_instance: Modifications complete after 2s [id=i-0d1ab0d8c1fc57f3d]

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

In most of the cases we are working in team where we want to share this code with rest of team members and the best way to share code is by using GIT

git add main.tf
git commit -m "first terraform EC2 instance"
vim .gitignore
git add .gitignore
git commit -m "Adding gitignore file for terraform repository"
  • Via .gitignore we are telling terraform to ignore(.terraform folder(temporary directory for terraform)and all *.tfstates file(as this file may contain secrets))
$ cat .gitignore
.terraform
*.tfstate
*.tfstate.backup
  • Create a shared git repository
git remote add origin https://github.com/<user name>/terraform.git
  • Push the code
$ git push -u origin master
  • To Perform cleanup whatever we have created so far, run terraform destroy
$ terraform destroy
aws_key_pair.example: Refreshing state... [id=example-key]
aws_security_group.examplesg: Refreshing state... [id=sg-0e1a943b062aa2315]
aws_instance.ec2_instance: Refreshing state... [id=i-0d1ab0d8c1fc57f3d]

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_instance.ec2_instance will be destroyed
  - resource "aws_instance" "ec2_instance" {
      - ami                          = "ami-01ed306a12b7d1c96" -> null
      - arn                          = "arn:aws:ec2:us-west-2:355622012945:instance/i-0d1ab0d8c1fc57f3d" -> null
      - associate_public_ip_address  = true -> null
      - availability_zone            = "us-west-2a" -> 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
      - id                           = "i-0d1ab0d8c1fc57f3d" -> null
      - instance_state               = "running" -> null
      - instance_type                = "t2.micro" -> null
      - ipv6_address_count           = 0 -> null
      - ipv6_addresses               = [] -> null
      - key_name                     = "example-key" -> null
      - monitoring                   = false -> null
      - primary_network_interface_id = "eni-0e5ddbe136b7d599d" -> null
      - private_dns                  = "ip-172-31-28-131.us-west-2.compute.internal" -> null
      - private_ip                   = "172.31.28.131" -> null
      - public_dns                   = "ec2-54-185-56-146.us-west-2.compute.amazonaws.com" -> null
      - public_ip                    = "54.185.56.146" -> null
      - security_groups              = [
          - "terraform-20191110052042521000000001",
        ] -> null
      - source_dest_check            = true -> null
      - subnet_id                    = "subnet-3b929b42" -> null
      - tags                         = {
          - "Name" = "my-webserver-instance"
        } -> null
      - tenancy                      = "default" -> null
      - volume_tags                  = {} -> null
      - vpc_security_group_ids       = [
          - "sg-0e1a943b062aa2315",
        ] -> null

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

      - root_block_device {
          - delete_on_termination = false -> null
          - encrypted             = false -> null
          - iops                  = 100 -> null
          - volume_id             = "vol-0f48e9a8f42ac6dc4" -> null
          - volume_size           = 8 -> null
          - volume_type           = "gp2" -> null
        }
    }

  # aws_key_pair.example will be destroyed
  - resource "aws_key_pair" "example" {
      - fingerprint = "45:dd:55:74:bf:a3:07:3f:c7:02:9a:c1:9a:37:bf:40" -> null
      - id          = "example-key" -> null
      - key_name    = "example-key" -> null
      - public_key  = ""
    }

  # aws_security_group.examplesg will be destroyed
  - resource "aws_security_group" "examplesg" {
      - arn                    = "arn:aws:ec2:us-west-2:355622012945:security-group/sg-0e1a943b062aa2315" -> null
      - description            = "Managed by Terraform" -> null
      - egress                 = [] -> null
      - id                     = "sg-0e1a943b062aa2315" -> null
      - ingress                = [
          - {
              - cidr_blocks      = [
                  - "0.0.0.0/0",
                ]
              - description      = ""
              - from_port        = 22
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "tcp"
              - security_groups  = []
              - self             = false
              - to_port          = 22
            },
        ] -> null
      - name                   = "terraform-20191110052042521000000001" -> null
      - owner_id               = "355622012945" -> null
      - revoke_rules_on_delete = false -> null
      - tags                   = {} -> null
      - vpc_id                 = "vpc-79e55a01" -> null
    }

Plan: 0 to add, 0 to change, 3 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_instance.ec2_instance: Destroying... [id=i-0d1ab0d8c1fc57f3d]
aws_instance.ec2_instance: Still destroying... [id=i-0d1ab0d8c1fc57f3d, 10s elapsed]
aws_instance.ec2_instance: Still destroying... [id=i-0d1ab0d8c1fc57f3d, 20s elapsed]
aws_instance.ec2_instance: Still destroying... [id=i-0d1ab0d8c1fc57f3d, 30s elapsed]
aws_instance.ec2_instance: Destruction complete after 30s
aws_key_pair.example: Destroying... [id=example-key]
aws_security_group.examplesg: Destroying... [id=sg-0e1a943b062aa2315]
aws_key_pair.example: Destruction complete after 0s
aws_security_group.examplesg: Destruction complete after 0s

Destroy complete! Resources: 3 destroyed.
  • Looking forward for you guys to join this journey

In addition to that, I am going to host 5 meetups whose aim is to build the below architecture.

  • Meetup: https://www.meetup.com/100daysofdevops
  • Day1(Nov 10): Introduction to Terraform https://www.meetup.com/100daysofdevops/events/266192294/
  • Day 2(Nov 16): Building VPC using Terraform
  • Day 3(Nov 17): Creating EC2 Instance inside this VPC using Terraform
  • Day 4(Nov 23): Adding Application Load Balancer and Auto-Scaling to the EC2 instance created on Day 3
  • Day5(Nov 24): Add Backend MySQL Database and CloudWatch Alarm using Terraform

21 Days of AWS using Terraform

Thanks, everyone who was the part of my earlier journey

100 Days of DevOps

21 Days of Docker

Starting from November 10, I am starting a Program, 21 Days of AWS using Terraform and the main idea behind this is to spend at least one hour of every day for next 21 days in Sharing AWS knowledge using terraform and then share progress via

Looking forward for you guys to join this journey.

In addition to that, I am going to host 5 meetups whose aim is to build the below architecture.

  • Meetup: https://www.meetup.com/100daysofdevops
  • Day1(Nov 10): Introduction to Terraform https://www.meetup.com/100daysofdevops/events/266192294/
  • Day 2(Nov 16): Building VPC using Terraform
  • Day 3(Nov 17): Creating EC2 Instance inside this VPC using Terraform
  • Day 4(Nov 23): Adding Application Load Balancer and Auto-Scaling to the EC2 instance created on Day 3
  • Day5(Nov 24): Add Backend MySQL Database and CloudWatch Alarm using Terraform