In this guide, you will learn everything you need to know about Kubernetes and how you can use it to deploy and manage containers at scale.
Modern applications are increasingly built using container and microservice architecture. Kubernetes is open-source software for deploying and managing those containers at scale. It groups containers into logical units for easier management, discovery, and scaling of your application.
The primary motivation behind this article is to give a clear overview of the entire world of Kubernetes and making it simple and easy to understand. This guide will go over all essential concepts of Kubernetes and will then use them all in a practical example.
Even if you had no prior experience with Kubernetes before, this article will prove to be the perfect start for your journey.
So, without wasting any further time, let's get started.
Why care about Kubernetes?
Before we dive into the technical concepts, let's first discuss why a developer should even use Kubernetes in the first place. Here are some reasons why developers should consider implementing Kubernetes in their work.
Moving containerized applications from development to production is a seeming less process when using Kubernetes. Kubernetes enables developers to orchestrate containers consistently in different environments across on-premises infrastructure and public and hybrid clouds.
Defining complex containerized applications and deploying them globally across multiple clusters of servers is made simple as Kubernetes optimizes resources according to your desired state. When Kubernetes scales applications horizontally, it will also automatically monitor and maintain container health.
Kubernetes has a vast and ever-growing collection of extensions and plugins created by developers and companies that make it easy to add unique capabilities such as security, monitoring, or management capabilities to your cluster.
Making use of Kubernetes requires an understanding of the different abstractions it uses to represent the state of the system. That is what this section is all about. Getting familiar with the most essential concepts and giving you a clearer picture of the general architecture.
A Pod is a group of multiple containers of your application that share storage, a unique cluster IP address, and information about how to run them (e.g. container image, ports, restart and failure policies).
They are the building block of the Kubernetes platform. When we create a deployment or a service, Kubernetes will automatically create a Pod with the container inside.
Each pod runs on the node it is scheduled on and remains there until termination or deletion. In case the node fails or terminates, Kubernetes will automatically schedule identical Pods on the other available Nodes of the cluster.
A node is a worker machine in a Kubernetes cluster and may be either a virtual or a physical machine, depending on the type of cluster. Each node is managed by the so-called master. The master automatically schedules pods across all nodes in the cluster, depending on their available resources and current configuration.
Each node has to run at least two services:
Kubelet - A process responsible for the communication between the Kubernetes master and the node
A container runtime - Responsible for pulling and running a container image (Docker for example)
A Service is an abstraction that defines a logical set of Pods and a policy by which to access them. Services enable loose coupling between dependent Pods.
Although each pod has a unique IP-Address, those addresses are still not exposed to the outside of the cluster. A service therefore allows your deployment to receive traffic from outside sources.
Services can be exposed in multiple ways:
ClusterIP (standard) - Only expose the port to the internals of the cluster
NodePort - Expose the service on the same port on every node in the cluster using NAT
Loadbalancer - Create an external load balancer to export the service on a specific IP Address
Deployments contain a description of the desired state of your application. The deployment controller will then try to make sure that the current state of the application meets that description.
A deployment runs multiple replicas of your application and automatically replaces any instances that fail or become unresponsive. In this way, deployments help ensure that your application is available to serve user requests.
Before we can start creating our cluster, we first need to install Kubernetes on our local machine.
If you are using Docker desktop on windows or mac, you can install Kubernetes directly in the settings window of the user interface.
Now that we have the most essential concepts out of the way let's continue by looking at the practical side of Kubernetes. This chapter will guide you through all the basics you need to know to deploy applications in a cluster.
Creating a cluster:
Minikube automatically creates a cluster when you start it.
Docker desktop should also automatically create a cluster after the installation. You can check if your cluster is running using the following commands:
# Get information about the cluster
# Get all nodes of the cluster
kubectl get nodes
Deploying an application:
Now that we have finished the installation and set up our first cluster, it is time to deploy an application to Kubernetes.
We use the create deployment command and pass the name of the deployment and the container image as arguments. This example creates an Nginx deployment with one container and one replica.
You can see your running deployments using the get deployments command.
kubectl get deployments
Get information about your deployments:
Here are a few commands you can use to get more information about your deployments and pods in Kubernetes.
Getting all pods:
You can get a list of all running pods using the kubectl get pods command:
kubectl get pods
The output should look similar to this.
Get a detailed description of your pods:
If you want a more detailed description of your pods, you can use the describe pods command instead.
kubectl describe pods
View the logs of a pod:
The information your application would typically send out to STDOUT become logs of your container. You can access those logs using the following command.
kubectl logs $POD_NAME
Note: You can get the name of your pod using either the get pods or describe pods command.
Executing a command in the container:
We can execute commands directly in our container using the kubectl exec command, which takes the pod name and the command we want to run as arguments.
kubectl exec $POD_NAME command
To clarify, let's look at an example where we start a bash terminal in the container.
kubectl exec -it $POD_NAME bash
Exposing your app publicly:
As we already discussed earlier, a service defines a policy by which the deployment can be accessed with. In this section, we are going to take a look at how this is done and some different options you have when exposing your services to the public.
Creating a service:
We can create a service using the create service command that will take the port we want to expose and the type of port as arguments.
kubectl create service nodeport nginx --tcp=80:80
This will create a service for our Nginx deployment and expose the port 80 of our container to a port on our host machine.
You can get the port on the host machine using the kubectl get services command:
As you can see, port 80 of the container is forwarded to port 31041 of my host machine. When you have the port, you can verify your deployment by visiting your localhost on that port.
Deleting a service:
We can also remove existing services using the delete service command.
kubectl delete service nginx
Scale up your app:
Scaling your application up and down is extremely simple using Kubernetes. You can change the number of replicas using a single command and Kubernetes will automatically create and maintain everything for you.
kubectl scale deployments/nginx --replicas=5
This command will scale our Nginx service up to five replicas.
This way of deploying applications works excellent for small one container application but doesn't provide the overview and reusability needed for more prominent applications. This is where YAML files come into play.
YAML files let you declare your deployment, services and pods using a markup language, so they are reusable and better for scaling. Yaml files will be covered in-depth in the following chapters.
Declaring Kubernetes object in YAML
Every object in Kubernetes can be represented as a declarative YAML object that provides details about what and how you want to run. These files are often used to increase the reusability of resource configuration like deployments, service, volumes and many more.
This section will show you the basics of YAML and how you can get a list of all available parameters and attributes of a Kubernetes object. We will also take a look at a deployment and service file to get a better feeling of the syntax and how you can deploy the file.
Getting the parameters of the different objects:
There are many different Kubernetes objects and it is hard to remember every setting. That is where the explain command comes into play.
kubectl explain deployment
DEPRECATED - This group version of Deployment is deprecated by
apps/v1beta2/Deployment. See the release notes for more information.
Deployment enables declarative updates for Pods and ReplicaSets.
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
Standard object metadata.
Specification of the desired behavior of the Deployment.
Most recently observed status of the Deployment.
You can also get the documentation of a specific field using the following syntax:
kubectl explain deployment.spec.replicas
More complex deployments will normally be written in YAML for easy reusability and changeability.
Here is the basic file structure:
# The name and label of your deployment
# How many copies of each pod do you want
# Which pods are managed by this deployment
# Regular pod configuration / Defines containers, volumes and environment variable
# label the pod
- name: mongo
- containerPort: 27017
The YAML file has multiple important sections:
apiVersion - Defines the version of the API
kind - The kind of Kubernetes object defined in the file (e.g. deployment, service, persistentVolume, ...)
metadata - Description of your YAML component containing name, labels and other information
spec - Defines the attribute of your deployment e.g. replicas, resource limits
template - Pod configuration of the deployment file
Now that you know the basic structure, you can deploy the file using the apply command.
kubectl apply -f deploy.yaml
Service files have a similar structure then deployments with minor differences in the parameters.
When a pod is deleted or a container restarts, the whole data of the file system is deleted. In most cases, this is a good thing, because your stateless application doesn't get littered with unneeded data. In other cases, persisting the data of your file system is vital for your application.
There are a few different kinds of storage:
Container filesystem - Standard storage that stores the data of a single container for his lifetime
Volume - Volumes let you save data and share it between different containers as long as the pod exists
Persistent Volumes - Persistent Volumes save data even after the pod is deleted or restarted. They are the long-term storage in your Kubernetes cluster.
Volumes let you save, share, and preserve data between multiple containers as long as the pod exists. This can be useful if you have pods with multiple containers that need to share data with each other.
There are two steps for using a volume in Kubernetes:
The pod defines the volume
The container uses the volumeMounts to add the volume to a specific path of the filesystem
You can add a volume to your pod using the following syntax:
The volumes tag is used to define the volume that can then be mounted to a specific directory of the container filesystem (in this case /etc/nginx).
Persistent volumes are almost identical to normal volumes, with the key difference that they preserve the data even after the pod is deleted. That is why they are used for long-term data storage purposes like a database, for example.
The most common way to define a persistent volume is a so-called Persistent Volume Claim (PVC) object, which connects to backend storage volumes through a series of abstractions.
Managing compute resources of your containers and applications is an important topic when it comes to container orchestration.
When your containers have a specified amount of resources available, the scheduler can make better decisions about which node to place the pod on. You will also run into fewer contention problems with the resources of different deployments.
There are two different types of resource specifications that we will cover in detail in the next two sections.
Requests tell Kubernetes the requirements of the pod and that it should only place the pod on nodes that have these requirements. Requests are defined under the resources tag on your container.
The apikey-volume refers back to the secret-apikey. We also specify the mountPath directory that the secret will be stored in and set readOnly to true.
When pulling from a private registry, you might need to authenticate before pulling your image. An ImagePullSecrets is a file that stores the authentification data and makes it available to all your nodes when they need to pull a specific image.
You can verify the changes using the get-context command.
kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* Default Default Default testnamespace
Simplifying Kubernetes with Docker Compose
For those of you coming from the Docker community, it might be easier to write Docker Compose files instead of Kubernetes objects. That is where Kompose comes into play. It lets you convert or deploy your docker-compose file to Kubernetes using a simple CLI (command line interface).
Kompose can be installed on all three mature operating systems in a very easy and simple manner.
Linux and Mac users just need to curl the binaries to install Kompose.
As with Docker Compose, Kompose also allows us to deploy our configuration using a simple command.
Now you should see the created resources.
kubectl get deployment,svc,pods,pvc
Converting a Compose file to Kubernetes Objects:
Kompose also has the ability to convert your existing Docker Compose file into the related Kubernetes object.
You can then deploy your application using the apply command.
kubectl apply -f filenames
Demo application deployment
Now that you know the theory and all the vital concepts of Kubernetes, it is time to put that knowledge into practice. This chapter will show you how you can deploy a backend application on Kubernetes.
The specific application in this guide is a GraphQL boilerplate for the Nest.js backend framework.
Before we can start with creating the Kubernetes objects, we first need to push the images to a publicly available Image Registry. This can either be a public registry like DockerHub or your own private registry.
If you want to know more about how to set up your own private Docker Image, visit this article.
To push the image, just add the image tag with the registry you want to push it to in your Compose file.
The file contains a deployment object with a single MongoDB container that is labeled mongo. It also contains a service that makes port 27017 available to the Kubernetes network (It cannot be accessed on the host machine but only from other containers in the same namespace).
The Nest.js Kubernetes object is a bit more complicated because the container needs some extra configuration like environment variables and imagePullSecrets.