This article is part of a series where we will explain Kubernetes, from the basics to the most advanced topics: deploying an application in a production cluster. Before diving into dense material, it’s necessary to understand why Kubernetes is needed. Years ago, most applications were huge monolithic codebases that had a very slow update cycle due to this very architecture.
This article is part of a series where we will explain Kubernetes, from the basics to the most advanced topics: deploying an application in a production cluster. Before diving into dense material, it’s necessary to understand why Kubernetes is needed. Years ago, most applications were huge monolithic codebases that had a very slow update cycle due to this very architecture. I say “years ago” optimistically, but even today, many applications in production are still like this, causing headaches for both developers and Ops teams. When it’s time for an update, many things can go wrong. Redeployment can take hours, and if the hardware where these applications are deployed fails, good luck to everyone involved.
Nowadays, these outdated blocks of code have slowly been broken down into much smaller independent components called microservices. The interesting thing about this new way of developing applications is that, being decoupled from the rest, they can be developed, updated, and scaled individually. This approach opens the door to a much easier, faster, and safer development process, allowing components to be changed quickly and in much shorter cycles depending on business requirements. Migrating to a microservices approach means taking a monolithic application, typically running on a server with a lot of computational capacity, and breaking it into smaller processes that run on usually separate infrastructure and communicate using synchronous protocols like HTTP, through which they expose RESTful APIs.
We still have a problem: if we're talking about a couple of microservices, maintaining this process manually is simpler than with the outdated monolithic architecture. But what happens if we're talking about hundreds of microservices? Doing this process manually is quite complex, and many things can go wrong. We need to automate – which includes auto-scaling these components on servers depending on demand, automatic configuration, monitoring, failure management, and if possible, automatic deployment. This is where Kubernetes comes in to change the game for all of us and make life easier.
Understanding What Kubernetes Is and Why Everyone Loves It
I always like to compare developing and deploying monolithic applications to walking, and using Kubernetes to driving a Tesla. The difference is that stark. This system allows developers to deploy their applications themselves or automatically as frequently as they want, without needing help from the Infrastructure team. That alone is a huge advantage. On the other hand, it helps the Infra team monitor hardware failures, and if hardware fails, the process of migrating those applications to healthy hardware is much easier (because it's automatic). Thus, their work is summarized to overseeing Kubernetes instead of managing the infrastructure and the applications running on it.
You might be wondering how Kubernetes manages to leave people with nothing to do. I also asked myself the same question, and it’s not magic. Kubernetes is an open-source container orchestration platform originally developed by Google, but today it's maintained by the Cloud Native Computing Foundation. Essentially, it abstracts the hardware infrastructure and exposes a huge computational resource where applications can be deployed and run without needing to know the underlying servers. All processes are standardized and uniform. This means that if an application runs in a Kubernetes environment, it should run in any other. For example, if a developer has a controlled (and much smaller, obviously) Kubernetes environment installed on their laptop and their microservices work locally, that same group of microservices should work exactly the same in a larger cluster. So, as a developer, I no longer have the excuse, "oh, but it works on my laptop," because they won’t believe me anymore.
With more companies accepting the Kubernetes model as the best way to run applications, it has slowly become the industry standard for running distributed applications in the cloud, but also on on-premise infrastructure.
Benefits of a Kubernetes-Based Ecosystem
Regardless of how many components are being developed and deployed, one of the biggest problems developers and Ops teams always face is dealing with different environments where applications run. This includes not just differences between development and production environments but also inconsistencies among production machines themselves, their different operating systems, installed libraries, processor architectures, network architectures, among others. In the best-case scenario, infrastructure engineers have to ensure that a single application has everything it needs to run on a server. But what happens if multiple applications need to coexist on the same server? More problems. To reduce the number of issues faced in production, the ideal is to maintain a consistent environment where any application can coexist, and that this environment is maintained in both development and production. Kubernetes solves this problem by involving developers more in the application deployment process, allowing them to understand the problems faced in production and address them early in development, not when the application is already in production. On the other hand, Kubernetes reduces the time between each application release, allowing any application to be included in shorter agile sprints and address issues more effectively.
Kubernetes allows developers and sysadmins to focus on what they do best. Even though both work to achieve the same goal of making software work successfully for clients, their tasks are not the same. Developers prefer to focus on developing new features to improve user experience and make applications increasingly functional, while sysadmins worry about the hardware infrastructure underlying those applications, security, resource usage, and other aspects that are not a priority for developers. Adopting Kubernetes draws a line between these two roles. Sysadmins no longer have to worry about the dependency compatibility of all applications on a server or manually scaling an application, and developers don’t have to turn to another team every time they want to deploy a new feature. This latter reason even leads many companies to rent clusters from Cloud providers like AWS, GCP, or Azure and forget about the infrastructure engineer role because it becomes unnecessary, allowing them to reduce costs.
The Basis of Kubernetes: Containers and Their Magic
A few lines back, I mentioned that Kubernetes orchestrates containers, and the truth is that today everything is a container. So much so that it’s customary to deliver an application with its file defining how to encapsulate it in one of them. Kubernetes uses Linux container technologies to provide application isolation, so before delving into Kubernetes itself, let’s start by highlighting the basics of containers.
Understanding What Containers Are
It’s normal for different software components running on the same host to require different versions of libraries, and conflicts can arise between them. This is why when an application is composed of a small number of large components, it’s normal to deploy each of them in its own VM, thus achieving isolation of each block. Up to this point, this strategy is understandable, but what happens if those components become much smaller and their number grows? Assigning a VM to each component isn’t feasible if you want to avoid wasting resources and keep hardware costs low. Deploying VMs means each one needs to be configured and maintained separately, and you likely need to hire a sysadmin to operate them.
Instead of using virtual machines to isolate the environments of each microservice, Linux containers can be implemented. These allow multiple services to be deployed on the same host machine, not only exposing them to each other but also isolating their dependencies and requirements to achieve ideal functionality – just as would be achieved with VMs but using far fewer resources. It’s important to note that containers are not VMs because the processes running inside them also run on the host machine's operating system, but from the container’s perspective, this process runs isolated within it. In the case of VMs, processes run on separate operating systems – meaning VM processes do not run on the host machine’s operating system.
Let’s compare both elements: unlike VMs, containers are very lightweight, allowing more software components to run on the same hardware. This is mainly because VMs need to run their own processes, requiring more computational resources besides those needed by the software component running inside them. A container, on the other hand, is a simple isolated process running on the host's operating system, consuming the resources needed by the application without overloading the OS with extra processes. The final result is that many more applications can run on the same "hardware":
Behind containers, there is a lot of technical detail, and I would need an entire article to explain them in detail. For now, let's keep in mind that they allow applications to be isolated in a way that consumes very few resources, have everything an application needs to function, allow applications to be abstracted from the underlying infrastructure, but are also volatile units.
In analogy, containers are those shipping containers traveling on a cargo ship across the Atlantic, the ship is the complex hardware underlying the containers, and Kubernetes is the ship's pilot or helmsman – the person steering the ship. In fact, Kubernetes is a Greek word that translates to "pilot" and is pronounced Koo-ber-net-ees.
Kubernetes from a High Level
Kubernetes allows applications to run on thousands of computing nodes as if they were one massive server, simplifying the development, deployment, and maintenance of applications. By standardizing the deployment ecosystem, this system ensures the process is always done the same way, regardless of whether the cluster has a couple of nodes or thousands, as seen in very large companies like Google. The cluster size makes no difference. Additional nodes mean more available resources.
Architecture of a Kubernetes Cluster
At the hardware level, a Kubernetes cluster is generally composed of many nodes, divided into two types:
- The master node: also called the “Kubernetes Control Plane,” which controls the entire Kubernetes system.
- The worker nodes: which run the applications developers deploy.
There are several important components within the nodes:
The Control Plane
It controls the cluster and makes it function. It's composed of several components that can run on a single master node or be distributed across several and replicated to ensure high availability (HA). The components of the control plane are:
- The API server: This is the component users communicate with via commands or tools like Lens, and other cluster components also communicate with it.
- The Scheduler: This component "schedules" applications – essentially assigning an available worker node to an application for deployment.
- The Controller Manager: It performs functions at the cluster level, like replicating components, monitoring nodes, managing cluster failures, etc.
- The etcd: Essentially a distributed data storage system that saves all cluster configurations in a persistent and secure manner, with a database behind this component.
It’s important to note that control plane components do not run applications; they only control functions related to the cluster itself. Application execution is done by worker nodes.
The Worker Nodes
These nodes run applications encapsulated in containers. The components of these nodes are responsible for executing, monitoring, and providing necessary services for the applications. These components are:
- Docker, rkt, containerd, or another container runtime: This component runs containers.
- The Kubelet: Communicates with the control plane's API server and manages containers on its node.
- The Kubernetes Service Proxy or kube-proxy: Balances network traffic within the node and manages communication between application components.
Behind Kubernetes, there are years of development and continuous improvement, and new functionalities are frequently added that would take many more articles to explain in detail. Some of these functionalities are rarely used, while others are part of a developer’s daily life when implementing Kubernetes for application deployment. In the following articles of this series, I will explain this system in much more detail until we put everything into practice and deploy an application in both development and production environments. In the next article, I will discuss the components that accompany applications to coexist within the cluster and mechanisms to expose them to the world. See you there!