How To Setup a AWS EKS Kubernetes Cluster

Follow me on Twitter: @ruanbekker

This will be a tutorial split up in two posts, where I will show you how to provision a EKS Cluster (Elastic Kubernetes Service) on AWS and in the next post, how to deploy a web application to your cluster ( Part2 — Deploy a Web App to EKS.)

And then came EKS

As some of you may know, I’m a massive AWS fan boy, and since AWS released their managed Kubernetes service, I was quite excited to test it out. A couple of months passed and I got the opportunity to test out on-the-job as we moved to Kubernetes.

A couple of moths has passed, and serving multiple production workloads on EKS, and I am really impressed with the service.

Amazon provides a vanilla Kubernetes version, they manage the master nodes and they have a extra component called the cloud controller that runs on the master nodes, which is the aws native component that talks to other aws services (as far as I can recall)

What are we doing today

We will cover this in this post:

  • Deploy a EKS Cluster
  • View the resources to see what was provisioned on AWS
  • Interact with Kubernetes using kubectl
  • Terminate a Node and verify that the ASG replaces the node
  • Scale down your worker nodes
  • Run a pod on your cluster

In the next post we will deploy a web service to our EKS cluster.

Install Pre-Requirements

We require awscli, eksctl and kubectl before we continue. I will be installing this on MacOS, but you can have a look at the following links if you are using a different operating system:

Install awscli:

$ pip install awscli

Install kubectl:

$ brew update 
$ brew install kubernetes-cli

Install eksctl:

$ brew tap weaveworks/tap 
$ brew install weaveworks/tap/eksctl

Deploy EKS

Create a SSH key if you would like to SSH to your worker nodes:

$ ssh-keygen -b 2048 -f ~/.ssh/eks -t rsa -q -N ""

Import your public key to EC2:

$ aws --profile dev --region eu-west-1 ec2 import-key-pair --key-name "eks" --public-key-material file://~/.ssh/

Provision your cluster using eksctl. This will deploy two cloudformation stacks, one for the kubernetes cluster, and one for the node group.

I am creating a kubernetes cluster with 3 nodes of instance type (t2.small) and using version 1.14:

$ eksctl --profile dev --region eu-west-1 create cluster --name my-eks-cluster --version 1.14 --nodes 3 --node-type t2.small --ssh-public-key eks[ℹ]  eksctl version 0.9.0
[ℹ] using region eu-west-1
[ℹ] setting availability zones to [eu-west-1a eu-west-1b eu-west-1c]
[ℹ] subnets for eu-west-1a - public: private:
[ℹ] subnets for eu-west-1b - public: private:
[ℹ] subnets for eu-west-1c - public: private:
[ℹ] nodegroup "ng-f27f560e" will use "ami-059c6874350e63ca9" [AmazonLinux2/1.14]
[ℹ] using Kubernetes version 1.14
[ℹ] creating EKS cluster "my-eks-cluster" in "eu-west-1" region
[ℹ] will create 2 separate CloudFormation stacks for cluster itself and the initial nodegroup
[ℹ] if you encounter any issues, check CloudFormation console or try 'eksctl utils describe-stacks --region=eu-west-1 --cluster=my-eks-cluster'
[ℹ] CloudWatch logging will not be enabled for cluster "my-eks-cluster" in "eu-west-1"
[ℹ] you can enable it with 'eksctl utils update-cluster-logging --region=eu-west-1 --cluster=my-eks-cluster'
[ℹ] Kubernetes API endpoint access will use default of {publicAccess=true, privateAccess=false} for cluster "my-eks-cluster" in "eu-west-1"
[ℹ] 2 sequential tasks: { create cluster control plane "my-eks-cluster", create nodegroup "ng-f27f560e" }
[ℹ] building cluster stack "eksctl-my-eks-cluster-cluster"
[ℹ] deploying stack "eksctl-my-eks-cluster-cluster"
[ℹ] building nodegroup stack "eksctl-my-eks-cluster-nodegroup-ng-f27f560e"
[ℹ] --nodes-min=3 was set automatically for nodegroup ng-f27f560e
[ℹ] --nodes-max=3 was set automatically for nodegroup ng-f27f560e
[ℹ] deploying stack "eksctl-my-eks-cluster-nodegroup-ng-f27f560e"
[+] all EKS cluster resources for "my-eks-cluster" have been created
[+] saved kubeconfig as "/Users/ruan/.kube/config"
[ℹ] adding identity "arn:aws:iam::000000000000:role/eksctl-my-eks-cluster-nodegroup-n-NodeInstanceRole-SNVIW5C3J3SM" to auth ConfigMap
[ℹ] nodegroup "ng-f27f560e" has 0 node(s)
[ℹ] waiting for at least 3 node(s) to become ready in "ng-f27f560e"
[ℹ] nodegroup "ng-f27f560e" has 3 node(s)
[ℹ] node "" is ready
[ℹ] node "" is ready
[ℹ] node "" is ready
[ℹ] kubectl command should work with "/Users/ruan/.kube/config", try 'kubectl get nodes'
[+] EKS cluster "my-eks-cluster" in "eu-west-1" region is ready

