Skip to main content

AWS EC2 Static IP: Assign Static Private IPv4 Addresses to EC2 Instances, Terraform Configuration

1058 words·
AWS AWS CLI Terraform EC2
Table of Contents

Prerequisites
#

Install AWS CLI
#

# Update packages
sudo apt update

# Unstall zip tool
sudo apt install unzip -y

# Download AWS CLI zip file
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"

# Unzip
unzip awscliv2.zip

# Install
sudo ./aws/install
# Verify installation / check version
/usr/local/bin/aws --version

Configure AWS CLI
#

# Start AWS CLI configuration
aws configure

Install Terraform
#

# Install the HashiCorp GPG key
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null

# Verify the GPG key fingerprint
gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint

# Add the official HashiCorp repository 
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

# Install Terraform
sudo apt update && sudo apt-get install terraform
# Verify installation / check version
terraform version



Terraform VPC & EC2 Stack
#

File and Folder Structure
#

The file and folder structure of the Terraform project looks like this:

aws-ec2-static-ip
├── ec2_sg.tf
├── outputs.tf
├── terraform.tf
├── variables.tf
└── vpc.tf

Terraform Configuration Files
#

Project Folder & Terraform Provider
#

# Create Terraform project folder
TF_PROJECT_NAME=aws-ec2-static-ip
mkdir $TF_PROJECT_NAME && cd $TF_PROJECT_NAME

  • terraform.tf
# Terraform Provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# Provider AWS Region
provider "aws" {
  alias  = "aws_region"
  region = var.aws_region
}

Variables
#

  • variables.tf
## EC2 Static Private IP Addresses

# VM1
variable "static_ip_1" {
  description = "Static IP for VM1"
  type        = string
  default     = "10.10.0.10"
}

# VM2
variable "static_ip_2" {
  description = "Static IP for VM1"
  type        = string
  default     = "10.10.0.11"
}


## AWS Region & Availability Zones

variable "aws_region" {
  description = "AWS Region"
  type        = string
  default     = "us-east-1"
}

variable "availability_zone_1" {
  description = "The availability zone for the subnet"
  type        = string
  default     = "us-east-1a"
}


## VPC: CIDR Blocks

variable "vpc_cidr" {
  description = "VPC CIDR block"
  type        = string
  default     = "10.10.0.0/16"
}

variable "vpc_subnet_cidr_1" {
  description = "Subnet CIDR block"
  type        = string
  default     = "10.10.0.0/24"
}


## VPC Name

variable "vpc_name" {
  description = "The name of the VPC"
  type        = string
  default     = "Example-VPC"
}


## SSH Key and EC2 Image

# SSH key pair name
variable "key_name" {
  default = "us-east-1-pc-le" # Define key pair name
}

# EC2 Image ID
variable "ami_id" {
  default = "ami-0e2c8caa4b6378d8c" # Define EC2 AMI ID
}

VPC and Subnets
#

  • vpc.tf
# VPC
resource "aws_vpc" "vpc" {
  provider = aws.aws_region
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name       = "${var.vpc_name}"
    Env        = "Production"
  }
}


# Public Subnet "10.10.0.0/24"
resource "aws_subnet" "vpc_subnet_public1" {
  provider = aws.aws_region
  vpc_id                  = aws_vpc.vpc.id
  cidr_block              = var.vpc_subnet_cidr_1
  availability_zone       = var.availability_zone_1
  map_public_ip_on_launch = true

  tags = {
    Name      = "${var.vpc_name} Subnet-Public-1"
    Env       = "Production"
  }
}


# Internet Gateway
resource "aws_internet_gateway" "vpc_igw" {
  provider = aws.aws_region
  vpc_id = aws_vpc.vpc.id

  tags = {
    Name        = "${var.vpc_name} IGW"
    Env         = "Production"
  }
}


# Public Routing Table
resource "aws_route_table" "vpc_public_routetable" {
  provider = aws.aws_region
  vpc_id = aws_vpc.vpc.id

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

  tags = {
    Name        = "${var.vpc_name} Public Route Table"
    Env         = "Production"
  }
}


# Associate Routes with Subnets
resource "aws_route_table_association" "vpc_subnet_public1_ra" {
  provider = aws.aws_region
  subnet_id      = aws_subnet.vpc_subnet_public1.id
  route_table_id = aws_route_table.vpc_public_routetable.id
}

EC2 Instances & Security Group
#

  • ec2_sg.tf
