🔧 Week 7: Infrastructure as Code

Day 3: Terraform with AWS - EC2 & Security Groups

⏱ Duration: 5 Hours

📚 Learning Objectives

  • Create EC2 instances with Terraform
  • Configure security groups for network access
  • Use data sources to fetch AMI IDs
  • Output useful information like public IPs

📖 Core Concepts (2 Hours)

Data Sources

Data sources allow Terraform to fetch information from AWS that already exists.

# Fetch the latest Amazon Linux 2023 AMI data "aws_ami" "amazon_linux" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["al2023-ami-*-x86_64"] } filter { name = "virtualization-type" values = ["hvm"] } } # Get current AWS region data "aws_region" "current" {} # Get available AZs data "aws_availability_zones" "available" { state = "available" } # Use data sources output "ami_id" { value = data.aws_ami.amazon_linux.id } output "current_region" { value = data.aws_region.current.name }

Security Groups

resource "aws_security_group" "web_sg" { name = "web-server-sg" description = "Security group for web server" # Inbound rule - SSH ingress { description = "SSH from anywhere" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] # In production, restrict this! } # Inbound rule - HTTP ingress { description = "HTTP from anywhere" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } # Inbound rule - HTTPS ingress { description = "HTTPS from anywhere" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } # Outbound rule - Allow all egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "web-server-sg" } }

EC2 Instance

resource "aws_instance" "web" { ami = data.aws_ami.amazon_linux.id instance_type = var.instance_type key_name = var.key_name vpc_security_group_ids = [aws_security_group.web_sg.id] # User data script - runs on first boot user_data = <<-EOF #!/bin/bash yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd echo "

Hello from Terraform!

" > /var/www/html/index.html EOF tags = { Name = "web-server" Environment = var.environment } } # Optional: Elastic IP for static public IP resource "aws_eip" "web_eip" { instance = aws_instance.web.id domain = "vpc" tags = { Name = "web-server-eip" } }

Key Pair for SSH

# Option 1: Reference existing key pair variable "key_name" { description = "Name of existing EC2 key pair" type = string default = "my-key-pair" } # Option 2: Create key pair from public key resource "aws_key_pair" "deployer" { key_name = "deployer-key" public_key = file("~/.ssh/id_rsa.pub") } # Then use in EC2: resource "aws_instance" "web" { # ... key_name = aws_key_pair.deployer.key_name }

Using count for Multiple Instances

variable "instance_count" { default = 3 } resource "aws_instance" "web" { count = var.instance_count ami = data.aws_ami.amazon_linux.id instance_type = "t2.micro" tags = { Name = "web-server-${count.index + 1}" } } # Output all IPs output "instance_ips" { value = aws_instance.web[*].public_ip }

🔬 Hands-on Lab (2.5 Hours)

Lab 1: Complete EC2 Web Server Project

# Create project mkdir -p ~/terraform-labs/lab3-ec2 cd ~/terraform-labs/lab3-ec2 # providers.tf cat > providers.tf << 'EOF' terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = var.region } EOF # variables.tf cat > variables.tf << 'EOF' variable "region" { description = "AWS region" type = string default = "us-east-1" } variable "instance_type" { description = "EC2 instance type" type = string default = "t2.micro" } variable "environment" { description = "Environment name" type = string default = "dev" } variable "project_name" { description = "Project name for tagging" type = string default = "terraform-demo" } EOF # data.tf cat > data.tf << 'EOF' # Get latest Amazon Linux 2023 AMI data "aws_ami" "amazon_linux" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["al2023-ami-*-x86_64"] } filter { name = "virtualization-type" values = ["hvm"] } } # Get default VPC data "aws_vpc" "default" { default = true } EOF # security_group.tf cat > security_group.tf << 'EOF' resource "aws_security_group" "web" { name = "${var.project_name}-web-sg" description = "Security group for web server" vpc_id = data.aws_vpc.default.id ingress { description = "SSH" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "HTTP" from_port = 80 to_port = 80 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.project_name}-web-sg" Environment = var.environment } } EOF # main.tf cat > main.tf << 'EOF' resource "aws_instance" "web" { ami = data.aws_ami.amazon_linux.id instance_type = var.instance_type vpc_security_group_ids = [aws_security_group.web.id] user_data = <<-EOF #!/bin/bash yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd # Create a nice HTML page cat > /var/www/html/index.html << 'HTML' Terraform Demo

🎉 Hello from Terraform!

This EC2 instance was created using Infrastructure as Code

Instance ID: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)

HTML EOF tags = { Name = "${var.project_name}-web-server" Environment = var.environment ManagedBy = "Terraform" } } EOF # outputs.tf cat > outputs.tf << 'EOF' output "instance_id" { description = "ID of the EC2 instance" value = aws_instance.web.id } output "instance_public_ip" { description = "Public IP of the EC2 instance" value = aws_instance.web.public_ip } output "instance_public_dns" { description = "Public DNS of the EC2 instance" value = aws_instance.web.public_dns } output "security_group_id" { description = "ID of the security group" value = aws_security_group.web.id } output "ami_id" { description = "AMI ID used" value = data.aws_ami.amazon_linux.id } output "web_url" { description = "URL to access the web server" value = "http://${aws_instance.web.public_ip}" } EOF

Lab 2: Deploy and Test

# Initialize terraform init # Format and validate terraform fmt terraform validate # Plan terraform plan # Apply terraform apply -auto-approve # Get outputs terraform output terraform output web_url # Test the web server (wait a minute for user_data to complete) curl $(terraform output -raw instance_public_ip) # View in browser echo "Visit: $(terraform output -raw web_url)" # Check EC2 in AWS Console or CLI aws ec2 describe-instances \ --filters "Name=tag:Name,Values=terraform-demo-web-server" \ --query 'Reservations[].Instances[].{ID:InstanceId,IP:PublicIpAddress,State:State.Name}' # IMPORTANT: Destroy to avoid charges! terraform destroy -auto-approve

✅ Day 3 Checklist

  • Can use data sources to fetch AMIs
  • Understand security group configuration
  • Can create EC2 instances with user_data
  • Know how to output useful information
  • Successfully deployed web server with Terraform