Why is Kubernetes so difficult? And how to learn it fast


So you want to learn Kubernetes but got lost in all the technical details and complicated architecture. It doesn’t need to be that way; with a step-by-step approach, you will start to use Kubernetes in no time and, while getting more familiar with the framework, will be able to go deep and master the concepts.

Although Kubernetes seems daunting with its concepts, configuration using YAML, and distributed system management, remember that it is just a tool intended to assist us in managing our applications more efficiently. By concentrating on one aspect at a time and adopting a systematic learning approach, mastering Kubernetes at your own pace is achievable. Undoubtedly, it can present some difficulty at first, but with an appropriate learning strategy, it’s a challenge you can confidently surpass.

Ok, so, how can you learn it fast, only enough to start working with it, or maybe start a conversation in a job interview to show that you are at least connected with the latest technologies in the market? Continue to read, and you will discover that.

What concepts are fundamental and you need to know?

Containers

Kubernetes is a containers orchestration tool; if you don’t know what containers are, it’s impossible to understand the function of Kubernetes.

If you never heard about containers, imagine it as a virtual machine. A kind of virtualized computer, usually small, and created to run a specific part of the application. It’s like having a new computer only to run your app’s database.

Of course, there are a lot of details on it, and there are significant differences between virtual machines and containers. Still, you don’t need to know these differences to understand Kubernetes operations.

Another critical comment here is that, despite docker being the most popular container tool, it is not a container synonymous. Plenty of other engines, even older than Docker, can create and run containers.

In summary: Containers are small virtualized computers running inside another physical (bare metal) or virtualized computers.

Container examples

Distributed Systems

Here is another critical element to understanding Kubernetes. The K8s only make sense under the prism of distributed systems.
Distributed systems is the term used in the industry to refer to a group of computers, sometimes even separated geographically, that appear as a single and coherent system. These computers are also known as nodes. These nodes communicate with each other (generally via messages) to coordinate the work (computational tasks). This architecture brought so many benefits to the industry that it is the default choice for software companies today.

One can say that Kubernetes automates and manages the containers to work flawlessly under a distributed systems architecture. The main selling points of Kubernetes are related to the advantages provided by this architecture as scalability, redundancy, and fault tolerance.

In summary: Distributed Systems are composed of many computers called nodes. The workload is then distributed among these nodes that are constantly communicating with each other to operate as one giant computer.

Distributed Services

The importance of Kubernetes

As I said before, containers are the way to go for most modern IT companies, but you don’t need to believe in me. In 2020 Gartner predicted that more than 75% of global organizations would be running containerized applications in production.

According to the 2021 annual survey from the Cloud Native Computing Foundation, 96% of organizations use or evaluate Kubernetes services.

2021 annual survey CNCF
2021 annual survey CNCF

This is not a trend, it’s an evolution of technology, and if you want to stay on top of it, you need to start to learn.

Now that you are convinced about the importance of Kubernetes in the market (or need a bit more info), and now that you have already learned about containers and distributed systems and how powerful this combination can be, I bet you start to get an idea why Kubernetes is so powerful. But if not, don’t worry. I promise you will not leave this article without a good basic understanding of Kubernetes.

In large environments, a few services or containers could not be enough to handle all the user requests, and the distributed architecture started becoming more complex to administrate.

The setup, maintenance, and costs of these clusters become well visible. With containerization, we can utilize all the server resources more efficiently and add high availability and scaling to our deployments. Still, the added costs to manage it face some additional challenges.

For example, how do we handle all this traffic? How do we distribute it among the servers in a fair way? What about one or more of the containers going down? The system needs to be smart enough to detect and, even better, fix it without human help.

We also need an intelligent way to share resources through the containers. We need to have standard storage where all containers can access the same data set because when our application runs on the first container, we need to write some data to the storage volume, which should also be updated for all other containers.

These are only a few of the reasons why Kubernetes was created and, for sure, why it is so popular these days. Now you probably will understand a little better the Kubernetes official definition: “A container orchestration framework.”


Connecting the dots

Now let’s connect the dots, get all this theory, and correlate it with the main components of the Kubernetes architecture.

Kubernetes architecture

Of course, Kubernetes is way more than that, but these blocks are the parts that will help you most when you try to understand why something is not working or getting weird errors during deployment. So, let’s pass through it part by part.

First of all, you see in the images the “nodes”. You remember the Distribution Systems explanation and how we can use different computers (physical or virtual) to share the workload and divide the computational tasks.

