So you’ve heard about Kubernetes, you know it’s something about clusters, high availability and…deployments?. But every time you hear about a new technology you say to yourself “Oh, here we go again. Another whole pack of documentation pages that will take me ages to read until I can have something working and I don’t even know if it’s worth the effort”. This should be a super-packed doc that will give you a slightly more than basic understanding of the platform, and hopefully give you an idea if it’s the thing for you. You’re just 11 minutes away from it. Here we go.
According to their official website, they say:
“Kubernetes (K8s) is an open-source system for automating deployment, scaling, and management of containerized applications.”
It’s a short yet simple definition. The most common containerisation platform used in Kubernetes is Docker. So this article will start with a simple Docker image, and explain the different moving parts on a Kubernetes cluster that will allow you to deploy and run your application, scale it and manage it.
For starters, if you’ve never heard of the term cluster, just think about it as a group of computers working as a unit, with some processes that coordinate the communication and actions between all these computers, or nodes. The process of managing this and the workloads operating in the cluster is called orchestration (as in Kubernetes is an open-source container-orchestration system for…).
To understand the basics of the architecture, think of it as a matryoshka.
Orchestrating the workload
First of all, your container (the one that hosts and runs your Docker image) will run on a Pod. This is the smallest unit of your workload managed by Kubernetes — think of it as the logical units, the instances, of your application. Each Pod can have one or more containers. The idea is that Pods are disposable and you don’t manage them directly: instead you manage a controller that will be in charge of maintaining a given number of these. These Pods contain an ephemeral storage drive attached to it, that will be destroyed when the Pod is destroyed. How can they be disposable? Because you will have a specification (simply called spec) that will tell Kubernetes exactly how its containers should start, so if one dies, there will be another one just like it ready to be fired up.
Now it’s the part where you say “What the…? Where did all those boxes come from? This was supposed to be simple!”. You may say it to yourself. Or even out loud. And your dog may stare at you not really understanding what’s going on. The Deployment: this is the place where you tell Kubernetes how to run your Pods (what containers they’ll have, which Docker image to use, how many resources to allocate, which configuration variables to pick), how many instances you want to spawn (and how to assess their health), etc. In other words, where you’ll define the spec. On the other hand, the ReplicaSet is the responsible of ensuring that the desired number of Pods (“replicas”) are running at any point in time and restarting/replacing them if necessary. It’ll also have for instance the strategy with which the Pods should be replaced when rolling out a new version (i.e.: at least 50% of the new version should be deployed before terminating the Pods of the old version).
Now you’re going something like: “Seb, this is so far the most beautiful yet simple explanation of Deployments in Kubernetes I’ve ever seen in my life…but I got lost already, how the **** am I supposed to access my API with all these layers? I just want to run a simple curl like in the ol’ days”. Enter the Service. This little fella will be the one exposing your Pods to the “outside”. Why the quotes? Because the “outside” can vary depending if the requests come from within or outside the cluster, and so will the type of Service you’ll use:
- ClusterIP — your service will only be accessible from within the cluster. It will have an allocated IP which you can use to contact your application.
- NodePort — your service will be exposed on a static port on each Node port, accessible outside the cluster.
- LoadBalancer — the cluster requests a Load Balancer to the cloud provider, in the form of a static IP (extra charges might apply here). This means it’s accessible outside the cluster too.
- ExternalName —creates a CNAME record in Kubernetes’ internal DNS (for example
subdomain.domain.com) that will “override” the real DNS record (only within the cluster). For instance, let’s say you’re migrating your workload from your own datacenter and you normally access your DB on
db-01.example.com, and before migrating all of it to Kubernetes, you want to migrate only the test environment so you deploy the DB in your cluster. You don’t need to change your application code: just by creating an ExternalName service all the requests will be properly re-routed to the new instance.
- None — this is called a “headless” service: it’s actually a ClusterIP service type with no clusterIP (if that makes sense?). It’s used in cases where you might need explicitly all the Pod IPs (one classic example is MongoDB, where the connection URLs on the client side requires all the instances IPs).
It creates type A DNS records that will resolve under the service name (if you name your service
headless, when doing
ping headless from any Pod in the cluster, it will resolve one of the Pod IPs).
Just one small detail that for the purpose of simplicity was deliberately omitted because of a hard truth: Services don’t give a crap about your Deployments or ReplicaSets. They just want to know which Pods to route the incoming traffic to.
So for example, the green service will go: “OK, I received this request, who’s got a label
app=green? You Pod number 1? There you go buddy. Next!”.
Ok, to summarise what we have so far:
- Pods — the smallest computing unit on Kubernetes. These are the instances of your application.
- ReplicaSet — the controller responsible of ensuring a certain number of Pods running at any point in time.
- Deployment — the orchestra director, organises all the resources so your Pods can run the way you want them to. This will be what you’re going to mainly be working on.
- Service — a bridge to the “outside world”
Bonus track: Autoscalers
There’s another component called HorizonalPodAutoscaler (HPA) that, based on some configurable metrics, can adjust the number of running Pods on a ReplicaSet to serve the incoming requests properly, and this number can fluctuate between previously defined upper and lower bounds. CPU, network packets per second, HTTP requests are just some examples of the metrics you can mix and match. This autoscaling will just affect Pods, so it needs to be complemented by a Node autoscaling, otherwise, depending on the bounds configured, there might not be enough resources to host them properly and some of them might not be scheduled.
In order to tell Kubernetes how to run our workloads, there are two main configuration objects that allow us to do that:
- ConfigMaps — for general configuration
- Secrets — for sensitive information
There’s not much more to say about this, both are key-value objects that will keep the data you throw at it. The main difference is that a Secret object will have some further considerations (because we’re talking about sensitive information), for example how you can consume it, when these values will be injected in the Pod lifecycle, etc. By default, Kubernetes doesn’t provide an encryption layer for Secrets so it relies on the provider for this (in exchange, for example, GCP provides by default encryption at rest in the Infrastructure layer, and also optionally in the application layer).
Kubernetes has something called ServiceAccount, and it’s a way to represent a (machine) user that will be allowed to execute certain operations in the cluster. You can define as many as you want, but usually the default is enough. This Service account can be modified and, for example, be given permissions to read a private Container Registry (the repository where the Docker images that the Pods need will be pulled from) that requires authentication.
Kubernetes internal architecture considerations
- Described aboved are some logical components of the cluster. Kubernetes has also the concept of Nodes — these in contrast are the physical part, which can be bare metal servers, VMs, etc where the Pods will be actually allocated to and consume resources from.
- These Nodes are organised in NodePools — they allow you to manage different groups of Nodes that might differ in size, resources, etc within the same cluster. So if you wanted to assign certain workloads to a specific set of Nodes (because for instance, they require more memory than the rest), you could do it with these. Here the concepts of Taints and Tolerations apply, to establish Node affinity, but these won’t be covered at this time
- Kubernetes has its own administrative workload, and that needs to run in your Nodes, so take this into account when sizing the Node pools. The cloud provider you use might add its own services, so that’s another
- Kubernetes organises the objects deployed in the cluster in namespaces. These provide another layer of isolation and security, so for instance, it’s possible to restrict access to certain namespaces to some users. By default, all user-generated workload will go to the default namespace, whilst Kubernetes workload belongs to the kube-system namespace.
Perhaps there are a few things that you’ll not be regularly involved with but that are good to know (useful for an interview when they ask you if you know Kubernetes, you might go like “Oh yeah, I regularly call my cluster’s control pane through its API to optimise the size of my NodePools” because “I replaced that 2 for a 3 in that textbox” doesn’t sound as fancy).
Stateless vs stateful
It’s one of the main differentiators in workloads, and it basically responds to this question: Do I need to keep track of the data stored in this particular Pod?.
If the answer is no, then you have a stateless deployment. Usually this is the case of most APIs, where the only data you could be saving in the storage is temporary/can be replicated from a spec (like logs, environment variables, config files, etc).
If the answer is yes, well then you guessed right, it’s stateful. This means that each Pod will have a sticky ID and besides its ephemeral storage, will have attached a persistent storage that will belong to that Pod in particular. Even if Pods can eventually die and be restarted, the new Pod will take the identity of the deceased Pod (and will reuse the same persistent storage).
Actually, in Kubernetes a stateless deployment is called Deployment; while a stateful deployment is called StatefulSet
Why an API?
The Kubernetes API is a REST API, and as any other API, lets you query and modify the components of your cluster. Every operation executed by a user or a process, whether internal or external, will go through this API.
The API receives objects to create/update/delete (yes, Deployments, Pods, Nodes, Secrets, etc are represented as objects) and then communicates with the corresponding actor of the control plane to execute the necessary actions.
As with any other type of objects, these are versioned, allowing evolution in the capabilities of both the API and cluster. These objects are extensible, meaning if you had to, you could create new types on demand.
You can contact the API as with any other REST API (curl, Postman?) or through the CLI, with a tool called kubect1. It operates mainly in two ways: for example, to create a Deployment you could simply run:
kubectl create deploy my-deployment –image=my-app
or in an IaC fashion (Infrastructure as Code):
kubectl create apply -f my-deployment.yaml
The advantage of the latter is that it can be versioned in your source control versioning repository, so it’s easier to track changes as of when who and why they were done.
It might look like more verbose than the first option, but in the long run you don’t need to remember what commands you executed and it’s easily portable to other applications you might have. Said in a different way: favour this method.
Some other commands examples
You can find these on Kubernetes official docs pages, the only reason I’m putting them here is to show how clear they tend to be, so the learning curve here is relatively flat.
Get current running pods
kubectl get pods
Get a Deployment details
kubectl describe deploy my-deployment
Delete a ReplicaSet
kubectl delete rs my-replicaset
The reason why I use the full name of the object and sometimes a shortened is because Kubernetes has shortcuts for most of the objects. Want to know all of them? Just run kubect1 api – resources
To read the full article, click the link below.