GitHub Repository Available
Overview #
Mermaid Flowchart #
graph LR
%% Region 1
subgraph Region2["AWS Region us-east-1"]
%% VPC
subgraph VPC["VPC 10.10.0.0/16"]
%% Region-AZ1
subgraph Subnet1["Subnet / AZ - us-east-1a"]
MountTarget1["EFS Mount Target"]
EC2-1["EC2 Instance 1"]
end
%% Region-AZ2
subgraph Subnet2["Subnet / AZ - us-east-2a"]
MountTarget2["EFS Mount Target"]
EC2-2["EC2 Instance 2"]
end
SG-EFS["SG EFS"] --> MountTarget1
SG-EFS["SG EFS"] --> MountTarget2
SG-EC2["SG EC2"] -->|Allow inbound TCP 2049
from SG EC2| SG-EFS EC2-1 -->|Allow outbound TCP 2049| SG-EC2 EC2-2 -->|Allow outbound TCP 2049| SG-EC2 end EFS["EFS File System"] end %% Connections MountTarget1 -.-> EFS MountTarget2 -.-> EFS %% Dashed Border classDef BoarderDash1 stroke-dasharray:5 5; class VPC,Subnet1,Subnet2 BoarderDash1 %% Node Colors style EFS fill:#d1f7d6,stroke:#333,stroke-width:2px style MountTarget1 fill:#d1f7d6,stroke:#333,stroke-width:2px style MountTarget2 fill:#d1f7d6,stroke:#333,stroke-width:2px style EC2-1 fill:#f7d6d6,stroke:#333,stroke-width:2px style EC2-2 fill:#f7d6d6,stroke:#333,stroke-width:2px
from SG EC2| SG-EFS EC2-1 -->|Allow outbound TCP 2049| SG-EC2 EC2-2 -->|Allow outbound TCP 2049| SG-EC2 end EFS["EFS File System"] end %% Connections MountTarget1 -.-> EFS MountTarget2 -.-> EFS %% Dashed Border classDef BoarderDash1 stroke-dasharray:5 5; class VPC,Subnet1,Subnet2 BoarderDash1 %% Node Colors style EFS fill:#d1f7d6,stroke:#333,stroke-width:2px style MountTarget1 fill:#d1f7d6,stroke:#333,stroke-width:2px style MountTarget2 fill:#d1f7d6,stroke:#333,stroke-width:2px style EC2-1 fill:#f7d6d6,stroke:#333,stroke-width:2px style EC2-2 fill:#f7d6d6,stroke:#333,stroke-width:2px
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 EFS Stack #
File and Folder Structure #
The file and folder structure of the Terraform project looks like this:
aws-efs-ec2
├── ec2.tf
├── efs_filesystem.tf
├── outputs.tf
├── security-groups.tf
├── terraform.tf
├── variables.tf
└── vpc.tf
Terraform Configuration Files #
Project Folder & Terraform Provider #
# Create Terraform project folder
TF_PROJECT_NAME=aws-efs-ec2
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
## 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"
}
variable "availability_zone_2" {
description = "The availability zone for the subnet"
type = string
default = "us-east-1b"
}
## 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"
}
variable "vpc_subnet_cidr_2" {
description = "Subnet CIDR block"
type = string
default = "10.10.1.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"
}
}
# Public Subnet "10.11.0.0/24"
resource "aws_subnet" "vpc_subnet_public2" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc.id
cidr_block = var.vpc_subnet_cidr_2
availability_zone = var.availability_zone_2
map_public_ip_on_launch = true
tags = {
Name = "${var.vpc_name} Subnet-Public-2"
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
}
# Associate Routes with Subnets
resource "aws_route_table_association" "vpc_subnet_public2_ra" {
provider = aws.aws_region
subnet_id = aws_subnet.vpc_subnet_public2.id
route_table_id = aws_route_table.vpc_public_routetable.id
}
Security Groups #
- security-groups.tf
# vpc: 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"
}
}
# Security Group for EFS Mount Targets
resource "aws_security_group" "efs_sg" {
provider = aws.aws_region
name = "EFS-SG"
description = "Security group for EFS access"
vpc_id = aws_vpc.vpc.id
ingress {
description = "Allow NFS traffic from EC2 instances"
from_port = 2049
to_port = 2049
protocol = "tcp"
security_groups = [aws_security_group.ec2_sg.id]
}
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 = "EFS SG"
Env = "Production"
}
}
EFS File System & Mount Targets #
Mount targets must be created in every availability zone (AZ) where the EFS file system needs to be mounted on EC2 instances.
- efs_filesystem.tf
# EFS File System
resource "aws_efs_file_system" "efs_filesystem" {
creation_token = "efs-ec2"
performance_mode = "generalPurpose"
throughput_mode = "elastic"
lifecycle_policy {
transition_to_ia = "AFTER_30_DAYS"
}
depends_on = [aws_vpc.vpc]
tags = {
Name = "EFS for EC2"
}
}
# Mount Target in Subnet 1
resource "aws_efs_mount_target" "efs_mount_target1" {
file_system_id = aws_efs_file_system.efs_filesystem.id
subnet_id = aws_subnet.vpc_subnet_public1.id
security_groups = [aws_security_group.efs_sg.id]
depends_on = [
aws_vpc.vpc,
aws_subnet.vpc_subnet_public1,
aws_security_group.efs_sg,
aws_efs_file_system.efs_filesystem
]
}
# Mount Target in Subnet 2
resource "aws_efs_mount_target" "efs_mount_target2" {
file_system_id = aws_efs_file_system.efs_filesystem.id
subnet_id = aws_subnet.vpc_subnet_public2.id
security_groups = [aws_security_group.efs_sg.id]
depends_on = [
aws_vpc.vpc,
aws_subnet.vpc_subnet_public2,
aws_security_group.efs_sg,
aws_efs_file_system.efs_filesystem
]
}
EC2 Instances #
- ec2.tf
# EC2 Instance in Public Subnet 1
resource "aws_instance" "ec2_public_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]
depends_on = [
aws_vpc.vpc,
aws_security_group.ec2_sg,
aws_efs_file_system.efs_filesystem,
aws_efs_mount_target.efs_mount_target1
]
tags = {
Name = "public-subnet-vm-1"
Env = "Production"
}
}
# EC2 Instance in Public Subnet 2
resource "aws_instance" "ec2_public_subnet2" {
provider = aws.aws_region
ami = var.ami_id
instance_type = "t3.small"
subnet_id = aws_subnet.vpc_subnet_public2.id
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.ec2_sg.id]
depends_on = [
aws_vpc.vpc,
aws_security_group.ec2_sg,
aws_efs_file_system.efs_filesystem,
aws_efs_mount_target.efs_mount_target2
]
tags = {
Name = "public-subnet-vm-2"
Env = "Production"
}
}
Outputs #
- outputs.tf
# EC2 Instance public IP
output "Public_Instance1_Public_IP" {
description = "Public IP address of the EC2 instance in the public subnet"
value = aws_instance.ec2_public_subnet1.public_ip
}
# EC2 Instance public IP
output "Public_Instance2_Public_IP" {
description = "Public IP address of the EC2 instance in the public subnet"
value = aws_instance.ec2_public_subnet2.public_ip
}
# EFS File System ID
output "EFS_File_System_ID" {
value = aws_efs_file_system.efs_filesystem.id
description = "The ID of the EFS file system"
}
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: 14 added, 0 changed, 0 destroyed.
Outputs:
EFS_File_System_ID = "fs-0bfe5e35bcbb57b44"
Public_Instance1_Public_IP = "54.210.85.168"
Public_Instance2_Public_IP = "44.193.7.244"
Verify Resources via Management Console #
EFS Mount Targets #
-
Open the AWS Management Console https://us-east-1.console.aws.amazon.com/efs/home?region=us-east-1#/file-systems/
-
Select the newly created
EFS for EC2
file system -
Select the “Network” tab
-
Verify EFS mount targets are created
Show Attach Instructions #
- Open the “Attach” section from the top right corner
Mount EFS #
SSH Into EC2 Instance #
# SSH into EC2 instance 1
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@54.210.85.168
# SSH into EC2 instance 2
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@44.193.7.244
Install EFS-Utils #
# Install requirements
sudo apt update && sudo apt -y install git binutils rustc cargo pkg-config libssl-dev gettext
# Clone efs-utils git repository
git clone https://github.com/aws/efs-utils && cd efs-utils
# Build & install amazon-efs-utils
./build-deb.sh
sudo apt-get -y install ./build/amazon-efs-utils*deb
# Verify Installation
amazon-efs-mount-watchdog --version
# Shell output
/usr/bin/amazon-efs-mount-watchdog Version: 2.2.0
Mount EFS #
Optional, adapt the AWS region in EFS configuration:
# Open the EFS configuration file
sudo vi /etc/amazon/efs/efs-utils.conf
# Uncomment and define AWS EFS Region
[mount]
region = us-east-1
# Create the mount point directory
sudo mkdir -p /mnt/efs
# Mount EFS file system: Define EFS ID
sudo mount -t efs -o tls fs-0bfe5e35bcbb57b44:/ /mnt/efs
# List mounted filesystems
df -h
# Shell
Filesystem Size Used Avail Use% Mounted on
/dev/root 6.8G 3.5G 3.3G 52% /
tmpfs 956M 0 956M 0% /dev/shm
tmpfs 383M 968K 382M 1% /run
tmpfs 5.0M 0 5.0M 0% /run/lock
efivarfs 128K 3.6K 120K 3% /sys/firmware/efi/efivars
/dev/nvme0n1p16 881M 76M 744M 10% /boot
/dev/nvme0n1p15 105M 6.1M 99M 6% /boot/efi
tmpfs 192M 12K 192M 1% /run/user/1000
127.0.0.1:/ 8.0E 0 8.0E 0% /mnt/efs
Verify EFS Share #
# Create a new file on the EFS share via EC2 instance 1
sudo touch /mnt/efs/file1
# Verify the file from the EC2 instance 2
ls /mnt/efs/
# Shell output:
file1
Links #
# GitHub Repository
https://github.com/jueklu/terraform-aws-efs-ec2
# EFS Utils Installation
https://github.com/aws/efs-utils?tab=readme-ov-file#on-other-linux-distributions