In the picture, you see 2 nodes, one control plane node, responsible for managing the system and where the management components are installed (we will talk about it in detail soon), and a worker node (or normal node); A node managed by the control plane and that holds our applications.

If you are using Kubernetes in the cloud like AWS, Google, or Azure, you will never even see the control plane. It is part of the “managed” advertised by these companies. But this is the subject of another post.

Before we start with the components, just another important note: You don’t necessarily need to separate the control plane from nodes, although it’s heavily recommended. I used Kubernetes in a production environment, already using only one node that acts as a control plane and worker node. Everything depends on the resources you have on your hands and the goals of the project.

Kubernetes Controller Manager

This component is responsible for maintaining the system working. It ensures everything is running according to the plan. We will talk later about that, but Kubernetes operates in a “descriptive” mode; you tell the system the final stage you want, and the framework will ensure, vial all its components, that the system is always according to your plan. Not trying to go too deep here, but K8s is a self-healing system; it’s always monitoring itself, and if something is different from the plan, it will take action to restore the conditions described in the plan. For example, you asked for 3 containers of your APP. One of the containers crashed and went down. Immediately Kubernetes will detect this condition and spin up another instance of that container to “follow” the plan you created.

It’s easy to understand this mechanism with practice, but now you know what part of the architecture is providing it.

The controller manager is not a single component but a few services running on this same label. Examples of controllers that shipped with Kubernetes today are the Replication Controller, Endpoints Controller, Namespace Controller, and Service Accounts Controller.

Kubernetes Database

Before starting to talk about etcd here, the default database for Kubernetes, I want to add an important note: K8s is extremely flexible, and you can change its components and services by different ones if you want. It’s a way more advanced topic, of course, but is important to understand that it is possible… A quick example here is exactly the database. K3s, one of the lightest distributions of Kubernetes, opens the possibility for you to choose the database you want according to your necessity.
From the lightest and simple SQLite to the traditional etcd, passing through other well-known databases such as PgSQL.
You can see more details directly on the K3s documentation.

As this article is about the basics, I will not enter too much into these details, but bear in mind that these components can be changed without losing the K8s’ core functionality.

Back to the topic, Kubernetes uses this database to store the configuration of the cluster, besides the actual state of the system and the desired state of the system.

There are reasons why etcd was selected as the main database for K8s. I will not enter into too many details to not confuse you now, but I will let here the official IBM definition about etcd:

Etcd is an open source distributed key-value store used to hold and manage the critical information that distributed systems need to keep running. Most notably, it manages the configuration data, state data, and metadata for Kubernetes, the popular container orchestration platform.

From IBM documentation

Kubernetes API Server

Maybe you are not like me, but every time I see the word API, I think of some sort of web interface; maybe it’s just a bug in my brain, but anyway, if you are like me, here is the first important note: it’s not.

The API is the component inside Kubernetes that will receive commands (we will get back to this topic when talk about kubectl) while interacting with the database to read and update the configurations.

That’s not that much to talk about the API component, it’s the part exposed to external clients, a key component inside the architecture for obvious reasons, reliable and robust. It will even scale itself in more instances if needed.

Kubernetes Scheduler

The scheduler is the part responsible for distributing the containers across the nodes.
Remember we talked about descriptive behavior, having a plan, and letting the Kubernetes do whatever is needed to establish a system according to your plan.
This plan can be simple as: “I want 3 instances of my application” or way more complex than that, for example, defining the resources to be used, affinity and anti-affinity specifications, specific conditions and constraints, data locality, and the list goes on.

Again, trying not to enter too many details, your plan can be really complex and with a lot of constraints, and for Kubernetes to know exactly where, how, and when to place the containers, it needs a dedicated service. This is the Kubernetes Scheduler.

In summary: What you need to know about the scheduler at this level of knowledge is that it’s a service responsible for placing the containers on the right node, using the right resources, and following the specification you created.

Kubelet

The kubelet is an agent that runs on each node in the cluster. It is installed inside the worker nodes and is responsible for spinning up, monitoring, and adjusting the containers inside the pods according to the specification.

In order to clarify the responsibilities of this service, I need to go a bit deeper here. It is important because the kubelet is not as intuitive as the API service, for example. Bear with me.

Kubelet’s primary responsibility is ensuring that the specified containers are running within a Pod and that they’re healthy.

