Infrastructure as Code: Setting Up EKS and RDS with Terraform in Minutes

4 min read
iacterraformeks
Infrastructure as Code: Setting Up EKS and RDS with Terraform in Minutes

In today’s time of automation managing infrastructure manually is time-consuming and difficult to scale. Terraform, the popular Infrastructure as Code (IaC) tool, allows us to provision, manage, and version your cloud resources declaratively.

In this guide, i’ll walk through building a fully functional EKS (Elastic Kubernetes Service) cluster and a PostgreSQL RDS database on AWS using Terraform.

Why Use Terraform for AWS EKS and RDS?

  • Consistency: Deploy the same environment repeatedly without manual mistakes.
  • Version Control: Keep infrastructure changes in Git alongside your application code.
  • Automation: Provision complex AWS resources like VPCs, EKS, and RDS in a single command.
  • Integration: Easy to integrate with CI/CD pipelines for continuous delivery

Prerequisites

Before we start, make sure you have:

  • An AWS account with sufficient IAM permissions.
  • Terraform installed (v1.6 or higher recommended).
  • kubectl installed for interacting with the EKS cluster.
  • Basic knowledge of AWS, Kubernetes, and PostgreSQL.

Project Structure

Here’s how our Terraform project is organized:

terraform/
├── main.tf          # Provider and shared data sources
├── variables.tf     # All configurable variables
├── outputs.tf       # Outputs for EKS & RDS details
├── eks.tf           # EKS cluster and node group resources
└── rds.tf           # RDS PostgreSQL instance

Step 1: Configure AWS Provider

In main.tf, we define the AWS provider and shared default VPC/subnets:

terraform {
  required_version = ">= 1.6.0"

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

provider "aws" {
  region = var.aws_region
}

# Shared default VPC and subnets for both EKS and RDS
data "aws_vpc" "default" {
  default = true
}

data "aws_subnets" "default" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.default.id]
  }

  filter {
    name   = "availability-zone"
    values = ["us-east-1a", "us-east-1b", "us-east-1c"] # only supported ones
  }
}

Step 2: Define Variables

All configurable parameters are stored in variables.tf:

variable "aws_region" {
  description = "AWS region to deploy resources in"
  type        = string
  default     = "us-east-1"
}

variable "eks_cluster_name" {
  description = "Name of the EKS cluster"
  type        = string
  default     = "expensetracker"
}

variable "rds_instance_identifier" {
  description = "RDS instance name"
  type        = string
  default     = "expense-tracker-db"
}

variable "rds_db_name" {
  description = "Database name in RDS"
  type        = string
  default     = "expense_tracker"
}

variable "rds_username" {
  description = "Master DB username"
  type        = string
  default     = "postgres"
}

variable "rds_password" {
  description = "Master DB password"
  type        = string
  sensitive   = true
}

variable "rds_instance_class" {
  description = "RDS instance type"
  type        = string
  default     = "db.t3.micro"
}

Step 3: Create the EKS Cluster

The eks.tf file provisions:

  • IAM roles for the cluster and node group
  • The EKS cluster
  • A managed node group
# EKS Cluster IAM Role
resource "aws_iam_role" "eks_cluster_role" {
  name = "${var.eks_cluster_name}-cluster-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Effect    = "Allow",
      Principal = {
        Service = "eks.amazonaws.com"
      },
      Action = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "eks_cluster_policy" {
  role       = aws_iam_role.eks_cluster_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
}

# EKS Cluster
resource "aws_eks_cluster" "this" {
  name     = var.eks_cluster_name
  role_arn = aws_iam_role.eks_cluster_role.arn

  vpc_config {
    subnet_ids = data.aws_subnets.default.ids
  }

  depends_on = [aws_iam_role_policy_attachment.eks_cluster_policy]
}

# Node Group IAM Role
resource "aws_iam_role" "eks_node_role" {
  name = "${var.eks_cluster_name}-node-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Effect    = "Allow",
      Principal = {
        Service = "ec2.amazonaws.com"
      },
      Action = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "node_AmazonEKSWorkerNodePolicy" {
  role       = aws_iam_role.eks_node_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
}

resource "aws_iam_role_policy_attachment" "node_AmazonEC2ContainerRegistryReadOnly" {
  role       = aws_iam_role.eks_node_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
}

resource "aws_iam_role_policy_attachment" "node_AmazonEKS_CNI_Policy" {
  role       = aws_iam_role.eks_node_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
}

# Node Group
resource "aws_eks_node_group" "default" {
  cluster_name    = aws_eks_cluster.this.name
  node_group_name = "${var.eks_cluster_name}-node-group"
  node_role_arn   = aws_iam_role.eks_node_role.arn
  subnet_ids      = data.aws_subnets.default.ids

  scaling_config {
    desired_size = 2
    max_size     = 3
    min_size     = 1
  }

  instance_types = ["t2.large"]

  depends_on = [
    aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy,
    aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly,
    aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy
  ]
}

Step 4: Create PostgreSQL RDS

The rds.tf provisions a PostgreSQL database with:

# Security Group for RDS
resource "aws_security_group" "rds_sg" {
  name        = "rds-postgres-sg"
  description = "Allow PostgreSQL access"
  vpc_id      = data.aws_vpc.default.id

  ingress {
    description = "PostgreSQL access"
    from_port   = 5432
    to_port     = 5432
    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"]
  }
}

# Subnet group for RDS (uses the same filtered default-VPC subnets)
resource "aws_db_subnet_group" "default" {
  name       = "default-rds-subnet-group"
  subnet_ids = data.aws_subnets.default.ids
}

# RDS PostgreSQL instance (no engine_version -> let AWS choose a supported one)
resource "aws_db_instance" "this" {
  identifier              = var.rds_instance_identifier
  engine                  = "postgres"
  instance_class          = var.rds_instance_class
  allocated_storage       = 20

  db_name                 = var.rds_db_name
  username                = var.rds_username
  password                = var.rds_password

  publicly_accessible     = true
  skip_final_snapshot     = true
  db_subnet_group_name    = aws_db_subnet_group.default.name
  vpc_security_group_ids  = [aws_security_group.rds_sg.id]

  backup_retention_period = 7
  storage_encrypted       = true
  deletion_protection     = false
}

Step 5: Outputs

Finally, we define outputs for easy access:

output "eks_cluster_endpoint" {
  description = "EKS cluster endpoint"
  value       = aws_eks_cluster.this.endpoint
}

output "eks_cluster_name" {
  description = "EKS cluster name"
  value       = aws_eks_cluster.this.name
}

output "rds_endpoint" {
  description = "RDS PostgreSQL endpoint"
  value       = aws_db_instance.this.endpoint
}

Step 6: Deploy the Infrastructure

Run the following commands:

terraform init
terraform plan -var="rds_password=YourStrongPassword123"
terraform apply -var="rds_password=YourStrongPassword123" -auto-approve
  • Terraform will provision EKS cluster, node group, and RDS instance.
  • Outputs will give the EKS API endpoint and PostgreSQL endpoint for connecting your apps.

terraform 1

terraform 2

terraform 3

Step 7: Connect EKS to RDS

Update your Kubernetes deployment with a secret for DB credentials:

kubectl create secret generic db-credentials \
  --from-literal=username=expensetrackeradmin \
  --from-literal=password=YourStrongPassword123

Reference this secret in your app’s deployment manifest to connect to RDS.

Written by Saurav Karki, DevOps Engineer Trainee at HamroPatro.