Setup Kubernetes Cluster with AWS EKS and Terraform

Stefano Monti
AWS Infrastructure
Published in
9 min readApr 1, 2023

Welcome to this tutorial on using Terraform to deploy a cluster on Amazon Web Services’ Elastic Kubernetes Service (EKS). Without having to set up, manage, and scale your own Kubernetes clusters, EKS is a managed Kubernetes service that makes it simple to use Kubernetes on AWS. On the other side, Terraform is a well-liked open-source infrastructure as code technology that enables declarative infrastructure definition and management.

This manual will help you through the process of using Terraform to deploy an EKS cluster on AWS. The procedures required to build up your environment, customize your Terraform code, and deploy your EKS cluster will be covered. By the end of this tutorial, you ought to be able to use Kubernetes management tools and have an operational EKS cluster running on Amazon.

This article will give you the information and resources you need to build an EKS cluster without difficulty, whether you are new to AWS or Terraform or have experience with both. So let’s get going!

Get the code!

You can find the code in this repo.
Let’s see what the code contains:

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}ba
}

This code block in the Terraform setup identifies the necessary provider for the configuration of Terraform. In this instance, HashiCorp’s provider for Amazon Web Services (AWS), with a version close to 4.0, is necessary.

Infrastructure may be built, modified, and versioned effectively and safely with Terraform. In order to communicate with various forms of infrastructure, such as AWS, Google Cloud, or Microsoft Azure, Terraform needs plugins called providers.

Before running any of the infrastructure provisioning code, Terraform will make sure the appropriate provider is installed and properly configured by providing it in this code block. This is crucial because it guarantees that the infrastructure code is executed in a consistent and dependable environment, lowering the possibility of errors and guaranteeing that the infrastructure is constructed according to plan.

provider "aws" {
region = "eu-west-1"
}

The code designates AWS as the service provider and “eu-west-1” as the region, which stands for the EU (Ireland) region. In other words, all resources created with the help of this configuration file will be created in the Amazon EU (Ireland) region.
You can choose the region you want.

data "aws_availability_zones" "available" {
state = "available"
}

This Terraform script defines a data source block that gets details about the Amazon Availability Zones that are accessible in a particular area. The data source is known as “aws availability zones,” and “available” is its alias.

As Terraform has access to the “aws availability zones” data source from the AWS provider, it is able to conduct availability zone queries via the Amazon API. The “status” argument in this example is set to “available,” which means that only the zones that are now available will be returned.

To get a list of availability zones for a particular region, utilize this data source. A collection of strings representing each availability zone is the result of this data source. This list can then be used to construct resources that need to be deployed over several availability zones in other portions of the Terraform configuration.

For instance, you could use this data source to get a list of the available zones and then use a “for each” loop to construct an EC2 instance in each zone if you wanted to establish one in each availability zone in a region.

module "vpc" {
source = "terraform-aws-modules/vpc/aws"

name = "stw-vpc"
cidr = "10.0.0.0/16"

azs = data.aws_availability_zones.available.names
private_subnets = ["10.0.0.0/22", "10.0.4.0/22", "10.0.8.0/22"]
public_subnets = ["10.0.100.0/22", "10.0.104.0/22", "10.0.108.0/22"]

enable_nat_gateway = true
single_nat_gateway = true
enable_vpn_gateway = false

}

Using the terraform-aws-modules/vpc/aws module from the Terraform registry, this Terraform module definition generates an Amazon Web Services (AWS) Virtual Private Cloud (VPC).

A VPC with the name stw-vpc and the Classless Inter-Domain Routing (CIDR) block 10.0.0.0/16 is created by the module. Also, it generates three public subnets with the CIDR blocks 10.0.100.0/22, 10.0.104.0/22, and 10.0.108/22 as well as three private subnets with the CIDR blocks 10.0.0.0/22, 10.0.4.0/22, and 10.0.8.0/22. The AWS Availability Zones in which the subnets should be formed are specified by the azs argument, and a list of available zones may be obtained using data.aws availability zones.available.names.

