Kubernetes Concepts and Hands-On with Minikube

Introduction
Kubernetes, also known as “k8s”, is an open-source container orchestration tool designed to automate deploying, scaling, and operating application containers. Docker containers can be used to develop and build applications, then Kubernetes can be used to run these applications. You can use other container engines with Kubernetes, but I’ll be using Docker since it is the most popular.
Previous Docker experience is recommended for this post to make more sense, at the very least an understanding of container concepts.
Kubernetes Objects
The following is Kubernetes in a nutshell:
At the highest level, we have Clusters. Each Cluster contains a Master Node and several Worker Nodes. Worker Nodes each have Pods, which are just a group of containers. So the order from top to bottom is:
Cluster -> Master Node/Worker Nodes -> Pods -> Containers
Each of these objects are declared through Deployments and Services within a specified Context and Namespace. Deployments, Services, Contexts, and Namespaces can be declared through the command line or using YAML files.
The kubectl
command is how we interact with Kubernetes objects.
Now, let’s dive deeper into each of these objects.
Clusters
Each cluster contains a single master node and multiple worker nodes. Each node contains its own processes. The master node is responsible for managing the cluster. In a production environment, it is recommended to have at least a three-node cluster in addition to the master node.
Master Node
The master node is responsible for the overall management of the cluster. It has an API Server, a Scheduler, a Controller Manager, and a distributed key-value store called etcd
.
- The API Server allows you to interact with the Kubernetes API.
- The scheduler watches created pods who do not ave a node assigned yet, and assigns the pod to run on a specific node.
- The Controller Manager runs controllers, which are simply background threads that run tasks in the cluster.
- The
etcd
store is used as a database for cluster datta such as job scheduling information and pod details.
We interact with the master node using the kubectl
command in the terminal. kubectl
has a configuration file called kubeconfig
that has server information and authentication information to access the API server.
Worker Nodes
These nodes are where applications operate. Worker nodes can be physical or virtual machines. They communicate back with the master node. Worker nodes can be exposed to the internet through a load balancer. There are three things running in each worker node: a process called kubelet
, a process called kube-proxy
, and a container engine.
The
kubelet
process is responsible for pod management within the node. It’s an agent that communicates with the API server to see if pods have been assigned to the nodes, executes pod containers via the container engine, mounts and runs pod volumes and secrets, and responds back to the master node.The
kube-proxy
process is a network proxy and load balancer for the service on a single worker node. It handles network routing for TCP and UDP packets and performs connection forwarding. Any traffic coming into the node is handled by this process. This is how an end-user ends up talking to a Kubernetes application.The Container Engine works together with the
kubelet
process to run containers on the node. Although in this post I’m using Docker, you can use other container engines. Containers of an application are tightly coupled together in a Pod.
Pods
A Pod is a single instance of a running process in your cluster. It is the smallest unit you can interact with in Kubernetes. A Pod is made up of a group of containers inside a Node that share storage, Linux namespace, and IP addresses.
When pods are deployed and running, the kubelet
process in the node communicates with the pods to check on state and health. The kube-proxy
process in the node routes any packets to the pods from other resources.
Pods are designed to be disposable. They never self-heal and are not restarted by the scheduler itself. You should never create pods just by themselves, always user higher-level constructs to manage pods.
Pods have several states: pending, running, succeeded, failed, and CrashLoopBackOff
pending
: A pod as been accepted by Kubernetes but a container has not been created yet.running
: A pod has been scheduled on a node and all of its containers have been created, and at least one container is in a running statesucceeded
: All the containers in a pod have exited with a status of 0 which means success. These containers will not be restarted.failed
: All containers in the pod have exited and at least one container has failed and returned a non-zero exit status.CrashLoopBackOff
: A container has failed to start and Kubernetes is repeatedly trying to restart the pod.
Contexts
A Context simply refers to a Kubernetes Cluster. Each Cluster that is created will have its own Context. Contexts tell the kubectl
command to which Cluster to run commands against. We’ll get some hands-on experience with Contexts in a later section.
Namespaces
Namespaces are virtual clusters within a single physical cluster. Within a single Cluster, you can define several namespaces to logically divide resources and applications. Similar to Contexts, we use Namespaces to further specify to kubectl
what objects we want to interact with.
Deployments
A Deployment is a representation of multiple identical pods, and how we describe a desired state in Kubernetes. After describing a desired state, Kubernetes then changes the actual state to match the state we want. We can create a Deployment directly in the command line like this:
|
|
We can also create a Deployment through a YAML file and specify more information, such as the number of pods we want using the replicas
line:
|
|
And then apply the YAML with the following command:
|
|
The Deployment will ensure that the number of Pods we want are running and available at all times.
Services
A Service is an abstract way to expose an application running on a set of pods as a network service. Kubernetes automatically assigns pods an IP address, a single DNS name for a set of pods, and can load-balance traffic across pods.
Deployments are used to describe a state and update Pods and applications, then Services are used to expose these Pods to make them accessible by users. You can also use Services to expose two deployments and get them to talk to each other, such as frontend and backend pods.
There are four types of Services in Kubernetes
ClusterIP
: The default type of service. It exposes the service internally in the cluster and can only be reached from within the cluster.NodePort
: This type of service exposes the service on each node’s IP on a static port. A ClusterIP service is automatically created and the NodePort service will route to it. You can access the NodePort service from outside of the cluster by using the NodeIP:NodePort socket.LoadBalancer
: This service type exposes the service externally using the load balancer of your chosen cloud provider. The external load balancer routes to NodePort and ClusterIP services which are automatically created.ExternalName
: Maps the service to to the contents of theExternalName
field. No Proxying of any kind is set up.
If a Deployment named mydeployment
has been previously created, we can create a Service using the command line as such:
|
|
Just like with Deployments, we can also specify a Service using a YAML file:
|
|
And apply it using
|
|
Kubernetes Hands-On with Minikube
Minikube is a tool that will start up a single-node Kubernetes cluster on a virtual machine on our computer. It’s great for getting comfortable with Kubernetes commands.
Installing Minikube
I’ll be showing steps to install Minikube on MacOS using brew
. If you’re running Windows, Your best bet is to refer to the official Kubernetes documentation.
You’ll need a hypervisor to run Minikube on. I chose VirtualBox, and installing it was easy as running:
|
|
Then we can install Minikube by running
|
|
After installation is complete, start up minikube
|
|
This command will download the Minkube .iso and run it using Virtualbox.
We can double check that everything is working by running the following:
|
|
We can also run Kubernetes commands to verify that we have a Node running:
|
|
And we have confirmation that a single Node is up and running!
Deploying An Image Through The Command Line
Now that Minkube is set up and we have a Cluster running, let’s recap some of the concepts we covered above. Recall Contexts and Namespaces.
By default, Minikube created a Cluster with a Context called ‘minikube’. We can see this by running:
|
|
Minikube also created a default Namespace simply called ‘default’. View it by running:
|
|
There are additonal Namespaces here used by core Kubernetes services, but we don’t have to worry about those.
All of our kubectl
commands right now will run against the ‘default’ Namespace within the ‘minikube’ Context. If in the future you have a Cluster running, say, on Amazon Web Services, you can switch to that Context/Namespace and run commands against that Cluster.
Now, let’s deploy a simple container that displays “Hello World” on a browser.
First, create a deployment specifying the image to use:
|
|
Then, create a service to expose the deployment:
|
|
Now we can access the service through Minikube with the following command:
|
|
You should get similar output to this:
|
|
And your browser should automatically open the IP/port in a new window. You should see a simple Bootstrap page that says “Hello”. We have successfully run a container with Kubernetes and accessed the application!
Now let’s recap some more concepts. Recall Deployments, Services, Nodes, and Pods.
We can view the deployments and services we created with the following commands:
|
|
|
|
There’s a default ‘kubernetes’ service that we can ignore. The one we created is ‘helloworld’.The Deployment we created specified the state we want our Cluster to be in, while the Service we created exposed the Pod created by the Deployment.
We can also view the single Node in our Cluster:
|
|
And we can view the Pod that was created by the Deployment:
|
|
And finally, we can view everything running on Minikube through the dashboard:
|
|
You’ll notice that there are a lot more Kubernetes Objects and features in the dashboard that I have not covered. Those are beyond the scope of this post and for more advanced purposes. However, feel free to read up on those once you have a solid grasp of the concepts covered in this post.
While it’s nice looking at a dashboard, I highly recommend getting comfortable with the kubectl
command to view and manage Kubernetes resources.
Deploying Using YAML Manifests
In an earlier section, I briefly mentioned that you can specify Kubernetes objects using YAML files. We’re going to deploy a Ruby on Rails application on Minikube using YAML files. (Note: The application was created by me for learning purposes, but we could be using any other image for this)
Using YAML files are useful because they allow you to version control your Kubernetes resources and modify them in a single file. Whenever changes are made to a YAML file, we simply need to run kubectl apply
as you’ll see soon. This process can even be automated in a CI service.
First, let’s start a fresh Minikube instance. Go ahead and delete the current Minikube VM and start another one:
|
|
Next, copy these two YAML files locally in your system:
deployment.yaml
|
|
service.yaml
|
|
Take some time to read through the files to get some idea of what’s going on. Kubernetes YAML files can be used for deep configuration, too much to cover in a single post. I recommend reading through documentation to better understand what’s possible in a YAML file.
For now, understand that the Deployment is specifying an image to pull (nfigueroa/forum
), a port to expose on the container (containerPort
), and the number of pods we want (replicas
).
The Service is specifying that we want to expose a Node port (nodePort
) on port 30000, and this Node port will point to port 3000 in the pod.
Let’s apply the Deployment YAML:
|
|
Once the Deployment has been created, we can view it:
|
|
And we can see the Pods starting up:
|
|
Next, let’s apply the Service YAML:
|
|
We can view the Service:
|
|
Now we can view our application with the same command we used before:
|
|
And your browser should automatically take you to the landing page. If for any reason the landing page is blank, double check that the pods are running. It might take a while to initialize the application.
YAML files can be created for other Kubernetes Objects as well, like Pods and Namespaces. While it is okay to create a YAML for an individual Namespace, it is not recommended to create individual Pods this way unless it is for testing purposes or a very specific situation. Always use higher level abstractions to create pods, like Deployments.
Feel free to try the YAML files with your own Docker images, just make sure you change the ports to the ones your application needs.
Refer to the Kubernetes documentation and see what other Objects and configurations can be declared using YAML files. There is too much to cover in this post.
Cleaning Up
After you’re done playing with Minikube you can shut it down so it doesn’t use up resources:
|
|
If you want to completely remove the Minikube virtual machine, run:
|
|
Conclusion
In this post, I covered the essential Kubernetes concepts. Then, we ran Kubernetes locally using Minikube and manually deployed a Docker image. After that, we deployed an application using YAML files.
There’s a lot more functionality that I did not cover, but this is enough to get started. Kubernetes is a complex piece of software and has a tough learning curve, but it is very rewarding. From this point forward, read the Kubernetes documentation and get a deeper understanding.