Now that our EKS cluster has been provisioned, let’s browse through our AWS Management Console to understand what was provisioned.

View the Provisioned Resources

If we have a look at the Cloudformation stacks, we can see the two stacks that I mentioned previously:

Navigating to our EC2 Instances dashboard, we can see the three worker nodes that we provisioned. Remember that AWS manages the master nodes and we cant see them.

We have a ASG (Auto Scaling Group) associated with our worker nodes, nodegroup. We can make use of autoscaling and also have desired state, so we will test this out later where we will delete a worker node and verify if it gets replaced:

Navigate using Kubectl

Eksctl already applied the kubeconfig to ~/.kube/config, so we can start using kubectl. Let's start by viewing the nodes:

$ kubectl get nodes
NAME STATUS ROLES AGE VERSION Ready <none> 8m50s v1.14.7-eks-1861c5 Ready <none> 8m55s v1.14.7-eks-1861c5 Ready <none> 8m54s v1.14.7-eks-1861c5

Viewing our pods from our kube-system namespace (we dont have any pods in our default namespace at the moment):

$ kubectl get pods --namespace kube-system
aws-node-btfbk 1/1 Running 0 11m
aws-node-c6ktk 1/1 Running 0 11m
aws-node-wf8mc 1/1 Running 0 11m
coredns-759d6fc95f-ljxzf 1/1 Running 0 17m
coredns-759d6fc95f-s6lg6 1/1 Running 0 17m
kube-proxy-db46b 1/1 Running 0 11m
kube-proxy-ft4mc 1/1 Running 0 11m
kube-proxy-s5q2w 1/1 Running 0 11m

And our services from all our namespaces:

$ kubectl get services --all-namespaces
default kubernetes ClusterIP <none> 443/TCP 19m
kube-system kube-dns ClusterIP <none> 53/UDP,53/TCP 19m

Testing the ASG

Let’s view our current nodes in our cluster, then select the first node, delete it and verify if the ASG replaces that node.

First, view the nodes and select one node’s address:

$ kubectl get nodes NAME                                           STATUS   ROLES    AGE   VERSION   Ready    <none>   37m   v1.14.7-eks-1861c5    Ready    <none>   37m   v1.14.7-eks-1861c5    Ready    <none>   37m   v1.14.7-eks-1861c5

Use the awscli to lookup the EC2 instance id, as we will need this id to delete the node:

$ aws --profile dev ec2 describe-instances --query 'Reservations[*].Instances[?PrivateDnsName==``].[InstanceId][]' --output text

Now that we have the EC2 instance id, delete the node:

