Did you know! Kubernetes is basically an API which you interact with through the kubectl command line tool? This API in turn manages the details of your deployment on K8S. Undoubtedly, the entire stack is way more complex than this, as it includes components such as the Container Runtime Interface (containerd) and the ETCD database for the control plane. Add onto that the kubelet service running on the nodes, and you can easily get lost in the intricacies of Kubernetes.
In this article, I'll be going with the basics, the underlying resources which sustain your application as it runs on Kubernetes. That is, Pods, Deployments, Services, and Persistent Volumes.
Just before, let's have a quick look at kubectl. It's a command line tool which basically acts as a frontend to requests to the Kubernetes API. When you create a pod with kubectl, it is really just making a request to the API to create the pod. And while you have a GUI to interact with your cluster in certain flavours, such as Rancher, it is extremely useful to know the CLI tools for when you inevitably have to fix something or delve deeper than the GUI lets you.
Pods
Pods are the most rudimentary component of K8S and cannot be subdivided further. A pod may contain one or more containers, such as the Docker containers you are undoubtedly familiar with. And, running a pod is similar in practice to running a container.
apiVersion: v1
kind: Pod
metadata:
name: dummy-nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
Here, you are specifying a pod of name dummy-nginx with one container inside, named nginx and using the image nginx:latest. The container is exposed through port 80. You can then create the pod by the command kubectl apply -f manifest.yaml
The downside of running an individual pod is that there are no strings attached. This means that if the pod crashes, there will be no means of bringing it back up. Alternatively, if you restart the cluster, the pod will not persist. Pods are useful for quick debugging where you can run a Ubuntu container and access its shell to troubleshoot applications within your cluster.
Deployments
A deployment is the next step up from a pod. It employs something called a ReplicaSet which gives it a load of bonus features which you would want for your application to be considered Highly Available.
A ReplicaSet itself acts as a "manager" to ensure that the desired number of replicas of a pod are running at a given time. For example, I may want 3 replicas of a webserver (nginx). The ReplicaSet will ensure that I have those 3 pods running, and if any of them were to crash or stop running, it will call for another replacement pod to be brought up. This can be particularly beneficial if the node running the pod were to crash and stop working.
Thus, a ReplicaSet is essential for scalability, high availability, and fault tolerance. A deployment can be thought of as a wrapper for the ReplicaSet, while offering additional functionality such as rolling updates and rollbacks which are very important when that update goes awry.
While you can create a deployment the same way as I did with the pod, using a manifest file which you need to attentively craft, lest you obtain some arcane error message. This can be done a bit like this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-demo
labels:
app: nginx-test
spec:
replicas: 1
template:
metadata:
labels:
app: nginx-test
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
But the way I like doing this would be directly with kubectl:
kubectl create deployment --image nginx:latest --port 80 --replicas 1 nginx-demo
Ain't that simple? And people have the balls to say that Kubernetes is complex.
Quick Practical
Using the above code, let's have a quick practical session!
As you can see here, I have used the kubectl command to create the deployment, and then retrieved the currently running deployments. After a few seconds, the deployment is ready and READY is now 1/1 meaning all replicas specified (1) are running! Awesome.
Now, we know that a deployment encapsulates a replicaset which manages pods. So, fetching the pods will show there's one running right there! You can use the -o wide flag to get even more details:
So, here the pod is over on the node I called potato-3 and has an internal IP of 10.42.5.4. This is an IP internal to the cluster and I cannot access the pod from outside. However, we want to access the pod from outside, otherwise how will you deliver your application? This brings us to services!
Services
A service is another object in the Kubernetes API which provides a static and constant route into your pods. Why is this important? Well, first of all, pods may be deleted and recreated on the fly. This may happen if the pod or node crashes. And, when it is recreated on a different node, with a different internal IP, the service will ensure that the route to the pod will be maintained.
I won't go too deep into services, but you need to know that there are 4 types.
ClusterIP
This service type is useful when you need connections within the cluster itself. For example, this Ghost website runs on Kubernetes and is made up of two pods. The first is the Ghost pod itself, and the second is the MySQL database which stores the website data. Now, I have no need of accessing the MySQL db from outside the cluster, so it is given a ClusterIP which the Ghost pod uses.
NodePort
The awesome thing with services is that their names are so self-explanatory. NodePort is what you would use to access a pod from outside your cluster. When creating this service, the control plane allocates an IP from a range (default: 30000-32767) for this pod. Then, you can easily access the pod through the IP of any of your worker nodes, followed by the port.
LoadBalancer
This is the fun one, in my opinion. It's what I use for my website. To use this service type, you need a Load Balancer. If you're hosting your cluster on the cloud, then you probably already have a Load Balancer (but need to pay for IPs). For me, on bare-metal servers, I use metalLB which uses ARP to give me IPs from a specified range. This image should be pretty self-explanatory. Afterwards, I just create an entry in my nginx reverse proxy to point to 192.168.1.217:80, and then do port forwarding rules in my router.
ExternalName
I won't lie, I don't use this. It's a way of exposing a service which does not run in your Kubernetes cluster, through your cluster. This might be beneficial for certificates or just integrating everything in one place.
Headless Services
Not a service type itself, but such a service has the spec.ClusterIP set to None. This means that no IP is generated and querying the service name in the K8S DNS will instead return the individual IPs of the pods in question. This may be useful when want your own load balancing option or stateful clusters like a database.
La Fin
And with that, you should be ready to deploy pods and create the services needed to expose them. These two concepts are the absolute foundation to Kubernetes and will spring up on any scale from homelab to production. Afterwards, we will have a look at deployments and replicasets, as well as how to scale your application.