The Kubelet performs health checks to ensure the containers in a Pod are up and running as expected. These checks include liveness and readiness probes, and they help manage the state of the containers and, ultimately, the application’s availability.

The Kubelet also manages the system resources for each container. This includes resource limits for things like CPU and memory, which can be specified in the configurations (your plan).

The Kubelet communicates with the container runtime (like Docker or containerd). Maybe it sounds obvious since the kubelet needs to manage the containers, but this information will be useful in the future.

Kubelet is responsible for reporting the status of the node to the control plane. It communicates with the API server to send heartbeats or status updates, providing information about the node’s health and the status of the Pods that it’s running.

While the Kubelet doesn’t actually schedule Pods (that’s the job of the Kubernetes scheduler), it is responsible for registering the node with the control plane and providing information about the resources available on the node. This information will then be used by the scheduler when deciding where to run new Pods.

Kube-proxy

Kube-proxy, as the name suggests, is responsible for network communication inside and outside of your cluster. It runs on each node and helps in forwarding requests to the right pods or services.

Kube-proxy maintains the network rules on nodes. These network rules allow network communication to your pods from network sessions inside or outside of your cluster. It uses the operating system’s packet filtering layer if available, or else it forwards the traffic itself.

It also enables Kubernetes service abstraction by maintaining networking rules for routing traffic to backend Pods. When you create a service in Kubernetes, the kube-proxy creates the appropriate rules to forward traffic to the service’s backend Pods.

A simple network-level load balancing is another feature of this component. By default, it uses a round-robin algorithm to forward traffic to backend Pods. The actual method can be configured per service, and alternatives include “Session Affinity” for routing the traffic from a particular client to the same Pod when possible.

Although not a primary function, kube-proxy does participate in a basic level of health checking. For TCP services, kube-proxy checks for an open TCP socket to determine if the service is healthy. If a Pod is removed or deleted, kube-proxy will also update the network rules to reflect this change.

Pod

A pod is the smallest and simplest unit in the Kubernetes object model that you create or deploy. A Pod represents a running process on your cluster and encapsulates an application container (or, in some cases, multiple containers), storage resources, a unique network IP, and options that govern how the container(s) should run.

Basically, a Pod is a “container” for your app container, and, although it’s technically possible to put more than one container into a pod, most of the real-world solutions you will see are into the 1:1 ratio, one pod holding one container.

For now, and for our goal here, the important part is to understand that Pod is where your application containers are running.

Kubectl

Kubectl is a command line interface (CLI) for running commands against Kubernetes clusters. It’s one of the primary tools used to interact with a Kubernetes cluster, allowing administrators and users to manage and control cluster and application behavior.

It is a binary (installed into your computer) and communicates with your cluster via the API service. Via commands, you can manage applications, perform administrative tasks, and inspect cluster resources.

It includes a wide variety of commands and operations. For example, you can use it to create and delete pods, services, volumes, and replica sets. You can also use it to scale applications, roll out updates, or roll back changes.

You will also use the kubectl to debug purposes, for example using kubectl logs to fetch the logs from a pod or kubectl exec to run a command inside the pod

The app configuration is flexible enough to allow you to use it in different clusters; all you need to do is specify the context configuration and switch between the contexts to connect to different clusters.

It’s also important to note that kubectl is version-specific, meaning the client version must be within one minor version of your Kubernetes API server version. For example, a v1.18 kubectl client should work with v1.17, v1.18, and v1.19 Kubernetes API servers.

Overall, kubectl is a robust, flexible tool that’s fundamental to interacting with a Kubernetes cluster.

The minimal yaml files you need to know to use Kubernetes

This is related to my personal experience and for sure doesn’t cover all the scenarios, but, knowing these files, you will be able to publish most of the applications and have a great start to the practical Kubernetes world. So, let me pass one by one here:

Namespace

Starting with the namespace, which normally is not even used as a file but as a simple command. You will need to understand the concept of namespaces to work with Kubernetes. Also, need to understand that not all Kubernetes objects accept a namespace (but don’t worry about that now).
A namespace is just like that, a separator for a series of common objects you will deploy into your cluster.
The command to execute it would be this: kubectl create namespace [yournamespace]
And the corresponding yaml file would be something like this:

Persistent Volumes and Volume Claims

