A beginner-friendly and step-by-step tutorial for Kubernetes. Prerequisites? Basic experience in web development and basic theoretical understanding of Docker. If you don't know Docker, please check out my other post on Docker here: https://markmaksi.hashnode.dev/docker-containers
What is Kubernetes?
In production environment we want to scale our servers up and down based on the load. In AWS for example there is a feature called "AWS Auto Scaling" that allows you to scale your server up and down automatically, where new virtual machines are spinned up on the fly on demand. But what if our application follows micro-services architecture and only few services experience different request load throughout the day? In this case, spinning an entire virtual machine would be inefficient for both the business and the environment. It is best to only scale those two services up and down. The tool that allows that to happen is Kubernetes.
Kubernetes, often referred to as k8s, is a tool for automatically creating, running and managing multiple containers inside a "Kubernetes cluster". We give it a YAML
configuration file to describe how we want our containers to run and interact with each other.
Kubernetes Installation
To run Kubernetes on a Windows machine, you have to install Docker. Inside Docker and from the Settings gear icon, enable Kubernetes and click "enable and restart".
To make sure Kubernetes is up and running: kubectl version
To follow along this tutorial, I recommend you watch my video YouTube tutorial:
Go to my github repository, fork it and clone it to your local machine: https://github.com/mmaksi/kubernetes-intro-tutorial.git
Install and run Docker
Run Kubernetes inside the Docker application
Inside the fork you cloned you can find a simple express server and a Dockerfile to containerize this express app. Kubernetes is used to orchestrate and manage Docker containers. So the first step to learn Kubernetes is to deploy a single Docker container inside what is called a Kubernetes cluster. What is a cluster? Let me introduce you to some Kubernetes terminologies.
Kubernetes Cluster: a collection of nodes + a master to manage them
Node: a virtual machine that will run our containers
Pod: a Kubernetes object that can run multiple containers, but usually we create one container inside each Pod, so for the sake of this blog I will define it more or less as "a running container"
Deployment: monitors a set of pods and makes sure that they are running and restarting them if they crashed.
Service: provides an easy to remember URL to access a running container either in the form of communication between pods or accessing it from outside the cluster like from a browser.
What are config files?
They are files written YAML syntax and tell Kubernetes about the different Deployments and Pods and Services (known as objects). They should always be stored with the project's source code because they act as documentation to what Kubernetes cluster and other compnents are doing.
PRO TIP: Always create objects using a config file, not the CLI
Create your first Pod
We usually create Pods using a Deployment. Remember? A Deployment is in charge of 1 or more Pods. Let's create a file inside the k8s
sub-directory called posts.yaml
.
# infra/k8s/posts.yaml
# where to create the object from
apiVersion: v1
# the type of object to create
kind: Pod
# name of the object (for logging purposes)
metadata:
name: posts
# configurations for the container inside the pod
spec:
containers:
# container name
- name: posts
# from what image to create that Docker container
image: markmaksi/posts:0.0.1
To create the Pod using the previous YAML file, we have to execute the file. Run: kubectl apply -f posts.yaml
Now you should see created
logged in your console.
To see our pods: kubectl get pods
Common Pods Commands
List running pods:
kubectl get pods
Execute the given command (e.g
sh
) in a running pod:kubectl exec -it posts sh
Print logs from the given pod:
kubectl logs posts
Process the given config file:
kubectl apply -f posts.yaml
print some info about the running pod:
kubectl describe pod posts
Delete a Pod:
kubectl delete pod <pod name>
TODOS:
Try deleting a Pod and re-creating it
Run all logs inside the container, try adding your own logs inside the codebase and check if they are printed as expected. HINT: you have to rebuild the image and the Pod once you make updates to the codebase.
Advanced: run
ls
command inside the container (Pod)
Congrats! Now you know how to write a Pod configuration file and how to create a Pod out of it! Oh... wait a second! To be honest that way of creating Pods isn't the standard way we do it. We create Pods using a Deployment configuration file. So let's see how it's done.
Deployments
A Deployment is a Kubernetes object responsible for managing one or multiple pods where containers live. The deployment is responsible for auto restarting a pod if it crashed. Also it is responsible for updating the pods in case we updated the code inside the container.
Let's remove the posts.yaml
file and instead create a file inside the k8s
sub-directory called posts-depl.yaml
. In a microservices architecture, we usually create a Deployment for each service. So you have posts-depl
and payment-depl
and auth-depl
and so on. Inisde every Deployment configuration file we tell Kubernetes what Pods to create:
# infra/k8s/posts-depl.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: posts-depl
spec:
# how many Pods to create and manage
replicas: 1
# manage all pods created with label 'app: posts'
selector:
matchLabels:
app: posts
template:
metadata:
# attach the label 'app: posts' to all the pods we will make
labels:
app: posts
spec: # like the pod config file
containers:
- name: posts # create a container from the latest version of the image
image: markmaksi/test:latest
resources:
limits:
memory: "256Mi"
cpu: "500m"
To create the deployment from the YAML file above, run: kubectl apply -f posts-depl.yaml
Common Deployment Commands
List running deployments:
kubectl get deployments
Process the given config file:
kubectl apply -f posts.yaml
Print some info about the running pod:
kubectl describe deployment posts-depl
Delete a Deployment:
kubectl delete deployment <deployment name>
Let's test the features of Deployment
Now if you try to delete the Pod that was created by the Deployment, and then list all the Pods you will see that indeed the Deployment created a new Pod with a new name, which means we successfully configured the Deployment to manage 1 Pod and restart it in case it crashed! Look at the screenshot below:
We previously mentioned that a Deployment is also responsible for updating the Pods in case our code has changed, so let's see how that is done!
Updating Deployment
To configure the Deployment to automatically update the image used to create the Pods, these steps are followed in a professional production:
The Deployment must be configured using the
latest
tag of the imageUpdate the code, like adding a new
console.log('new app version')
Rebuild the image
Push the image to Docker Hub
Run:
kubectl rollout restart deployment <deployment name>
Now that we have learned how to create a Deployment and configure it to manage multiple Pods, you might ask yourself: how are we going to connect to our application that is hidden behind a Deployment and behind a Pod and behind a container?!?! Let's discuss Services.
Services
Types of Services:
Cluster IP: Sets up communication between Pods in a cluster, like what happens in a microservices application.
NodePort: makes a Pod accessible from outside the cluster. Usually used for development purposes.
Load Balancer: makes a Pod accessible from outside the cluster. This is the right way to expose a Pod to the outside world in a production environment.
NOTE: Load Balancer Service tells Kubernetes cluster to reach out to the cloud provide in which it is deployed (AWS, Azure, etc..) to provision a load balancer whose purpose is to route incoming requests from the internet to a single pod inside the Kubernetes cluster.
On the other hand, there is a specific pod called Ingress Controller whose job is to route incoming requests to different pods inside the cluster. It works with the load balancer to route requests to the right pods.
Now let's create a service. Creating a Cluster IP doesn't make sense because we only have 1 Pod. Creating a Load Balancer requires more services and configuration. So let's create a simple Node Port that we can access from the outside world.
in infra/k8s
create a file posts-srv.yaml
that looks like this:
apiVersion: v1
kind: Service
metadata:
name: posts-srv
spec:
type: NodePort
selector:
# expose the pod with this label to the outside world (review the deployment file)
app: posts
ports:
- name: posts # name of the service
protocol: TCP
port: 3000 # port of the NodePort Service
targetPort: 3000 # port of the express app in the pod
Think of the selector in a YAML file like the selctor in CSS. In CSS a selctor selects an HTML element, and in a YAML file, a selector selects an object with a certain label
Now run: kubectl apply -f posts-srv.yaml
Now run: kubectl get services
and you will see an output like this:
The NodePort Service is finally up and running and is listening on Port 30417. To access this port on Windows and MacOS, simply go to: localhost:30417/posts
and you will finally see the message!
If you have problems seeing the message, check out your logs. Use kubectl logs <posts-pod-name>
to check if you can see any logs.
Ok, now you might ask yourself, why do we have 3 ports configured? Let me explain.
When executing the NodePort Service YAML file, Kubernetes exposed a random port 30417. Also in the YAML file we have two ports as follows:
ports:
port: 3000 # port of the NodePort Service
targetPort: 3000 # port of the express app in the pod
Let's review the architecture of what we have built so far.
We have an express app running a Docker container that is wrapped in a Kubernetes Pod and that Pod is inside a Kubernetes Node and that Node lives in a Cluster.
30417 refers to the Cluster Node in which the Pod lives. That Cluster Node is connected with the NodePort Service with port 3000 and that in turn is connected with the express app through port 3000.
Congratulations!! You've learned how to play with a Kubernetes YAML file and create a Deployment with Pods and a simple NodePort Service that allows us to access a containerized app from a web browser!
If you like this blog post, consider sharing it with your aspiring software engineers and look for my upcoming blog posts and YouTube videos. Have any ideas for future content? Please leave a comment on my YouTube video or my blog post here on Hashnode.