How to automate the spin up an AWS EC2 instance as a web server using Terraform

This is my second post in the Terraform series. In the previous post, I have shown you how to automate the creation of the basic services on AWS like VPC, subnet, EC2 using Terraform. If you are a beginner and have not seen that post, then I would strongly recommend checking here first. In this post, we will see how to automate the spin up(creating) an EC2 instance, assign security ports and install a web package as part of bootstrapping scripts inside that making it a webserver. Bootstrapping scripts means the scripts that are executed at the start of the machine.

Prerequisite

  1. An account with AWS – Free tier
  2. Setting up access key and secret key with AWS.
  3. Basic knowledge of AWS EC2 service.

Lets dive in.

Configuring AWS Provider

Provider specifies what infrastructure platform we need to use. As we are using AWS, I mentioned the AWS provider in Terraform as below. To get the access key and secret key, check the post here. Access Key and secret key are necessary login credentials required to log in to our AWS account from Terraform.AWS also needs regions to be mentioned while connecting to the provider. So I have selected the region Asia Pacific Singapore which is ap-southeast-1.

provider "aws" {
   region  = "ap-southeast-1"
   access_key = "XXXXXXXXXX"
   secret_key = "XXXXXXXXX"
}

Initializing

Locals is the keyword used to define the values that are repeatable in our configuration to the main.tf file. I am keeping the bootstrapping script that we need to install the webserver using the locals keyword. Locals are different from variables. These do not change the values during the execution.

Note – Even though we have used the bootstrapping script only once, I have defined this as locals for demonstration purposes.

locals {
  instance-userdata = <<EOF
               #!/bin/bash             
                yum install httpd -y
                echo "Welcome to my home page" > var/www/html/index.html 
                service httpd start
                chkconfig httpd on
EOF
}

Creating Security Groups

Now we will create the security group, defining both the inbound and outbound traffic to the webserver. Since we will be accessing the webserver from the HTTP port, we will enable HTTP in the inbound port. I am also enabling the SSH port so that If I need to login via SSH. Ingress is the keyword used for inbound traffic and egress is the keyword used to define the outbound traffic in the Terraform terminology.

#Setting up Security for EC2 instance

resource "aws_security_group" "sample_instance_port" {
  name        = "sample_instance_port"
  description = "security group"
   

#Inbound Security for EC2 instance #allow web traffic
#Allow http
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
#Allow ssh
 ingress {
    from_port   = 22
   to_port     = 22
  protocol    = "tcp"
  cidr_blocks = ["0.0.0.0/0"]
  }

Generating the Key pair File

Key Pair file is essential to log in to the EC2 instance. Login to AWS.Go to EC2Dashboard->KeyPairs->Create Key Pair. Create a key pair with the .ppk extension since we have created a Linux instance.

Creating the EC2 Instance

Now let us create an EC2 instance with the details below. Note that we are not using ay VPC and subnet here to make it simple.User_data is the section where the required script to install the web package is available. Here, we need to reference the user data using the local which was initialized in the section Initializing above.

resource "aws_instance" "sample_instance" {
  ami           = "ami-03ca998611da0fe12"
  instance_type = "t2.micro"
  key_name = "My_Key_Pair_ppk"
  user_data = "${local.instance-userdata}"
    security_groups= [aws_security_group.sample_instance_port.name]
    tags= {
    Name = "dev_instance"
  }
}

It is time to execute the code. Let us initiate the Terraform using the command INIT

Execution

Now execute the Terraform Plan

Output--
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.sample_instance will be created
  + resource "aws_instance" "sample_instance" {
      + ami                          = "ami-03ca998611da0fe12"
      + 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                     = "My_Key_Pair_ppk"
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + secondary_private_ips        = (known after apply)
      + security_groups              = [
          + "sample_instance_port",
        ]
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "dev_instance"
        }
      + tenancy                      = (known after apply)
      + user_data                    = "b930f477a9441f2af65fedb39a8d665666a710a9"
      + 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)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + enclave_options {
          + enabled = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_security_group.sample_instance_port will be created
  + resource "aws_security_group" "sample_instance_port" {
      + arn                    = (known after apply)
      + description            = "security group"
      + egress                 = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 0
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "-1"
              + security_groups  = []
              + self             = false
              + to_port          = 0
            },
        ]
      + 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
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 80
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 80
            },
        ]
      + name                   = "sample_instance_port"
      + name_prefix            = (known after apply)
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + tags                   = {
          + "Name" = "sample_instance_port"
        }
      + vpc_id                 = (known after apply)
    }

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.

Now execute Terraform APPLY with auto approve option as below. This will skip the step where we need to provide our approval manually by typing yes.

C:\Terraform>terraform apply -auto-approve  
Output--

aws_security_group.sample_instance_port: Creating...
aws_security_group.sample_instance_port: Creation complete after 3s [id=sg-03cf27a5ed0d231de]
aws_instance.sample_instance: Creating...
aws_instance.sample_instance: Still creating... [10s elapsed]
aws_instance.sample_instance: Still creating... [20s elapsed]
aws_instance.sample_instance: Creation complete after 24s [id=i-00031b19d6637be51]

Hurray !! Its done. Lets check our AWS console.

Validation of our steps

As you can see above, one instance is running already. We can also see below that security ports are created.

Now let us try to access the webserver with the Public IP provided above.

Are we done? Nah , just a moment. We need to terminate everything to avoid extra bills from AWS.

Let us destroy all the resources using the Terraform

Execute Terraform destroy

Output --
aws_instance.sample_instance: Destroying... [id=i-00031b19d6637be51]
aws_instance.sample_instance: Still destroying... [id=i-00031b19d6637be51, 10s elapsed]
aws_instance.sample_instance: Still destroying... [id=i-00031b19d6637be51, 20s elapsed]
aws_instance.sample_instance: Still destroying... [id=i-00031b19d6637be51, 30s elapsed]
aws_instance.sample_instance: Still destroying... [id=i-00031b19d6637be51, 40s elapsed]
aws_instance.sample_instance: Destruction complete after 41s
aws_security_group.sample_instance_port: Destroying... [id=sg-03cf27a5ed0d231de]
aws_security_group.sample_instance_port: Destruction complete after 0s

Destroy complete! Resources: 2 destroyed.

Conclusion

In this post, you have learned about how to implement the creation of a webserver from EC2 from Terraform. Thank you for staying till here. If you have any feedback then please comment down below.

Happy Learning!

Leave a Comment