# EC2 Instance 1
resource "aws_instance" "vm1_subnet1" {
  provider = aws.aws_region
  ami                    = var.ami_id
  instance_type          = "t3.small"
  subnet_id              = aws_subnet.vpc_subnet_public1.id
  key_name               = var.key_name
  vpc_security_group_ids = [aws_security_group.ec2_sg.id]
  private_ip             = var.static_ip_1  # Assign static IP

  depends_on = [
    aws_vpc.vpc,
    aws_security_group.ec2_sg
    ]

  tags = {
    Name = "public-subnet-vm-1"
    Env  = "Production"
  }
}

# EC2 Instance 2
resource "aws_instance" "vm2_subnet1" {
  provider = aws.aws_region
  ami                    = var.ami_id
  instance_type          = "t3.small"
  subnet_id              = aws_subnet.vpc_subnet_public1.id
  key_name               = var.key_name
  vpc_security_group_ids = [aws_security_group.ec2_sg.id]
  private_ip             = var.static_ip_2  # Assign static IP

  depends_on = [
    aws_vpc.vpc,
    aws_security_group.ec2_sg
    ]

  tags = {
    Name = "public-subnet-vm-2"
    Env  = "Production"
  }
}


# Security Group for SSH Access and Ping
resource "aws_security_group" "ec2_sg" {
  provider = aws.aws_region
  name        = "SG"
  description = "Security group for SSH access and ping"
  vpc_id      = aws_vpc.vpc.id

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

  ingress {
    description = "Allow ping"
    from_port   = 8
    to_port     = -1
    protocol    = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    description = "Allow all outbound traffic"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  depends_on = [aws_vpc.vpc]

  tags = {
    Name = "SG"
    Env  = "Production"
  }
}

Outputs
#

  • outputs.tf
# EC2 Instance 1 public IP
output "VM1_Public_IP" {
  description = "VM1 Public IP"
  value       = aws_instance.vm1_subnet1.public_ip
}

# EC2 Instance 1 private IP
output "VM1_Private_IP" {
  description = "VM1 Private IP"
  value       = aws_instance.vm1_subnet1.private_ip
}


# EC2 Instance 2 public IP
output "VM2_Public_IP" {
  description = "VM2 Public IP"
  value       = aws_instance.vm2_subnet1.public_ip
}

# EC2 Instance 2 private IP
output "VM2_Private_IP" {
  description = "VM2 Private IP"
  value       = aws_instance.vm2_subnet1.private_ip
}



Configuration Deployment
#

Initialize Terraform Project
#

This will download and install the AWS Terraform provider defined in the terraform.tf file with “hashicorp/aws”, as well as setting up the configuration files in the project directory.

# Initialize the Terraform project
terraform init

Validate Configuration Files
#

# Validates the syntax and structure of Terraform configuration files
terraform validate

# Shell output:
Success! The configuration is valid.

Plan the Deployment
#

# Dry run / preview changes before applying them
terraform plan

Apply the Configuration
#

# Create network stack
terraform apply -auto-approve

# Shell output:
Apply complete! Resources: 8 added, 0 changed, 0 destroyed.

Outputs:

VM1_Private_IP = "10.10.0.10"
VM1_Public_IP = "54.90.168.252"
VM2_Private_IP = "10.10.0.11"
VM2_Public_IP = "3.87.25.171"



Verify Network Connectivity
#

VM1 to VM2
#

# SSH into EC2 instance 1
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@54.90.168.252
# Ping EC2 instance 2 via it's (static) private IP address
ubuntu@ip-10-10-0-10:~$ ping 10.10.0.11

# Shell output:
PING 10.10.0.11 (10.10.0.11) 56(84) bytes of data.
64 bytes from 10.10.0.11: icmp_seq=1 ttl=64 time=0.888 ms
64 bytes from 10.10.0.11: icmp_seq=2 ttl=64 time=0.293 ms

VM2 to VM1
#

# SSH into EC2 instance 2
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@3.87.25.171
# Ping EC2 instance 2 via it's (static) private IP address
ubuntu@ip-10-10-0-11:~$ ping 10.10.0.10

# Shell output:
PING 10.10.0.10 (10.10.0.10) 56(84) bytes of data.
64 bytes from 10.10.0.10: icmp_seq=1 ttl=64 time=0.375 ms
64 bytes from 10.10.0.10: icmp_seq=2 ttl=64 time=0.288 ms