Running applications typically require servers. In the good old days, it was not possible to define and enforce boundaries for running applications on a server and to ensure fairness in resource usage. As a result, we were constrained to run a single application on a server, and obviously, this resulted in poor resource utilization.
This led to the introduction of virtualization, which allows us to create multiple virtual instances of physical resources on a single physical machine.
A virtual machine (VM) is a virtualized instance of a computer system being managed by software, termed a hypervisor. Each VM operates as a self-contained and isolated entity with its virtual resources. Multiple VMs can coexist on the same physical server. Virtualization resulted in better resource utilization. It is important to highlight here that each VM is completely isolated and has its own operating system. This approach has several limitations, including limiting the number of VMs that can share a physical system.
Containers provide a lightweight virtualization solution when compared to VMs as multiple containers running on a host physical system share OS. Like VMs, each container has its own set of resources, including CPU share, but it shares the OS with other containers. Docker is a widely used container runtime for managing containers.
Containers offer several benefits compared to VMs and are widely used to bundle applications. However, managing containers in a production environment and providing services such as fault tolerance and load balancing is a challenging task.
This is where Kubernetes comes to the rescue. It is an open-source and extensible container orchestration platform. The project was open-sourced by Google in 2014. It automates the deployment, scaling, and management of containerized applications. Kubernetes allows the management and coordination of clusters of containers across multiple hosts, providing services such as fault tolerance and scalability.
Note: Kubernetes is often called and written as K8s, as there are eight letters between “K” and “s.”
A Kubernetes deployment is called a Kubernetes cluster with two types of resources; control plane and nodes. Each cluster has a pool of worker nodes and they run the containerized applications on Pods, which represent one or more co-located containers. These nodes are managed by the control plane, as shown in the illustration below. In a production environment, the cluster would contain multiple worker nodes, and the control plane would run across multiple machines ensuring high availability and fault tolerance.
The main components of the control plane are discussed below:
etcd: This is the key-value storage for storing the Kubernetes cluster’s data, service discovery details, and API objects.
kube-scheduler: It schedules newly created Pods on a worker node.
kube-controller-manager: It runs controller processes such as the node controller for handling node failures and the job controller. There is a separate controller component for Cloud integration.
kube-apiserver: The Kubernetes API server is the primary management entity for the cluster, receiving all REST requests.
Every worker node in the Kubernetes cluster also runs some components, as shown in the illustration above. We have specified Docker as the container runtime; however, Kubernetes supports many other runtimes. A high-level overview of them is as follows:
Kubelet: It manages the containers in the Pod and ensures that they are running and healthy.
Kube-proxy: It allows network communication to the Pods from the internet or inside the cluster.
Let’s first familiarize ourselves with some key concepts related to Kubernetes:
Pods: The basic building blocks of Kubernetes. A Pod is the smallest deployable unit in Kubernetes and represents one or more co-located containers.
ReplicaSets: Ensure that a specified number of Pod replicas are always running. Generally, we do not manage ReplicaSets directly and use a high-level concept, Deployments.
Deployments: A higher-level abstraction that manages ReplicaSets. Deployments enable us to define and update your application’s desired state declaratively.
Services: The Pods on a host can communicate with other Pods on the host. However, we can use the Service API, if we want to expose the application running on the Pods to the outside world (or within the cluster). Services allow us to abstract away the underlying Pod IPs and provide services such as load balancing.
Namespaces: Provide a way to logically divide cluster resources and thus, resource names need to be unique within a namespace.
In this section, we’ll deploy a sample application on minikube, a local Kubernetes cluster. You are required to follow the steps mentioned on the minikube website to install minikube on your local system. Then, use the commands below to start the cluster:
minikube start
To interact with the Kubernetes cluster, you can use the kubectl command-line utility to perform various operations on the cluster using the Kubernetes API. Follow the instructions available on the Kubernetes website to install the kubectl CLI. Alternatively, minikube also comes with kubectl which can be accessed using minikube kubectl -- [commands]. For this blog, we’ll assume that we have kubectl installed.
The general structure for kubectl commands is providing an <action> to be performed on a <resource>. To get a list of nodes, we can use the command below. We have also provided some other common examples to get you started. Note that by appending --help at the end of a command, you can get more information about its usage.
kubectl get nodeskubectl get nodes --helpkubectl get podskubectl describe pods nginx-pod
kubectl scale deploy/nginx-deployment --replicas=4
kubectl set image deploy/nginx-deployment nginx=nginx:1.25
kubectl rollout status deploy/nginx-deploymentkubectl rollout undo deploy/nginx-deployment
These commands help you scale quickly, update safely, and recover fast—core operational skills for running reliable Kubernetes workloads.
Let’s start by creating our first Pod. In practice, we do not create Pods directly; they are created using workload resources, such as Deployments. However, to get us started, here is the YAML template for creating a Pod:
apiVersion: v1kind: Podmetadata:name: nginx-podspec:containers:- name: nginximage: nginx:1.25.1ports:- containerPort: 80
The YAML file shown above is easier to understand. We name our Pod as nginx-pod and specify it to contain a single container running nginx. It is important to reiterate that Pods are the basic building blocks of Kubernetes. A Pod is the smallest deployable unit in Kubernetes, and the most common use case is the one-container-per-Pod model, where each Pod runs a single container.
kubectl can be used in two different ways: imperative or declarative. When used declaratively, we provide a manifest, such as the YAML file shown above, that describes our desired state, and kubectl submits it to the cluster, determining how to achieve it. On the other hand, when used imperatively, we provide cluster-specific commands to instruct kubectl on what actions to take.
To create the Pod shown in the file above, save the contents in a file named nginx-pod and then use kubectl apply as follows:
kubectl apply -f nginx-pod.yamlkubectl get pods
It may take a few seconds for the status of the Pod to change from
ContainerCreatingtoRunning. You should be able able to see1/1in theREADYcolumn.
The second command gets a list of Pods, and if everything goes well, we’ll be able to find our Pod listed there.
Congratulations! You have created your first Pod on Kubernetes!
Our happiness may be short-lived as we can see that the Pod is running a container with nginx listening at port 80. However, we won’t be able to access it by using http://127.0.0.1:80. This is understandable; the Pod is running inside a cluster and, by default, is not directly accessible.
Generally, we do not directly access Pods, but again, to get us started, we can use port-forward with kubectl, which establishes a tunnel to direct traffic from our host machine to the specified port on a Pod.
kubectl port-forward nginx-pod 8080:80
After running the command above, browse to http://127.0.0.1:8080 in the browser and you should be able to see the welcome page of the nginx server. Press “Ctrl + C” to end the port-forwarding session. We can now delete this Pod as we’ll manage them by creating Deployments.
kubectl delete pod nginx-podkubectl get pods
In the previous section, we created our first Pod, but we also learned that, in practice, we do not create Pods directly and use the workload resources, such as Deployments. In this section, we will create our first Deployment using the manifest below:
apiVersion: apps/v1kind: Deploymentmetadata:name: nginx-deploymentlabels:app: nginxspec:replicas: 2selector:matchLabels:app: nginx-podtemplate:metadata:labels:app: nginx-podspec:containers:- name: nginximage: nginx:1.25.1ports:- containerPort: 80
There are three important parts of this manifest. We name the Deployment as nginx-deployment and then create a ReplicaSet by specifying the number of replicas to be 2. We learned earlier that ReplicaSets ensures that a specified number of Pod replicas are running at all times. The Deployment name would guide the name of the replicas, as we’ll see later. Finally, we specify the Pods template in lines 12–21. This serves as an example to reiterate that we do not normally create Pods directly and manage them using higher-level concepts such as Deployments.
Let’s save the manifest in a file named nginx-deployment.yaml and then create a Deployment using the following command:
kubectl apply -f nginx-deployment.yamlkubectl get deployments
If all goes well, we should be able to see our Deployment in the list. We can notice 2/2 in the READY column that this matches our ReplicaSet specification. We can get a list of Pods to confirm this as well.
We can now test the availability of Deployment by deleting Pods and see how it automatically achieves the desired state by starting new Pods. We need to use the commands below:
kubectl get podskubectl delete pod nginx-deployment-7d6955794c-s8c2hkubectl get pods
The name of the Pod being deleted would be different for you but you’ll notice that as soon as a Pod is deleted, another one is instantiated with a different name. We can observe the AGE column to confirm the behavior.
Congratulations on creating your first Deployment!
Two small upgrades make a beginner Deployment more production-ready:
Health probes ensure Kubernetes only routes traffic to ready Pods and restarts unhealthy ones.
Resource requests/limits make sure Pods are scheduled on nodes with enough capacity and don’t starve neighbors.
spec:replicas: 2template:metadata:labels: { app: nginx }spec:containers:- name: nginximage: nginx:1.25ports: [{ containerPort: 80 }]readinessProbe:httpGet: { path: /, port: 80 }initialDelaySeconds: 3periodSeconds: 5livenessProbe:httpGet: { path: /, port: 80 }initialDelaySeconds: 10periodSeconds: 10resources:requests: { cpu: "100m", memory: "128Mi" }limits: { cpu: "300m", memory: "256Mi" }
The readiness probe gates traffic until the container is ready.
The liveness probe restarts it automatically if it gets stuck.
Requests guide scheduling, while limits prevent noisy-neighbor problems.
Keep app settings outside of container images using ConfigMaps and Secrets.
apiVersion: v1kind: ConfigMapmetadata: { name: web-config }data:WELCOME_MSG: "Hello from Kubernetes"---apiVersion: v1kind: Secretmetadata: { name: web-secret }type: OpaquestringData:API_TOKEN: "dev-only-token"
Mount them into the container environment:
env:- name: WELCOME_MSGvalueFrom: { configMapKeyRef: { name: web-config, key: WELCOME_MSG } }- name: API_TOKENvalueFrom: { secretKeyRef: { name: web-secret, key: API_TOKEN } }
This pattern keeps images generic and configurations environment-specific, making your Deployment both robust and portable.
We have learned that we can use the Service API to expose the application running on the Pods to the outside world. Services allow us to abstract away the underlying Pod IPs and provide services such as load balancing. Generally, multiple types of Services can be created, and many related use cases exist. We can create a Service for our Deployment using the command below:
kubectl expose deployment nginx-deployment --type=LoadBalancer --name=nginx-service --port=80
The kubectl expose allows to expose the Kubernetes objects, in our case, a Deployment, as a new Kubernetes Service. We can see the newly created Service in the list of services and get more details about the Service using the describe command as follows:
kubectl get serviceskubectl describe service nginx-service
There is a lot of information to cover in this blog, but the value of interest for us is in the NodePort field; it specifies a random port, which can be used to access the Service. Since we are using minikube for this blog, we can access the Service using the command below:
minikube service nginx-service
If everything goes fine, this will open up the welcome page of nginx. However, we are not directly accessing the Pods, and we can confirm this behavior by deleting the existing Pods and reaccessing the Service. We leave this as an exercise for you!
So far, the tutorial used a NodePort, which opens a high port on each node and forwards traffic to your Pods.
In a real cluster, you’ll usually choose between:
ClusterIP (default): Stable virtual IP, reachable only inside the cluster. Great for backend-to-backend communication.
NodePort: Opens a port on every node; simple but not ideal at scale.
LoadBalancer: Requests a managed load balancer from the cloud provider that forwards to your Service.
Ingress: A single HTTP(S) entry point that routes by host or path to multiple Services—cleaner than exposing every app directly.
apiVersion: v1kind: Servicemetadata: { name: web-clusterip }spec:type: ClusterIPselector: { app: nginx }ports: [{ port: 80, targetPort: 80 }]---apiVersion: v1kind: Servicemetadata: { name: web-nodeport }spec:type: NodePortselector: { app: nginx }ports: [{ port: 80, targetPort: 80, nodePort: 30080 }]---apiVersion: v1kind: Servicemetadata: { name: web-loadbalancer }spec:type: LoadBalancerselector: { app: nginx }ports: [{ port: 80, targetPort: 80 }]
On Minikube, you won’t get a real LoadBalancer, but you can still test Ingress.
Enable the addon:
minikube addons enable ingress
Create an Ingress that maps / to your Service:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata:name: web-ingressspec:rules:- http:paths:- path: /pathType: Prefixbackend:service:name: web-clusteripport: { number: 80 }
Apply and test:
kubectl apply -f web-ingress.yamlminikube tunnel # run in another terminal on some OSes
Ingress allows a single HTTP endpoint to front many apps and is the standard choice for user-facing services in Kubernetes.
Kubernetes is an open-source and extensible container orchestration platform. Kubernetes allows the management and coordination of clusters of containers across multiple hosts, providing services such as fault tolerance and scalability.
In this blog, we thoroughly covered the architecture and components of Kubernetes, coupled with an introduction to various key concepts. We provided a hands-on guide on deploying a sample application on minikube, a local Kubernetes cluster. We intentionally kept the presentation simple and focused on creating Pods, Deployment, and a Kubernetes Service. We encourage you to enrich your knowledge and understanding of Kubernetes by following the courses below.
Kubernetes is a powerful container management tool that's taking the world by storm. This detailed course will help you master it. In this course, you'll start with the fundamentals of Kubernetes and learn what the main components of a cluster look like. You'll then learn how to use those components to build, test, deploy, and upgrade applications and, as well as how to achieve state persistence once your application is deployed. Moreover, you'll also understand how to secure your deployments and manage resources, which are crucial DevOps skills. By the time you're done, you'll have a firm grasp of Kubernetes and the skills to deploy your own clusters and applications with confidence.
Businesses are modernizing and adopting multi-cloud approaches to deliver services. This shift in application deployment has given rise to containerization and Kubernetes for the deployment, management, and scaling of containerized applications. This course introduces you to serverless computing and shows you how to build serverless applications using Knative. It teaches CI/CD using Tekton and shows you how to build pipelines triggered by GitHub events. You will create a pipeline that builds container images using Build and, later, Cloud Native Buildpacks. In the last part of the course, you will build a web application that integrates with GitHub using a GitHub App, and triggers application build and deployment in response to GitHub events.