Provisioning a Kubernetes cluster on Amazon EKS— Part I
General concepts about Kubernetes
Microservices Architecture (MA) is already a reality in the production environment of big corporations such as Linkedin, Netflix, Google and, others. In MA, all parts that compose the system are implemented separately, in a self-contained and containerized way. All of the containers that compose an application work together and communicate through some type of message service. This kind of architecture has some advantages in relation to monolithic architecture¹:
1- In MA, repairing and updating can be done separately, without impacting others parts making the release process more agile. Meanwhile, in monolithic, each new alteration or update requires that all the application be rebuilt.
2- In MA, if a given point of application fails, we can do local repair without affecting the other components. In contrast, in monolithic architectures, a single point of failure can compromise the entire application.
3- In MA, we don’t need robust machines to support the entire application. We can scale the infrastructure horizontally and on demand. In contrast, in a monolithic architecture, we scale the infrastructure vertically for more capacity.
I suggest reading the article beginners-guide-to-microservices-explaining-it-to-a-5-year-old by Sidarth Malhotra. It illustrates differences between the architectures in a very creative way.
To make all containers work together in the right way it is necessary to have some kind of container orchestrator/manager. It was from this need that Kubernetes¹ ² was born.
Kubernetes, also known as k8s, is an Opensource container orchestration system that aims to automate the deployment, scaling, and management of applications. It works various mechanisms such as replication, self-healing, key configuration management, resiliency, and more.
Containerization has been a reality in Google’s work routine for more than a decade. Practically all the services we know, Google Maps, Google Search, Gmail, Google Earth and etc, run on a container architecture. Managing the containers that make up each of these services is not, even today, a simple task. Imagine a few years ago. There was no tool of any kind for this purpose. Which is why Google, around the year 2000, started the development of the Borg project, a container-oriented cluster management tool. In very general terms Borg consisted of a macro framework called BorgMaster responsible for managing user requests and processes called Borglets that ran containerized applications distributed in replicas.
Some changes occurred and Borg evolved into Omega but, both initiatives were totally focused on Google’s own needs and architecture and therefore did not serve a broader purpose. Thus the idea was born to develop something more generic, free and that could be used by anyone. It was in 2014 that Google created its newest opensource project, Kubernetes. For the sake of curiosity, Kubernetes is a word of Greek origin used to designate “helmsman pilot”.
At the time other commercial initiatives for container orchestration were available, but nothing really free. The main initiatives were tied to large vendors and associated with long and bureaucratic contracts. After its inception, strong community engagement led to Kubernetes’ very rapid rise. Just over 4 years after its emergence large enterprises were already migrating their containner infrastructure to Kubernetes. In 2016 Google donated Kubernetes to the Cloud Native Computing Foundation which is still its maintainer today.
The Kubernetes architecture
The Kubernetes cluster consists of two types of machines, the kuberntes control plane and the worker nodes. The control plane is responsible for managing the entire kubernetes cluster while the worker nodes are responsible for running the containers and performing the workload. The control plane consists of five elements (See Figure 2):
kube-apiserver : This is the front-end of Kubernetes, responsible for all the communication. It is from here that we interact with the cluster.
etcd : Key-value database that stores data about the state of the kubernetes cluster. Information about the lifecycle of a pod, resources, nodes, etc is persisted in etcd.
kube-scheduler : Manages incoming requests and allocates your application for execution on a given worker. It decides where a given workload will be executed.
kube-controller-manager : Manages all kubernetes controllers such as, replica set, adimission controler, authentication controler, deployments e authorization controler.
cloud-controler-manager : It interacts with the cloud provider and manages shared resources such as load balancers and disk volumes.
Os kubernetes worker nodes are composed by three elements:
kubelet : Component responsible for running pods (we will see later what a pod is) and also mediates message exchanges between kube-apiserver and the runtime container.
kube-proxy : Responsible for communication between Pods of different nodes and between Pods and the internet.
Container runtime : Responsible for starting and ending containers and managing their communications. Commonly Docker containers are used, however, other containerization services can be used.
Elements of Kubernetes
Pod : The most central and important concept in Kubernetes. A Pod is the smallest object in Kubernetes, representing one or more containers, storage volumes and a single IP address. Each container within the Pod will have a common IP and file system and so it is possible to share resources between containers. All the containers in a Pod are on the same worker node. A Pod cannot have containers on more than one worker node.
ReplicaSet : It is a set of Pods and, guarantees the desired state of the application. For example if it is desired that your application has 3 Pods and each of them has 3 replicas, the ReplicaSet works to this happen.
Deployment : Manages the versioning of the application. It is from the deployment that we can roll-back the applications. The rollback is done in an orchestrated way, that is, Pods are finalized to do the versioning without compromising the application’s operation. Each deployment version is registered inside the cluster.
Services : They represent ports and are responsible for exposing the pods for communication. From the service we have access to the application itself. For example, if we want to expose an application to the Internet we must create a Load Balancer service and from it, access to the application through a predefined port.
Namespaces : Namespaces are virtual sub-clusters within the physical cluster. They provide a scope for objects in the cluster. With them we can separate the cluster applications into a smaller and more organized group.
Pods have a lifecycle (See Figure 3). When the pod has been accepted by Kubernetes but the containers have not been created yet, this pod is in the Pending state. When the pod has been allocated to a node and all containers have been created, this pod leaves the pending state to the Running state. Throughout the execution of a Pod it has either finished its processing correctly and then assumes the Succeeded state or some kind of failure in the process has taken it to the Failed state. There is yet another state that a Pod can assume, the Unknown state. This state is a bit rarer to happen and is associated with internal Kubernetes problems. In this case, Kubernetes cannot determine the current state of the container or even communicate with it.
Creating and interacting with a Kubernetes cluster
All the major cloud providers have their own managed services for building a kubernetes cluster. AWS has Elastic Kubernetes Service (EKS), Azure has Azure Kubernetes Service (AKS), and Google has Google Kubernetes Engine (GKE). Although the above solutions comprise the real kubernetes landscape in production, for development, testing, or even study, local solutions have been developed. These are the ones we will talk about⁴. Examples of local solutions are k3s, rke2, Minikube, and k3d.
k3d is a tool supported by SUSE Rancher that greatly simplifies the study of kubernetes. Broadly speaking, k3d performs the installation of a k3s cluster inside a docker container and is therefore more performative than other solutions. The installation process can be done, under Linux, by the command :
wget -q -O - https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
visit the documentation for more details⁵. Once k3d is installed, we create a kuberntes cluster using
k3d cluster create <cluster-name>
That’s it! We have a kubernetes cluster ready to be used and tested. In k3d you can pass flags when creating the cluster. For example, if we want to specify the number of worker nodes to be used we can do :
k3d cluster create --agents <Number_of_worker_Nodes>
For more information about k3d, visit⁵’⁶ .
Now that we have a Kuberntes cluster running on our machine, we can interact with it. We do this through kubectl. The kubectl is the command line interface (CLI) that communicates with the Kuberntes server API. From it we can deploy an application, evaluate how the cluster elements such as Pods and Replica Sets are doing, list Services, Namespaces and much more. Here we will explore some basic kubectl commands. The first command we will look at is
Returns the status of the cluster. Para ter informação dos nodes,
kubectl get nodes
If we had used the agents=2 option when creating the cluster, we would have two worker nodes as shown in the image below.
kubectl get namespaces
The namespaces listed are created by default at cluster creation time. When we deploy an application to our kubernetes cluster, if you don’t specify a namespace, the application will be deployed to the default namespace. As a best practice we always want to create namespaces for each application. As mentioned earlier, this makes it much easier to manage and monitor the cluster. We create namespaces through
kubectl create <Nome_do_namspace>
notice that my-app now appears in the list. It is in this namespace that I want to deploy the my-app application. Let’s evaluate the pods of our my-app application. To do so, just :
kubectl get pods -n <Nome_do_namespace>
As in fact it should be. There is no deployed application in the my-app namespace. For illustration purposes I will deploy NGINX to this namespace.
NOTE: I will not go into the details of the .yaml manifest used for deployment. It will be described in more details in the part two of this article. In this text I will dedicate to the first steps with Kubernetes and how to interact with the cluster.
To get information like, date and time of Pod creation, the image is being used, ip, port, we can do
kubectl describe pod <NAME>
In case there are already deployed applications (we will see how to deploy an application in part 2 of this article) in our cluster we can list them through
kubectl get deployments -n <Namespace_name>
Conclusions and perspectives
In this article we take our first steps with Kubernetes. We started with the origin of the technology with Google, showed how to create a local cluster and finally, how to interact with it. It was an introductory text that will serve as a tool for part II of this text.
In part II, we will use Terraform and EKS to create a cluster on AWS. We will also explore the structure of a .yaml manifest, deploy applications to a cluster on AWS and finally expose the service to the internet. It will be a complete project, very close to what happens in production.
 Cloud Native Devops with Kubernetes: Building, Deploying, and Scaling Modern Applications in the Cloud, Arundel, J. and Domingus, J., O’Reilly Media, Incorporated, (2019)
 Kubernetes in Action, Luksa, M., Manning, (2018).
 Large-scale cluster management at Google with Borg, Proceedings of the European Conference on Computer Systems (EuroSys), ACM, Bordeaux, France (2015)
 We will dedicate a part II of this article to a cloud-based solution. Our purpose here is to give an overview about kubernetes.
 Minikube — https://minikube.sigs.k8s.io/docs/start/
 k3d — https://k3d.io/v5.3.0/