Create a simple EKS cluster
Tags: aws, terraform, networking, eks
Published: 2022-10-21 01:15:03

Amazon Elastic Kubernetes Service (Amazon EKS) is a managed service that you can use to run Kubernetes on AWS without needing to install, operate, and maintain your own Kubernetes control plane or nodes. Kubernetes is an open-source system for automating the deployment, scaling, and management of containerized applications.

In this article I'll help you to create a simple, default cluster without expanding into all of the available options. By using infrastructure as a code tool (IAC) **[HashiCorp Terraform](https://www.terraform.io/)**

**Prerequisites:**
1- Terraform Tool: An IAC tool, use version >= 0.13.1.
2- AWS CLI: A command line tool for working with Amazon EKS.
3- `kubectl`: A command line tool for working with Kubernetes clusters.

I've created the all next steps in my [GitHub repo](https://github.com/Noura98Houssien/simple-EKS-cluster) please follow the Getting started part in the readme to see how to use it.

**Steps to create your first EKS cluster:**
1- Create VPC and subnets that meet [Amazon EKS requirements](https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html)
2- Create Amazon EKS cluster with required IAM role.
3- Create Amazon EKS node group with required IAM role.
4- Run the code 
5- Get into the cluster 
6- Clean up 

So lets start with the first step..

_**1-Create VPC and subnets**_

When you create a cluster, you specify a VPC and at least two subnets that are in different Availability Zones.

