# AWS Terraform Lab

## Overview

This lab will guide you through creating SSH keys, installing Terraform, and provisioning EC2 infrastructure using Infrastructure as Code principles. You’ll work from your existing user instance created in the intro lab to create and manage a new web server instance.

## Prerequisites

- Completed AWS VM Lab
- Access to your existing EC2 user instance via AWS Console via “Instance Connect”
- Know your group identifier

Your group identifier remains “groupXY” For example, if your group is 12 your group identifier is “group12”.

## Lab Objectives

By the end of this lab, you will have:

- Installed Terraform on your user instance
- Configured S3 backend for Terraform state management
- Created a security group using Terraform
- Launched a server EC2 instance using Terraform

## Part 1: Verify S3 Bucket for Terraform Backend

### Step 1.1: Navigate to S3 Console

1. Open the AWS console and navigate to S3

### Step 2.2: Verify the S3 bucket exists

1. Verify that a bucket for your group exists. This bucket was created in a previous lab. The name of the bucket corresponds to your group identifier:

- nsrc-groupXY-data

Example: if your group identifier is 12, the bucket name would be `nsrc-group12-data`

## Part 2: Connect to Your Existing Instance

### Step 2.1: Access Your User Instance

1. Navigate to EC2 Console
2. Click “Instances” in the left navigation
3. Find your group instance named “groupXY-server”. Your group created this instance during the AWS VM lab
4. Select your instance and click “Connect”
5. Choose “EC2 Instance Connect” tab
6. Click “Connect” to open the browser terminal

## Part 3: SSH Key Generation

You will create an SSH key pair (private and public keys), and upload just the public part to AWS EC2 for use in a new VM that you will create.

### Step 3.1: Generate SSH Key Pair

1. Generate a new SSH key pair, with empty passphrase on the private key:

    ssh-keygen -t ed25519 -f ~/.ssh/manager-key -N ""

2. Verify the key files were created:

    ls -la ~/.ssh/

You should see `manager-key` (private key) and `manager-key.pub` (public key).

### Step 4.2: Upload Public Key to AWS

1. Display the public key content:

    cat ~/.ssh/manager-key.pub

It’s one line of text, with three parts: the key type, some binary data, and a key identifier.

2. Upload the public key to AWS EC2:

    aws ec2 import-key-pair --key-name "groupXY-manager" --public-key-material fileb://~/.ssh/manager-key.pub

(for example, the key name might be `group12-manager`)

3. Verify the key pair was uploaded:

    aws ec2 describe-key-pairs --key-names "groupXY-manager"

(You could name the key anything you like. Make sure you verify the name you uploaded.)

## Part 4: Terraform Installation

### Step 4.1: Download and Install Terraform

1. Update the system package index:

    sudo apt update

2. Download the Terraform binary:

    wget https://releases.hashicorp.com/terraform/1.12.2/terraform_1.12.2_linux_amd64.zip

3. Install unzip if not already available:

    sudo apt install -y unzip

4. Unzip the Terraform binary:

    unzip terraform_1.12.2_linux_amd64.zip

5. Move Terraform to system PATH:

    sudo mv terraform /usr/local/bin/

6. Verify Terraform installation:

    terraform version

## Part 5: Terraform Configuration Files

### Step 5.1: Create Terraform Directories

1. Create the terraform directory structure:

    mkdir -p ~/terraform/ec2-instance
    cd ~/terraform/ec2-instance

2. Verify you are in the correct location:

    pwd

You should see: `/home/ubuntu/terraform/ec2-instance`

3. Verify the directory structure:

    ls -la

You should see that this is empty apart from the current directory (`.`) and the parent directory (`..`).

(The `-l` flag gives a long format listing. Names starting with `.` are normally hidden; the `-a` flag causes them to be shown)

### Step 5.2: Create Variables File

Before we start, be aware we will need to find some information to properly fill the variables file values

#### group_identifier: groupXY (remember? your group)

#### region: the AWS region (example: ap-southeast-1 )

