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.
Your group identifier remains “groupXY” For example, if your group is 12 your group identifier is “group12”.
By the end of this lab, you will have:
Example: if your group identifier is 12, the bucket name would be
nsrc-group12-data
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.
ssh-keygen -t ed25519 -f ~/.ssh/manager-key -N ""ls -la ~/.ssh/You should see manager-key (private key) and
manager-key.pub (public key).
cat ~/.ssh/manager-key.pubIt’s one line of text, with three parts: the key type, some binary data, and a key identifier.
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)
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.)
sudo apt updatewget https://releases.hashicorp.com/terraform/1.12.2/terraform_1.12.2_linux_amd64.zipsudo apt install -y unzipunzip terraform_1.12.2_linux_amd64.zipsudo mv terraform /usr/local/bin/terraform versionmkdir -p ~/terraform/ec2-instance
cd ~/terraform/ec2-instancepwdYou should see: /home/ubuntu/terraform/ec2-instance
ls -laYou 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)
Before we start, be aware we will need to find some information to properly fill the variables file values
nano variables.tfAdd 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)
}
Create the main.tf file:
nano main.tfAdd 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
}
terraform initIt 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
terraform planTerraform 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.
terraform applyyes when prompted to confirm the deployment.aws ec2 describe-instances --filters "Name=tag:Name,Values=groupXY-terraform" --query 'Reservations[*].Instances[*].[InstanceId,State.Name,PublicIpAddress]' --output tableterraform outputterraform showterraform/, 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/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.
After completing this lab, you should have:
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.
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-1The 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:
It is also possible to do such searches within terraform itself, using “data sources” such as the aws_ami data source.