_**VPC requirements and considerations:**_
in the [vpc.tf](https://github.com/Noura98Houssien/simple-EKS-cluster/blob/master/vpc.tf) file I called the VPC module created by terraform with the required parameters to create VPC that meets the requirements of EKS cluster creation.

```
locals {
  region = data.aws_region.current.name
}
module "vpc" {
  source                  = "git::https://github.com/terraform-aws-modules/terraform-aws-vpc.git?ref=v3.16.0"
  name                    = var.vpc_name
  cidr                    = var.vpc_cidr
  azs                     = ["${local.region}a", "${local.region}b"]
  public_subnets          = cidrsubnets(var.vpc_cidr, 1, 1)
  enable_dns_hostnames    = true
  enable_dns_support      = true
  map_public_ip_on_launch = true

  tags = {
    Name = var.vpc_name
  }
  public_subnet_tags = {
    Name                                        = "public subnet"
    "kubernetes.io/role/elb"                    = "1"
    "kubernetes.io/cluster/${var.cluster_name}" = "shared"
  }


}
```
* The VPC must have a sufficient number of IP addresses available for the cluster, any nodes, and other Kubernetes resources that you want to create.
* The VPC must have DNS hostname and DNS resolution support. Otherwise, nodes can't register to your cluster.
* The VPC might require VPC endpoints using AWS PrivateLink. we need this in cases if we want to create a private cluster but for simplicity, I'll create a public cluster so there is no need for this now. However, AWS recommends creating a private cluster for the production workload.[read more about public and private cluster]( https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html)

_**Subnets requirements and considerations:**_
* The subnets must have enough available IP addresses to deploy all of your nodes and Kubernetes resources to. AWS recommend at least 16 IP addresses.
* The subnets can be a public or private in our case will create public subnets.
* The public subnet must auto-assign IPv4 because we will deploy the nodes on it.
* If you want to deploy load balancers to a subnet, the subnet must have the following tag:

```
Key	                                Value
kubernetes.io/role/elb	                1
```
* If you use Version 2.1.1 or earlier of the AWS Load Balancer Controller requires the following tag:

```
Key	                                Value
kubernetes.io/cluster/my-cluster	shared
```
at the end of this step, we will have vpc created with 2 public subnets in a different availability zone, public route table and internet gateway.


![eks vpc](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/29sdmyysg8ve19r0jp2j.png)


**_2-Create Amazon EKS cluster with required IAM role_**
* Kubernetes clusters managed by Amazon EKS make calls to other AWS services on your behalf to manage the resources that you use with the service. Before you can create Amazon EKS clusters, you must create an IAM role with the following IAM policies:
 `"arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"`
 `"arn:aws:iam::aws:policy/AmazonEKSVPCResourceController"`
* Create a default cluster (public) and specify the subnet ids to be the public subnet.

and that all in [eks-cluster.tf](https://github.com/Noura98Houssien/simple-EKS-cluster/blob/master/eks-cluster.tf)

```
resource "aws_iam_role" "eksClusterRole" {
  name = "eksClusterRole"

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

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

resource "aws_iam_role_policy_attachment" "AmazonEKSVPCResourceController" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController"
  role       = aws_iam_role.eksClusterRole.name
}

resource "aws_eks_cluster" "this" {
  name     = var.cluster_name
  role_arn = aws_iam_role.eksClusterRole.arn

  vpc_config {
    subnet_ids = module.vpc.public_subnets
  }

  # Ensure that IAM Role permissions are created before and deleted after EKS Cluster handling.
  # Otherwise, EKS will not be able to properly delete EKS managed EC2 infrastructure such as Security Groups.
  depends_on = [
    aws_iam_role_policy_attachment.AmazonEKSClusterPolicy,
    aws_iam_role_policy_attachment.AmazonEKSVPCResourceController,
  ]
}
```

**_3- Create Amazon EKS node group with required IAM role_**
* The Amazon EKS node kubelet daemon makes calls to AWS APIs on your behalf. Nodes receive permissions for these API calls through an IAM instance profile and associated policies. Before you can launch nodes and register them into a cluster, you must create an IAM role for those nodes to use when they are launched,So you must create an IAM role with the following IAM policies:
`"arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"`
`"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"`
`"arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"`
* Create a node group in the public subnet and with it's configration(max, min,and desired size) and instance type.

and all that in [eks-node-group.tf](https://github.com/Noura98Houssien/simple-EKS-cluster/blob/master/eks-node-group.tf)

```
resource "aws_iam_role" "eksWorkerNodeRole" {
  name = "eksWorkerNodeRole"

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

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

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

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

resource "aws_eks_node_group" "this" {
  cluster_name    = var.cluster_name
  node_group_name = "${var.cluster_name}-nodeGroup"
  node_role_arn   = aws_iam_role.eksWorkerNodeRole.arn
  subnet_ids      = module.vpc.public_subnets
  instance_types  = var.instance_types
  scaling_config {
    desired_size = var.desired_size
    max_size     = var.max_size
    min_size     = var.min_size
  }

  update_config {
    max_unavailable = 1
  }

  # Ensure that IAM Role permissions are created before and deleted after EKS Node Group handling.
  # Otherwise, EKS will not be able to properly delete EC2 Instances and Elastic Network Interfaces.
  depends_on = [
    aws_eks_cluster.this,
    aws_iam_role_policy_attachment.AmazonEKSWorkerNodePolicy,
    aws_iam_role_policy_attachment.AmazonEKS_CNI_Policy,
    aws_iam_role_policy_attachment.AmazonEC2ContainerRegistryReadOnly,
  ]
}
```

**_4- Run the code_**

* `git clone https://github.com/Noura98Houssien/simple-EKS-cluster.git`

* `cd simple-EKS-cluster`
* create a file with name terraform.tfvars and copy past the values as in terraform.tfvars.tmpl in example folder.

```
vpc_name       = "my-VPC1"
vpc_cidr       = "10.0.0.0/16"
cluster_name   = "my-EKS1"
desired_size   = 2
max_size       = 2
min_size       = 1
instance_types = ["t3.medium"]
```
* `terraform init`
* `terraform plan`
* `terraform apply`
![terraform apply](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sfxv692bwn1gbwrhl4sp.png)
as you can see there are 17 resource will be added.
and there are some outputs will known after apply.
It takes around 10 mins to create.
after the creation your environment will be like that 


![deployment env](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k3z7biqx93ilqc54wqux.png)


**_5- Get into the cluster_** 
* Update `kubectl` config file by using that command.
 
`aws eks update-kubeconfig --region region-code --name my-cluster`
The example output is as follows.

```
Added new context arn:aws:eks:region-code:111122223333:cluster/my-cluster to /home/username/.kube/config
```

* Confirm communication with your cluster by running the following command. `kubectl get svc`
The example output is as follows.

```
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   172.20.0.1   <none>        443/TCP   15m
```
* Confirm the worker nodes created successfully by running the following command.  `kubectl get nodes`
The example output is as follow.

```
NAME                          STATUS   ROLES    AGE   VERSION
ip-10-0-171-51.ec2.internal   Ready    <none>   12m   v1.23.9-eks-ba74326
ip-10-0-94-160.ec2.internal   Ready    <none>   12m   v1.23.9-eks-ba74326
```
**_6- Clean up_**
* run that command to remove all resources

`terraform destroy`
The example output is as follows.

```
Plan: 0 to add, 0 to change, 17 to destroy.
```

**_summary:_**
In this article, we learned how to create a simple EKS cluster using Terraform, which is a powerful infrastructure as a code tool.
Hopefully, this has added new value to your knowledge.

Our next step is to deploy the application into the cluster and make it accessible through the internet. I have explained the process in detail [here](https://dev.to/aws-builders/create-a-simple-ingress-nginx-controller-on-amazon-eks-cluster-with-full-example-5ei1).