#### VPC ID: the ID of the VPC we are working in

#### instance_ami: The image ID (AMI) for the operating system you want to use as the base to build your server

### Create the variables.tf file:

    nano variables.tf

Add the following content:

    variable "aws_region" {
      description = "AWS region for resources"
      type        = string
      default     = "ap-southeast-1"
    }

    variable "group_identifier" {
      description = "Unique identifier for the group"
      type        = string
      default     = "groupXY"   # change to your group identifier
    }

    variable "instance_ami" {
      description = "AMI ID for EC2 instance"
      type        = string
      default     = "ami-08e7e250e7e3deb9b"  # Ubuntu 24.04 LTS for ap-southeast-1
    }

    variable "vpc_id" {
      description = "VPC ID for resources"
      type        = string
      default     = "vpc-066d87efc4131ee00"
    }

    variable "ssh_key" {
      description = "public ssh key"
      type        = string
      default     = "groupXY-manager" # your group ssh key (change)
    }

### Step 5.3: Create Main Configuration File

###### Remember! Your group S3 bucket name needs to be updated, as well as the region. When defining the terraform backend variables are not allowed

Create the main.tf file:

    nano main.tf

Add the following content:

    terraform {
      required_version = ">= 1.0"
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "~> 5.0"
        }
      }

      # variables not allowed in backend block
      backend "s3" {
        bucket = "nsrc-groupXY-data"
        key    = "terraform/ec2-instance/terraform.tfstate"
        region = "ap-southeast-1"
      }
    }

    #we can use variables from now on
    provider "aws" {
      region = var.aws_region
    }

    data "aws_vpc" "selected" {
      id = var.vpc_id
    }

    data "aws_subnets" "selected" {
      filter {
        name   = "vpc-id"
        values = [var.vpc_id]
      }
    }

    data "aws_subnet" "selected" {
      id = data.aws_subnets.selected.ids[0]
    }

    resource "aws_security_group" "terraform" {
      name_prefix = "${var.group_identifier}-terraform"
      vpc_id      = var.vpc_id

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

      ingress {
        description = "HTTPS"
        from_port   = 443
        to_port     = 443
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
      }

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

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

      tags = {
        Name = "${var.group_identifier}-terraform"
      }
    }

    resource "aws_instance" "terraform" {
      ami             = var.instance_ami
      instance_type   = "t2.small"
      key_name        = "${var.ssh_key}"
      subnet_id       = data.aws_subnet.selected.id

      vpc_security_group_ids = [aws_security_group.terraform.id]

      tags = {
        Name = "${var.group_identifier}-terraform"
      }

      ## we will install nginx to show user_data
      ## capabilities
      user_data = <<-EOF
                  #!/bin/bash
                  apt update
                  apt install -y nginx
                  systemctl start nginx
                  systemctl enable nginx
                  echo "<h1>Terraform Server - ${var.group_identifier}</h1>" > /var/www/html/index.html
                  EOF
    }

    output "instance_id" {
      description = "ID of the EC2 instance"
      value       = aws_instance.terraform.id
    }

    output "instance_public_ip" {
      description = "Public IP address of the EC2 instance"
      value       = aws_instance.terraform.public_ip
    }

    output "instance_public_dns" {
      description = "Public DNS name of the EC2 instance"
      value       = aws_instance.terraform.public_dns
    }

## Part 6: Deploy Infrastructure

### Step 6.1: Initialize Terraform

1. Initialize Terraform with the backend configuration:

    terraform init

It has created a hidden directory called `.terraform` and a hidden file `.terraform.lock.hcl`, which you can see if you run `ls -la`

    $ ls -la
    total 24
    drwxrwxr-x 3 ubuntu ubuntu 4096 Aug 12 14:11 .
    drwxrwxr-x 3 ubuntu ubuntu 4096 Aug 12 14:05 ..
    drwxr-xr-x 3 ubuntu ubuntu 4096 Aug 12 14:10 .terraform
    -rw-r--r-- 1 ubuntu ubuntu 1407 Aug 12 14:11 .terraform.lock.hcl
    -rw-rw-r-- 1 ubuntu ubuntu 2472 Aug 12 14:10 main.tf
    -rw-rw-r-- 1 ubuntu ubuntu  685 Aug 12 14:10 variables.tf