The module makes it possible to use a single Network Address Translation (NAT) gateway, allowing private subnets to connect to the internet using a single public IP address. Because the parameter enable vpn gateway is set to false, the module does not construct a VPN gateway for this VPC.

The module establishes a VPC with private and public subnets and activates NAT gateway so that private subnets can access the internet. The VPC and subnets that have been built can be used to deploy a variety of AWS resources, including EC2 instances, RDS databases, and more.

If you want to know more about VPCs, I’ve written a post on this argument. You can find it here.

Ok, now you could be a little confused about the subnets created and IPs available in every network. Look at this magic CLI command to list all the available addresses in a subnet:

sipcalc -s /22 10.0.0.0/16

This should be your result:

-[ipv4 : 10.0.0.0/16] - 0

[Split network]
Network - 10.0.0.0 - 10.0.3.255
Network - 10.0.4.0 - 10.0.7.255
Network - 10.0.8.0 - 10.0.11.255
Network - 10.0.12.0 - 10.0.15.255
Network - 10.0.16.0 - 10.0.19.255
Network - 10.0.20.0 - 10.0.23.255
Network - 10.0.24.0 - 10.0.27.255
Network - 10.0.28.0 - 10.0.31.255
Network - 10.0.32.0 - 10.0.35.255
Network - 10.0.36.0 - 10.0.39.255
Network - 10.0.40.0 - 10.0.43.255
.....

You can find more info on sipcalc here

Now it’s time to describe the EKS cluster, but first I calculated some variables inside the ‘locals’ block to get things easier:

locals {
vpc_id = module.vpc.vpc_id
vpc_cidr = module.vpc.vpc_cidr_block
public_subnets_ids = module.vpc.public_subnets
private_subnets_ids = module.vpc.private_subnets
subnets_ids = concat(local.public_subnets_ids, local.private_subnets_ids)
}

There are multiple variable assignments in the “locals” block. In the initial assignment, “vpc id” is set to contain the value of the “vpc id” variable from the “vpc” Terraform module. The second assignment changes “vpc cidr’s” value to that of the module’s “vpc cidr block” variable.

The “public subnets ids” variable is set to the value of the “public subnets” variable from the “vpc” module by the third assignment. The fourth assignment does the same thing by setting the value of “private subnets ids” to the value of the “private subnets” variable from the same module.

The value of “subnets ids” is finally set in the fifth assignment to the concatenation of “public subnets ids” and “private subnets ids.”

Overall, this code creates a local variable called “locals” that refers to different resource IDs and CIDR blocks from a Terraform module called “vpc” and merges the public and private subnet values into a single list.

module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 19.0"

cluster_name = "stw-cluster"
cluster_version = "1.24"

cluster_endpoint_public_access = true

cluster_addons = {
kube-proxy = {
most_recent = true
}
vpc-cni = {
most_recent = true
before_compute = true
service_account_role_arn = module.vpc_cni_irsa.iam_role_arn
configuration_values = jsonencode({
env = {
# Reference docs https://docs.aws.amazon.com/eks/latest/userguide/cni-increase-ip-addresses.html
ENABLE_PREFIX_DELEGATION = "true"
WARM_PREFIX_TARGET = "1"
}
})
}
}

vpc_id = local.vpc_id
subnet_ids = local.subnets_ids
control_plane_subnet_ids = local.private_subnets_ids

# EKS Managed Node Group(s)
eks_managed_node_group_defaults = {
ami_type = "AL2_x86_64"
instance_types = ["t3.medium"]
iam_role_attach_cni_policy = true
}

eks_managed_node_groups = {
stw_node_wg = {
min_size = 2
max_size = 6
desired_size = 2
}
}

}

This Terraform module uses the terraform-aws-modules/eks/aws module to provision an Amazon Elastic Kubernetes Service (EKS) cluster on AWS.

