Getting Started with AWS and Terraform: Deploying a Linux Apache Web Server on AWS EC2 Instance using Terraform
Tags: aws, awscloud, terraform, upskilling
Published: 2024-01-20 07:23:20

In our earlier post, we walked through the process of setting up a beginner's environment on AWS using Terraform. Now, let's dive into the basic services of AWS cloud - network and compute. 
In this post, I will guide you through the steps of launching a simple website on an EC2 Linux instance using Terraform. It will use single AZ, single EC2 server without auto-scaling.

We'll break down the process into manageable components by creating Terraform modules for network and web server.

## Architecture Overview:
Before we get started, let's take a quick look at the architecture we'll be working with:
![Architecture Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/67a9ix7k6v2kjolpuv3w.png)

## Step 1: Creating the VPC and Network Components
The first step is to set up the foundational elements. We'll create a Virtual Private Cloud (VPC) with an Internet Gateway, a public subnet, and a route table. Additionally, we'll set up a security group to manage access to the compute resources.

Variables used throughout the modules are mentioned in variables.tf file.
variables.tf

```terraform
variable "aws_region" {
  type        = string
  description = "AWS region to use for resources."
  default     = "us-east-1"
}

variable "aws_azs" {
  type        = string
  description = "AWS Availability Zones"
  default     = "us-east-1a"
}

variable "enable_dns_hostnames" {
  type        = bool
  description = "Enable DNS hostnames in VPC"
  default     = true
}

variable "vpc_cidr_block" {
  type        = string
  description = "Base CIDR Block for VPC"
  default     = "10.0.0.0/16"
}

variable "vpc_public_subnets_cidr_block" {
  type        = string
  description = "CIDR Block for Public Subnets in VPC"
  default     = "10.0.0.0/24"
}

variable "instance_type" {
  type        = string
  description = "Type for EC2 Instance"
  default     = "t2.micro"
}

variable "instance_key" {
  default = "MyKeyPair"
}
```

main.tf in VPC module
Create VPC
```terraform
# Create the VPC
resource "aws_vpc" "app_vpc" {
  cidr_block           = var.vpc_cidr_block
  enable_dns_hostnames = var.enable_dns_hostnames
}
```
Create internet gateway
```terraform
# Create the internet gateway
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.app_vpc.id
}
```
Create public subnet, route table and association
```terraform
# Create the public subnet
resource "aws_subnet" "public_subnet" {
  vpc_id                  = aws_vpc.app_vpc.id
  cidr_block              = var.vpc_public_subnets_cidr_block
  map_public_ip_on_launch = true
  availability_zone       = var.aws_azs
}

# Create the route table
resource "aws_route_table" "public_rt" {
  vpc_id = aws_vpc.app_vpc.id

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

# Assign the public route table to the public subnet
resource "aws_route_table_association" "public_rt_asso" {
  subnet_id      = aws_subnet.public_subnet.id
  route_table_id = aws_route_table.public_rt.id
}
```
Create security group which allows inbound SSH and HTTP traffic and all outbound traffic. (SSH just to connect and check EC2!)
```terraform
# Create the security group
resource "aws_security_group" "sg" {
  name        = "allow_ssh_http"
  description = "Allow ssh http inbound traffic"
  vpc_id      = aws_vpc.app_vpc.id

  ingress {
    description      = "SSH from VPC"
    from_port        = 22
    to_port          = 22
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  ingress {
    description      = "HTTP from VPC"
    from_port        = 80
    to_port          = 80
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  egress {
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
}
```

## Step 2: Launching an EC2 Instance with Apache Web Service
With the network components in place, we'll proceed to launch an EC2 instance. We'll use the Amazon Linux 2 AMI and configure it to run an Apache web service. We will use userdata to install apache and create an index.html which displays instance metadata.

main.tf of WEB module
```terraform
# Get latest Amazon Linux 2 AMI
data "aws_ami" "amazon-linux-2" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["amzn2-ami-hvm*"]
  }
}

# Create the Linux EC2 Web server
resource "aws_instance" "web" {
  ami             = data.aws_ami.amazon-linux-2.id
  instance_type   = var.instance_type
  key_name        = var.instance_key
  subnet_id       = var.subnet_id
  security_groups = var.security_groups


  user_data = <<-EOF
  #!/bin/bash
  yum update -y
  yum install -y httpd.x86_64
  systemctl start httpd.service
  systemctl enable httpd.service
  instanceId=$(curl http://169.254.169.254/latest/meta-data/instance-id)
  instanceAZ=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone)
  pubHostName=$(curl http://169.254.169.254/latest/meta-data/public-hostname)
  pubIPv4=$(curl http://169.254.169.254/latest/meta-data/public-ipv4)
  privHostName=$(curl http://169.254.169.254/latest/meta-data/local-hostname)
  privIPv4=$(curl http://169.254.169.254/latest/meta-data/local-ipv4)
  
  echo "<font face = "Verdana" size = "5">"                               > /var/www/html/index.html
  echo "<center><h1>AWS Linux VM Deployed with Terraform</h1></center>"   >> /var/www/html/index.html
  echo "<center> <b>EC2 Instance Metadata</b> </center>"                  >> /var/www/html/index.html
  echo "<center> <b>Instance ID:</b> $instanceId </center>"                      >> /var/www/html/index.html
  echo "<center> <b>AWS Availablity Zone:</b> $instanceAZ </center>"             >> /var/www/html/index.html
  echo "<center> <b>Public Hostname:</b> $pubHostName </center>"                 >> /var/www/html/index.html
  echo "<center> <b>Public IPv4:</b> $pubIPv4 </center>"                         >> /var/www/html/index.html
  echo "<center> <b>Private Hostname:</b> $privHostName </center>"               >> /var/www/html/index.html
  echo "<center> <b>Private IPv4:</b> $privIPv4 </center>"                       >> /var/www/html/index.html
  echo "</font>"                                                          >> /var/www/html/index.html
EOF
}
```

## Steps to run Terraform
Now we will see how to run the above created terraform infrastructure automation

```terraform
terraform init
terraform plan
terraform apply -auto-approve
```

Once the terrform apply completed successfully it will show the public ipaddress of the apache server as output

```terraform
Apply complete! Resources: 7 added, 0 changed, 0 destroyed.

Outputs:

instance_id = "i-0d32ce14df3987030"
public_ip = "http://54.165.140.50/"
```

## Running Website
And there you have it – a basic website running on an EC2 Linux instance in AWS, all orchestrated using Terraform. 


![Running Website](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pel5uvr6tkbs308ql0sv.png)


![EC2 Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3cq8ftz1g4uxazi5nacg.png)

## Cleanup
Remember to stop AWS components to avoid large bills.

```terraform
terraform destroy -auto-approve
```


In our next module, we'll take it a step further and explore launching the same website on a Windows Server. Happy coding!

## Resources
GitHub Link: https://github.com/chinmayto/terraform-aws-linux-webserver
EC2 Documentation: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html