In the development environment, there’s hardly anyone who hasn’t heard about containerization and Docker. Many companies use this technology to “package” their applications, facilitating developers and administrators in deploying them on servers and making them available to clients using Kubernetes or similar orchestration tool.
The next generation of containerization technology involves systems for orchestrating and managing services. This directly stems from the popular microservices architecture, which is an evolution of the Service-Oriented Architecture (SOA) and shifts in organizing applications installed on servers towards virtual machines and now containerization.
The most popular project, found in the vast majority of projects, is Kubernetes.
Other notable projects include Docker Swarm, Apache Mesos (with Mesosphere Marathon), CoreOS Fleet, Amazon Elastic Container Service (ECS, and Fargate), Cloud Foundry Diego, Azure Container Service, just to name a few.
The project in question was created by Google and is currently developed under the auspices of the Cloud Native Computing Foundation. The first versions of the engine were written in C++ and later rewritten in Go. Kubernetes version 1.0 was released on July 21, 2015, and since then, it has been actively developed by the community.
Service orchestration
Orchestration involves controlling the entire lifecycle of a service, in our case, containers. The lifecycle includes service creation, changes, monitoring, and removal.
For containers, this involves aspects such as:
- Configuring dependencies between services (e.g., service X must be able to access the database service)
- Monitoring both services and servers
- Allocating resources (disk, CPU time, memory)
- Deployment and change management
- Service availability within containers (health check)
- External access to services
- Scaling
- Service discovery, i.e., detecting services in the cluster and connecting them to a load balancer
- Migrating containers between hosts in the cluster in case one of the servers fails
Fundamental Concepts
All resources (objects) in Kubernetes are defined using YAML files. We don’t get into great detail about Kubernetes internals at this time, but let’s quickly see the most basic objects we can create to operate any application.
Pod
A Pod is the basic organizational unit, consisting of one or several containers. It can also have an attached shared virtual disk.
Pods are managed by the Kubernetes cluster and can be dynamically started and stopped depending on the available resources in the cluster. Each Pod is assigned an IP address within the cluster, which can also change dynamically.
When defining a new Pod, we typically use objects like Service, Controller, Deployment, or Jobs. However, it is possible to define a single Pod.
Example:
apiVersion: v1
kind: Pod
metadata:
name: one-pod
labels:
app: one-pod
spec:
containers:
- name: one-pod-container
image: ubuntu:24.04
command: ['/bin/bash', '-c', 'while true; do echo "Hello from DevOpsFury"; sleep 360; done']
This YAML file will create a Pod named “one-pod” with a single container that prints “Hello IT Professional”.
Each Pod or set of Pods can additionally have various health checks defined. This could be an HTTP query, a standard TCP connection test, or even a command executed inside the container.
It’s important to remember that after creating a Pod, there is no link between the containers and the above definition. If a container stops working, Kubernetes (or rather, the Controller) will not try to restart it – the container will simply be removed.
Deployment
To ensure that our Pod is always available, we can use a Deployment object. This object typically contains the definition of our Pod and additional information like how many containers of a given type we want to run, or the port on which the application listens.
By defining our service this way, we have more control over how many containers should be running or whether to update the image from which the container will start.
Example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-hello-deployment
spec:
selector:
matchLabels:
app: nginx-hello-service
replicas: 3
template:
metadata:
labels:
app: nginx-hello-service
spec:
containers:
- name: nginx-hello
image: nginxdemos/hello:0.3
ports:
- containerPort: 80
This example defines a Deployment named “nginx-hello-deployment” and a Pod labeled “nginx-hello-service”, consisting of three containers (“replicas: 3”) named “nginx-hello”. We used the image “nginxdemos/hello” version 0.3 and declared that the service will open port 80.
After creating this object in the cluster, we should find two functioning containers.
It’s important to note that updating the number of replicas in a Deployment will not add or remove containers in the Pod. The only way to apply changes in a Deployment is to change the image or its version.
Replication Controller
The Replication Controller is a service that ensures our Pods are always available in the number we’ve defined. It deletes containers if there are too many and starts new ones if there are too few.
This also applies if a Pod stops working or is manually deleted.
While you can still manually create a Replication Controller, it is not recommended, and the documentation suggests using a Deployment object instead.
Services
Now that we have defined a Deployment, or several such objects, we can group them into a unit called a Service. A Service can consist of one or several sets of Pods.
To specify which Pods should be included, we use labels we previously defined in the Deployment object. Selectors (not always required) are used for this configuration to grant access to external services (e.g., a database or service in another cluster).
Example:
apiVersion: v1
kind: Service
metadata:
name: nginx-hello-service
spec:
selector:
app: nginx-hello-service
ports:
- protocol: "TCP"
port: 80
targetPort: 80
Using the previously defined Deployment object, this Service will only be available to other services within the cluster.
Persistent Volumes
If we need to persistently store data for reuse by other containers, we can create volumes that will be mounted to a specific directory in the container. This could be a database data disk or simply static files that we want to serve using a service.
Example definition:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sample-pv-claim
labels:
app: nginx-hello-service
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
This definition of a PersistentVolumeClaim (PVC) in Kubernetes is a request for storage by a user. It specifies characteristics of the storage required for a particular application or service.
Let’s break down the components of this PVC definition to understand the options being set:
- apiVersion: v1: This specifies the version of the Kubernetes API you’re using to create this object. For PVCs, v1 is the API version.
- kind: PersistentVolumeClaim: This indicates the kind of Kubernetes object you are defining. In this case, it’s a PersistentVolumeClaim, which is used to request physical storage in the Kubernetes cluster.
- metadata: This section contains metadata about the PVC.
- name: sample-pv-claim: This is the name of the PersistentVolumeClaim. It’s how you will refer to this PVC in Kubernetes commands and in your deployment configurations.
- labels: Labels are key/value pairs that are used to organize and select subsets of objects.
- app: nginx-hello-service: This label indicates that the PVC is associated with the “nginx-hello-service”. Labels can be used to organize and to select objects and groups of objects.
- spec: This section contains the specification of the PVC.
- accessModes: This field specifies the access modes required by the PVC. Access modes include how the volume can be mounted and used.
- ReadWriteOnce: This mode means that the volume can be mounted as read-write by a single node. This is a common mode used for many applications that need persistent storage.
- resources: This section specifies the resources required by the PVC.
- requests: This field specifies the minimum amount of resources required.
- storage: 2Gi: Here, the PVC is requesting a minimum of 2 GiB (Gibibytes) of storage. This is the amount of disk space that will be allocated for the PVC and can be used by the application or service associated with the PVC.
Jobs
Besides regular services that open ports and allow network traffic, there are special services – Jobs. These units create one or more Pods intended to run for a certain period and successfully complete (the last script run in the container must return a 0 status to the system).
Completed jobs cannot be resumed, and we will have to run them again manually.
A special version of Jobs is CronJobs, which we can configure to run at a specific time or at regular intervals, for example, every 10 minutes.
Example:
apiVersion: batch/v1
kind: Job
metadata:
name: pi-with-timeout
spec:
backoffLimit: 5
activeDeadlineSeconds: 100
template:
spec:
containers:
- name: one-pod-container
image: ubuntu:24.04
command: ['/bin/bash', '-c', 'echo "Hello from DevOpsFury"; sleep 360']
restartPolicy: Never
In the example above, we also introduce a maximum number of restarts if the job fails (backoffLimit) and a maximum time in which the job must finish (activeDeadlineSeconds). The command “sleep 360” in the container command specification, will cause the job will stop working after 100 seconds and restarted. It will fail after five attempts as we’ve configured in backoffLimit
setting.
DaemonSet
The last important object often used is DaemonSet. We are using this type of service for running tasks that need to operate all the time, for example, on all servers (Nodes) in the Kubernetes cluster.
We’re using DaemonSets for running services that must operate on all or some servers. We could use this type of objects for services including log distribution agents like logstash, monitoring agents like Datadog, or agents maintaining disk access, like ceph or glusterd.
Questions before migrating to Kubernetes
Kubernetes is currently the most popular container management engine and is considered one of the more stable ones. The community tends to agree that mastering Kubernetes is a necessary skill, and many employers boast having smaller or larger production environments under its control.
While such clusters seem to be commonplace, Kubernetes is not necessarily the best choice for everyone. There are several questions worth answering when choosing the architecture and foundation for our application:
- Do we understand the domain we are operating in? In other words, are we rewriting an existing application and want to use newer technologies?
- What will the migration or new solution implementation look like?
- What is our current deployment and maintenance process for our application? What will need to change?
- Do we have a process for recording and storing logs?
- What does application monitoring look like, and how will it need to change?
- How do you manage your database changes?
- Do we have experience working with the public cloud where our application will run? And most importantly, do we have specialists in our organization who can manage the Kubernetes implementation? If not, who could help us gain necessary knowledge?
Kubernetes is undoubtedly a good solution for developed organizations that have outgrown their current technologies and are looking for a way to increase the frequency of releasing new changes while maintaining or improving scalability.
Is Kubernetes Right for my organization?
For new applications, implementing Kubernetes may be a significant challenge, especially for a young team. The complexity and overhead of managing a Kubernetes environment can be daunting without the necessary experience and infrastructure in place.
Your team will require a thorough understanding of both the technology itself and the specific requirements of the application you will be deploying. Teams need to consider the learning curve and whether the benefits of using Kubernetes outweigh the initial and ongoing efforts required for its management.
Some of the headaches related to maintenance of your own Kubernetes cluster inside a data center, you can consider using managed cluster solution like AWS EKS, GKE from Google or Azure AKS. There are also dedicated solutions that can be cheaper and are fully managed, like Sharedkube.
For applications that demand high availability, scalability, and a microservices architecture, Kubernetes offers significant advantages. It can streamline the deployment process, enhance the efficiency of resource utilization, and provide a robust ecosystem for managing containerized applications at scale. The ability to deploy consistently across different environments and cloud providers also makes Kubernetes a versatile choice for multi-cloud strategies.