### Step 6.2: Plan Infrastructure Changes

1. Review the planned infrastructure changes:

    terraform plan

### Watch carefully!

Terraform is listing the changes it plans to commit. You should always review the output of the plan with the utmost care.

This is the last chance to make sure everything you planned to do.

We call it: the last possible responsible moment!

If you are sure, let’s continue.

### Step 6.3: Apply Configuration

1. Deploy the infrastructure:

    terraform apply

2. Type `yes` when prompted to confirm the deployment.

## Part 7: Test results

### Step 7.1: Verify Deployment

1. Note the output values displayed after successful deployment
2. Verify the instance is running:

    aws ec2 describe-instances --filters "Name=tag:Name,Values=groupXY-terraform" --query 'Reservations[*].Instances[*].[InstanceId,State.Name,PublicIpAddress]' --output table

## Part 8: Terraform data

### Step 8.1: See what Terraform can tell

1. See outputs

    terraform output

- What data can you obtain?
- Go back to the main.tf file, look for ‘output’ definitions

2. See what Terraform knows

    terraform show

- What you see is the “view” Terraform has in its database.
- What do you think would happen if you change something in the AWS Console without using Terraform to change the code and apply?

3. Note that Terraform has created a state file, under prefix `terraform/`, as an object in your S3 bucket. You can check it is there using the AWS console, or at the command line:

    aws s3 ls s3://nsrc-groupXY-data
    aws s3 ls --recursive s3://nsrc-groupXY-data/terraform/

4. From the “terraform show” output, find your instance’s public IP address, and enter that into a browser. If it refuses the connection, it may still be in the process of booting up; try again a minute or two later.

### Good job!

## Part 9 (optional): use Terraform to apply changes

If you have time available, this is an extra step you can do.

Edit your `main.tf` and change the instance type from `t2.small` to `t2.micro`

Check the output of `terraform plan`. It it going to destroy and recreate the instance, or is it going to update it in-place?

To update the instance to match the new desired configuration, use `terraform apply` as before, and watch the output.

Note that this particular change requires restarting your instance, and it will likely get a new IP address.

## Expected Results

After completing this lab, you should have:

- Successfully installed Terraform on your user instance
- Created proper directory structure matching the S3 backend path
- Configured variables.tf with region and identifier variables
- Configured S3 backend for Terraform state management
- Created a security group with appropriate web server rules
- Launched an EC2 instance running nginx web server via terraform
- Tested the deployment
- Learned some Terraform tricks

# Additional information

For this lab, we needed to supply VPC ID and AMI ID to Terraform. Where did these values come from?

You can obtain the VPC ID from the AWS Console, by searching for the “VPC” service, or from the command line:

    aws ec2 describe-vpcs

You can get AMI IDs using the “AMI Catalog” under EC2. Note that different regions have their own AMI IDs.

##### Trick!

You can do a search with the AWS CLI to find an AMI from a given vendor like Canonical (Ubuntu), this is an example:

    aws ec2 describe-images --owners 099720109477 \
      --filters "Name=name,Values=ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*" \
      "Name=state,Values=available" --query 'sort_by(Images, &CreationDate)[-1].ImageId' \
      --region ap-southeast-1

The output would be something like this: `ami-08e7e250e7e3deb9b`

Over time as new versions of the AMI are released, the AMI ID would change. The important part is understanding how to use the AWS CLI to find the information.

These were the pieces of data we had to supply:

- owners 099720109477 - Canonical’s owner ID
- filters - Looks for Ubuntu 24.04 LTS (Noble) server images
- query - Sorts by creation date and gets the most recent one
- region ap-southeast-1 - Specifies Singapore region

It is also possible to do such searches within terraform itself, using “data sources” such as the aws_ami data source.