These yaml files are used to define the storage options of your apps and where your app or database, or service will save the data. These volumes can be inside your cluster, into the network, or even in the cloud (remote). The persistent volume configuration defines exactly what storage you are making available to your cluster.
Unlike ephemeral storage (which is linked to the Pod’s lifecycle), a PV (Persistent Volume) exists independently of any individual Pod that uses the PV. This makes it suitable for storing data that needs to persist beyond the lifecycle of a Pod or that needs to be shared between Pods.

Persistent Volumes can be provisioned either statically by an administrator or dynamically using Storage Classes. When a PV is provisioned, it comes with a certain amount of storage capacity, which can be utilized by Pods via Persistent Volume Claims (PVC).

PVs can be mounted on a Pod in various modes. The three access modes are ReadWriteOnce (the volume can be mounted as read-write by a single node), ReadOnlyMany (the volume can be mounted read-only by many nodes), and ReadWriteMany (the volume can be mounted as read-write by many nodes).

The cluster administrator sets a reclaim policy and determines what happens to a PV when it is released from its claim. The policy can be set to “Retain” (the PV is not deleted and remains with its data), “Delete” (both the PV and its associated storage are deleted), or “Recycle” (the PV’s data is deleted before it’s made available for other PVCs).

PVs have a specific capacity defined when they’re created. However, many PV types support expansion, and the associated PVC can be resized without disrupting the Pod using it.

Secrets Configuration

Kubernetes Secrets are a type of Kubernetes object that is used to store sensitive data, such as passwords, OAuth tokens, and SSH keys. Storing this sort of information in Secret is safer and more flexible than hardcoding it into your application’s code.

Here are some key aspects of Kubernetes Secrets:

  1. Secure Storage: Secrets provide a mechanism to store sensitive data like passwords, tokens, keys, or even a small configuration file. They are stored in tmpfs on the nodes and sent to the kubelet that manages the pods needing the secret, reducing the risk of secret data being written to disk and potentially exposed.
  2. Environment Variables or Files: The secrets can be consumed by pods in two ways: either as environment variables or as files from a volume mounted on one or more of its containers.
  3. Secret Volume: Secrets can be mounted as a data volume, and the containers in the Pod can access the Secret’s data from files. The Secret data is automatically encrypted in etcd, the backend store for Kubernetes.
  4. Size Limit: As of Kubernetes version 1.9, the size of a Secret is limited to 1MB. This limit prevents the misuse of secrets to store large amounts of data which can consume significant amounts of API server and kubelet memory.
  5. Immutable Secrets: Kubernetes supports making Secrets immutable (cannot be updated or deleted) for better safety and performance.
  6. Encryption: Secrets are stored in base64 encoding, not encrypted. However, Kubernetes supports encryption at rest and in transit for Secrets.
  7. Role-Based Access Control (RBAC): You can limit who can access or update a Secret using Kubernetes’ RBAC and policy-based controls.

Despite its name, Secrets are not intended to be a comprehensive security solution for managing sensitive data. For instance, they do not provide strong encryption, and a secret is accessible to anyone who has been granted the appropriate permissions within the cluster. For a more secure solution, you may want to look into more advanced options, such as integrating with a Key Management Service or a secrets management tool.

It’s also important to note that when you create a Secret, the data isn’t automatically encrypted in etcd. If you want to secure your Secrets more thoroughly, you should enable encryption at rest for your Kubernetes cluster.

Deployment Configuration

Into the deployment file, you define the final desirable state of your application/system. Remember we talked about plans before; you decide how you want your application running.
How many resources, how many instances, how do you want to scale, what volumes to use, and so on…

Here are the main configurations you can find in your deployment files:

  1. ReplicaSets: A Deployment manages one or more ReplicaSets. Each ReplicaSet manages a set of identical pods and ensures that a specified number is running at all times.
  2. Updates: Deployments can automatically update the Pods they manage in a controlled fashion. They can gradually roll out changes to a Pod or its configuration or roll them back if something goes wrong.
  3. Pod Template: The Deployment specifies a Pod Template used to create the Pods in the ReplicaSet. The Pod Template defines the container(s) that should be run as part of the Pod and other Pod-level configurations.
  4. Labels and Selectors: Deployments use labels and selectors to determine which Pods belong to which ReplicaSet. This is a key mechanism for identifying and managing the Pods.
  5. Scaling: Deployments allow for easy scaling of the number of Pods. You can manually increase or decrease the number of Pods or use a Horizontal Pod Autoscaler to adjust the number of Pods based on CPU utilization or other metrics.
  6. Pause and Resume: Kubernetes Deployments allow you to pause a Deployment to apply multiple fixes to its PodTemplateSpec and then resume it to start a new rollout.
  7. Status Condition: Deployment status is a representation of the runtime state of the deployment.
  8. Rollback: If a Deployment is not stable, Kubernetes will provide the functionality to roll back to a previous revision.