$ aws --profile dev ec2 terminate-instances --instance-id i-0d016de17a46d51782
"TerminatingInstances": [
"CurrentState": {
"Code": 32,
"Name": "shutting-down"
"InstanceId": "i-0d016de17a46d5178",
"PreviousState": {
"Code": 16,
"Name": "running"

Now that we have deleted the EC2 instance, view the nodes and you will see the node has been terminated:

$ kubectl get nodes
NAME STATUS ROLES AGE VERSION Ready <none> 41m v1.14.7-eks-1861c5 Ready <none> 41m v1.14.7-eks-1861c5

Allow about a minute so that the ASG can replace the node, and when you list again you will see that the ASG replaced the node :

$ kubectl get nodes
NAME STATUS ROLES AGE VERSION Ready <none> 50s v1.14.7-eks-1861c5 Ready <none> 42m v1.14.7-eks-1861c5 Ready <none> 42m v1.14.7-eks-1861c5

Run a Pod

Run a busybox pod on your EKS cluster:

$ kubectl run --rm -it --generator run-pod/v1 my-busybox-pod --image busybox -- /bin/sh

You will be dropped into a shell:

/ $ busybox | head -1
BusyBox v1.31.1 (2019-10-28 18:40:01 UTC) multi-call binary.

And exit the shell:

/ $ exit
Session ended, resume using 'kubectl attach my-busybox-pod -c my-busybox-pod -i -t' command when the pod is running
pod "my-busybox-pod" deleted

Scaling Nodes

While I will not be covering auto-scaling in this post, we can manually scale the worker node count. Let’s scale it down to 1 node.

First we need to get the EKS cluster name:

$ eksctl --profile dev --region eu-west-1 get clusters
my-eks-cluster eu-west-1

Then we need the node group id:

$ eksctl --profile dev --region eu-west-1 get nodegroup --cluster my-eks-cluster
my-eks-cluster ng-f27f560e 2019-11-16T16:55:41Z 3 3 3 t2.small ami-059c6874350e63ca9

Now that we have the node group id, we can scale the node count:

$ eksctl --profile dev --region eu-west-1 scale nodegroup --cluster my-eks-cluster --nodes 1 ng-f27f560e[ℹ]  scaling nodegroup stack "eksctl-my-eks-cluster-nodegroup-ng-f27f560e" in cluster eksctl-my-eks-cluster-cluster
[ℹ] scaling nodegroup, desired capacity from 3 to 1, min size from 3 to 1

Now when we use kubectl to view the nodes, we will see we only have 1 worker node:

$ kubectl get nodes
NAME STATUS ROLES AGE VERSION Ready <none> 73m v1.14.7-eks-1861c5

Clean Up

If you want to follow along deploying a web application to your EKS cluster before we terminate the cluster, have a look at Part 2 — EKS Tutorial before continuing.

Once you are ready to terminate your EKS cluster, you can go ahead and terminate the cluster:

$ eksctl --profile dev --region eu-west-1 delete cluster --name my-eks-cluster[ℹ]  eksctl version 0.9.0
[ℹ] using region eu-west-1
[ℹ] deleting EKS cluster "my-eks-cluster"
[+] kubeconfig has been updated
[ℹ] cleaning up LoadBalancer services
[ℹ] 2 sequential tasks: { delete nodegroup "ng-f27f560e", delete cluster control plane "my-eks-cluster" [async] }
[ℹ] will delete stack "eksctl-my-eks-cluster-nodegroup-ng-f27f560e"
[ℹ] waiting for stack "eksctl-my-eks-cluster-nodegroup-ng-f27f560e" to get deleted
[ℹ] will delete stack "eksctl-my-eks-cluster-cluster"
[+] all cluster resources were deleted

Further Reading on Kubernetes

This is one amazing resource that covers a lot of Kubernetes topics and will help you throughout your EKS journey:

Thank You

Let me know what you think. If you liked my content, feel free to checkout my content on or follow me on twitter at @ruanbekker

Originally published at on November 16, 2019.




DevOps Engineer and Open Source Enthusiast

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Anatomy of a Landing Zone — Part II

Using My Programming Super Powers To Combat Evil

Surviving Fire Adoptable

5 Reasons on Why You Should Use Flexcil Instead of Samsung Notes

Unity Development — Level Design

How does Flux Protocol work?!

Integrating With Stripe- Server Basics

Home DevOps Pipeline: A junior engineer’s tale (4/4)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ruan Bekker

Ruan Bekker

DevOps Engineer and Open Source Enthusiast

More from Medium

Kubernetes Setup On Centos7

Minikube installation | Kubernetes Tutorial

Docker Security and K8 Security Contexts

Kubernetes Architecture Terminology in a 3 min read. Professional Friendly.