The module’s location is specified by the source argument, and the desired version is specified by the version parameter.

The EKS cluster’s name and the Kubernetes version it should use are specified by the cluster name and cluster version parameters, respectively.

The cluster endpoint public access parameter defines whether or not the cluster’s API server endpoint should be made available to the general public.

Any EKS add-ons that should be deployed with the cluster are specified in the cluster addons block. In this instance, the most recent version of the kube-proxy add-on is being installed, and the vpc-cni add-on is being deployed with particular configuration values, such as prefix delegation enabled and a warm prefix target specified.

Subnet ids specifies the IDs of the subnets within that VPC where the worker nodes will be deployed, and vpc id specifies the ID of the VPC in which the cluster will be deployed.

The IDs of the subnets within that VPC where the control plane will be deployed are specified by the control plane subnet ids setting.

The Amazon Machine Image (AMI) type and instance type default settings are specified in the eks managed node group defaults block for the EKS managed node group configuration.

One or more EKS managed node groups can be specified in the eks managed node groups block before the cluster is established. In this instance, stw node wg, a single node group, is being established with a desired size of 2, a minimum size of 2, and a maximum size of 6.

module "vpc_cni_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.0"

role_name_prefix = "VPC-CNI-IRSA"
attach_vpc_cni_policy = true
vpc_cni_enable_ipv4 = true

oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:aws-node"]
}
}
}

This Terraform module will help you use IRSA (IAM Roles for Service Accounts) and the VPC CNI (Container Network Interface) plugin to create an AWS IAM role for a Kubernetes service account.

The IAM role with the prefix “VPC-CNI-IRSA” is created by the module “terraform-aws-modules/iam/aws/modules/iam-role-for-service-accounts-eks”. Also, the VPC CNI policy, which is necessary for the VPC CNI plugin to work effectively, is attached to the role by the module.

The OpenID Connect (OIDC) provider that will be utilized for authentication is defined in the “oidc providers” block. The “provider arn” element, which is derived from the “oidc provider arn” output of the “eks” module, defines the ARN (Amazon Resource Name) of the OIDC provider. The Kubernetes service account that will be linked to the IAM role is specified by the “namespace service accounts” parameter. In this instance, the role will be linked to the “kube-system:aws-node” service account.

Overall, this module makes it possible for Kubernetes pods to take on IAM responsibilities utilizing IRSA with the VPC CNI plugin, facilitating a safe and smooth interaction between Kubernetes and AWS services.

Time to launch terraform commands!

Let’s begin with:

$ terraform init

Terraform sets up a backend to store the state of your infrastructure when you run terraform init. It also downloads the necessary provider plugins and initializes a number of other parameters.

When you run terraform init, the following processes take place:

  • Installs the required provider plugins that have been downloaded.
  • the backend is set up to store the infrastructure’s current state.
  • the backend is set up to use the chosen backend provider (if applicable).
  • creates the necessary directories and files to configure Terraform’s working directory.
  • determines whether the necessary provider versions are set up and accessible locally.
  • enables any necessary parameters or variables in the Terraform environment.

One of the first tasks you execute when working with a fresh Terraform project or when setting up an old project on a new machine is normally terraform init.

$ terraform plan
$ terraform apply

The terraform apply command is used to actually apply the changes that Terraform will make to your infrastructure, as opposed to using it to preview them.

The terraform plan command can be used to generate a report on the changes that will be made to your infrastructure depending on the current state and the configuration after you have written your Terraform configuration files. This enables you to examine and confirm the alterations that will be made before putting them into effect.

Use the terraform apply command to implement the changes to your infrastructure after you are happy with them. To achieve the intended state, Terraform will add, alter, or destroy resources as necessary.

After a little bit of time you should see the following message on you terminal that indicates that the deploy is complete!

Done!! You can now check on your AWS console that all the resources have been created in the specified region.

That’s all and have fun!

Bye 😘

--

--