Here is an example of a deployment file:

Service Configuration

This object is responsible for connecting your containers internally.

A Kubernetes Service is an abstraction that defines a logical set of Pods and a policy by which to access them, sometimes called a micro-service. A selector usually determines the set of Pods targeted by a Service.

Here’s more about what a Kubernetes Service does:

  1. Pod Networking: Each Pod in a Kubernetes cluster has a unique IP, but those IPs are not exposed outside the cluster without a Service. Services allow your applications to communicate with each other and the outside world.
  2. Service Discovery: Kubernetes Services are automatically available via DNS if your cluster is configured with a DNS add-on. This means other Pods in the same cluster can access your service using your assigned name.
  3. Load Balancing: Kubernetes Services have an integrated load-balancer that distributes network traffic to all Pods of an exposed Deployment. Services will continuously monitor the running Pods using endpoints to ensure the traffic is sent only to available Pods.
  4. Service Types: There are four types of Services in Kubernetes – ClusterIP, NodePort, LoadBalancer, and ExternalName. ClusterIP is the default type, exposing the Service on a cluster-internal IP. NodePort exposes the service on each Node’s IP at a static port. LoadBalancer exposes the service externally using a cloud provider’s load balancer. ExternalName maps the service to the contents of the externalName field (e.g., foo.bar.example.com) by returning a CNAME record.

Ingress Configuration

Finally, to connect the external world with your internal services and then your containers, we need another Kubernetes object called Ingress Configuration.

It varies a lot depending on your ingress controller – Nginx reverse proxy, or trafik are well-known examples.

Here are some configurations you may find in the Ingress configuration file

  1. Ingress Controller: An Ingress Controller is responsible for fulfilling the Ingress, usually with a load balancer.
  2. Ingress Rules: Define how incoming HTTP and HTTPS requests are directed to Services. For example, you can route requests to different services based on the requested host or the URL path.
  3. Hostnames and Paths: Ingress allows you to define hostnames and paths that map to specific Services. For example, you could route requests for api.mydomain.com to an API service, and mydomain.com to a web frontend service.
  4. SSL/TLS: You can secure your Ingress by specifying a Secret that contains a TLS private key and certificate. This enables you to handle HTTPS traffic.
  5. Load Balancing: An Ingress effectively provides load-balancing for your services. Depending on the implementation of the Ingress controller, it can provide additional features like SSL termination, path rewrites, or custom HTTP rules.
  6. Service Exposure: Ingress is a way to expose your Services to the outside world from outside the cluster. It is typically used where Services must be accessible from outside the cluster.

Again, It’s important to note that the Ingress object merely provides the routing rules; you must have an Ingress Controller in your cluster to actually implement these rules. Examples of popular Ingress Controllers include NGINX, Traefik, and the Google Kubernetes Engine (GKE) Ingress Controller.

Certificate Files (Optional, maybe?)

I’m pointing it out as maybe optional because technically, you do not need a certificate or make your site connection encrypted (HTTPS) if you don’t want… Although, these days, why would someone put a site in production without HTTPS? It’s basically free and provides a layer of security for your site.

Anyway, if you want to configure this part too, another 2 configurations are needed.

First, you will need a certification provider, it’s easy to install it. I will show you how in the practical article.

Second, you need to configure the entry for each website you are publishing.

You can see below an example of this configuration, using let’s encrypt and cert-manager and storing the certificate as a secret in the cluster:

Conclusion

Well, that’s it, I tried hard not to go too deep and only pass the information you will really need in your day-by-day work with Kubernetes.

I’m sure that with this basic knowledge and starting to practice, you will be able to tame this monster called Kubernetes and slowly advance to more complicated topics, scenarios, and configurations.

It’s a long journey, but this knowledge will give you a huge jump start in your studies, and with good practice, you can even pass into some interviews for DevOps around there.

Fabio Fernandes

Strategy-minded IT transformation leader with extensive experience in Information Technology across several industry sectors. Proven track record of leading digital transformation initiatives, aligning technology services with business goals. Microsoft, AWS, and Scrum Master Certified, with extensive global expertise.

Recent Posts