Kubernetes Revision

Kubernetes is designed to orchestrate containers.

Why is Container Orchestration Needed?

Imagine you have:

  1. An application split into multiple services (e.g., frontend, backend, database), each running in its own container.

  2. Multiple instances of these containers to handle high traffic.

  3. A need to ensure these containers stay healthy and restart if they fail.

Managing this manually for dozens or hundreds of containers would be overwhelming. Container orchestration automates these tasks.

Example: Running a Web App with Kubernetes

Let’s say you have a web application with three components:

  1. Frontend (React or Angular)

  2. Backend API (Node.js, Python)

  3. Database (MySQL)

Without Orchestration:

  • You manually start containers for each service.

  • You restart failed containers yourself.

  • You update each container manually during a new release.

With Kubernetes:

  1. Define Your Application in a YAML File

  2. Deploy to Kubernetes:

  3. Kubernetes Handles the Rest

  • A cluster is a collection of machines (physical or virtual) working together to run applications using Kubernetes. It consists of one master node and multiple worker nodes. Think of it as a team: the master node is the manager, and the worker nodes are the employees doing the tasks.

Master Node Components

  1. API Server:

    • Significance: Acts as the central communication hub for the cluster.

    • Function:

      • Receives commands from users (kubectl or other tools).

      • Passes those instructions to the other components.

    • Example: When you type kubectl apply -f deployment.yaml, the API server processes this request and instructs the cluster to deploy your application.

  2. Scheduler:

    • Significance: Decides which worker node will run a specific task or application.

    • Function:

      • Analyzes the available resources on worker nodes (CPU, memory).

      • Places tasks on the most suitable node.

    • Example: If worker node 1 is busy, it assigns the job to worker node 2.

  3. etcd:

    • Significance: The brain's memory—a distributed key-value store.

    • Function:

      • Stores all the cluster’s state and configuration data.

      • Ensures consistency across the cluster.

    • Example: If a node fails, Kubernetes consults etcd to determine what was running there and reschedules it on another node.

  4. Controller Manager:

    • Significance: Handles the cluster's overall health and operations.

    • Function:

      • Manages controllers.

      • Examples of controllers:

        • Node Controller: Monitors worker nodes and replaces failed ones.

        • Replica Controller: Ensures the correct number of app instances are running.

    • Example: If 3 replicas of your app are required, but 1 crashes, the Controller Manager restarts it automatically.


Worker Node Components

  1. Kubelet:

    • Significance: The worker node's agent that follows orders from the master node.

    • Function:

      • Ensures the containers on the worker node are running as instructed by the master.

      • Reports the node's status back to the master.

    • Example: The Kubelet ensures the frontend container is running and will restart it if it crashes.

  2. Kube-proxy:

    • Significance: Manages networking for apps in the cluster.

    • Function:

      • Routes traffic to the right container or pod.

      • Allows communication between containers, worker nodes, and the outside world.

    • Example: If a user accesses your app, kube-proxy directs their request to the appropriate container.

  3. Container Runtime:

    • Significance: The software that runs your containers.

    • Function:

      • Responsible for starting, stopping, and managing the lifecycle of containers.

      • Examples: Docker, containerd.

    • Example: If your app's container is built with Docker, the runtime runs it on the worker node.


How They Work Together

  1. You issue a command: kubectl apply -f app-deployment.yaml.

  2. The API Server receives the request and updates the desired state in etcd.

  3. The Scheduler assigns the task to a suitable worker node.

  4. The Controller Manager ensures that the required number of app instances are running.

  5. On the worker node:

    • Kubelet ensures the assigned containers are running.

    • Kube-proxy routes network traffic to the correct containers.

    • The Container Runtime (e.g., Docker) runs the containers.

What is kubectl?

kubectl (pronounced cube-control) is a command-line tool used to interact with a Kubernetes cluster. It acts as the interface between the user and the Kubernetes API Server, allowing you to manage, monitor, and troubleshoot applications and resources in your cluster.

What is a Pod in Kubernetes?

A Pod is the smallest and most basic unit of deployment in Kubernetes. It represents a single instance of a running process in your cluster. It can run one or more containers and share the same resources.

How Kubernetes Helps with Scalability, Load Balancing, High Availability, Rollouts, and Rollbacks?

  • Scalability:

  • Adds or removes instances when needed. Example:

    1. Imagine you run an online store, and during a sale, many users visit your site.

    2. You start with 2 instances of your app. As traffic increases, Kubernetes automatically scales up to 10 instances.

    3. When traffic drops, it scales back down to save resources.

  • Load Balancing:

  • Spreads traffic evenly across instances. Example:

    1. Your app has 3 pods running.

    2. A user visits your site; Kubernetes sends their request to Pod 1.

    3. Another user visits; their request is routed to Pod 2, and so on.

  • High Availability:

  • Keeps your app running, even during failures. Example:

    1. You have 5 pods running your app.

    2. If one pod crashes, Kubernetes automatically creates a new pod to replace it.

    3. If a worker node fails, Kubernetes reschedules the pods to other nodes.

  • Rollouts:

  • Updates your app without downtime. Example:

    1. You release a new version of your app (v2).

    2. Kubernetes starts replacing the old pods (v1) with new ones (v2), one at a time.

    3. Users don’t experience downtime because the old pods keep running until the new ones are ready.

  • Rollbacks:

  • Reverts to a stable version if things go wrong. Example:

    1. Your new version (v2) has a bug.

    2. Kubernetes quickly rolls back to the previous version (v1).

    3. Users won’t notice because the rollback happens smoothly.

What is minikube?

  • A tool to run a Kubernetes cluster locally on your computer.

  • It's like a mini Kubernetes lab for testing and learning.

  • It sets up a small, single-node Kubernetes cluster on your machine.

  • It’s ideal for experimenting and development without needing a full-fledged cloud setup.

Deployment of nginx for practicing Kubernetes :

At first , we create deployment ; then, we view the deployments and also the pods.

By default, Nginx listens on port 80 inside the container, but this port is inside the Pod's network namespace, not accessible directly from your local machine.

To make the Nginx service available outside the Pod, we can run the command :

kubectl expose deployment my-nginx --port=80 --type=LoadBalancer

  • This creates a Service in Kubernetes. A Service provides a stable network endpoint to access your Pods.

  • --port=80: Exposes the Pod's internal port 80 to the Service.

  • --type=LoadBalancer: Requests a cloud load balancer (or Minikube simulates one) to expose the Service externally.

Justification: A Service is necessary because Pods are ephemeral (they can restart or move to another node). A Service ensures a consistent way to access your Nginx app, regardless of where the Pod runs.

To check if the Service was created successfully, we can use the command : kubectl get services

This displays the list of Services, including:

  • ClusterIP: The internal IP assigned to the Service (accessible within the cluster).

  • External-IP: The external endpoint (if applicable).

  • Port: The port exposed by the Service.

Then we run the command minikube service my-nginx . Minikube translates the Kubernetes Service's external endpoint into a URL that you can access on your local machine. We can use minikube service my-nginx --url to obtain the accessible URL

On visiting the URL , through our local machine, we get :-

What is Minikube Dashboard?

Minikube Dashboard is a web-based user interface (UI) that allows you to visually interact with the resources running in your Minikube Kubernetes cluster. It provides a graphical view of various Kubernetes objects like Pods, Deployments, Services, ReplicaSets, and other components within your Minikube environment.

Demo WebApp Project :

At first, we will dockerize a simple react app by following the steps mentioned in my previous blog : https://iamkishaloy.hashnode.dev/docker-revision . Then we will build its image by a command like docker build -t kishaloy01/webapp-demo:02 . (since my Dockerhub username is kishaloy01). Then we will use docker login and enter our credentials properly ; then finally we will push the image like docker push kishaloy01/webapp-demo:02

Then we can use the command : kubectl create deployment webapp --image=IMAGEID (In my case, it was kubectl create deployment webapp --image=kishaloy01/webapp-demo:02 )

We can then check with get deployments and get pods commands :

We can then expose the PORT using service :

NOTE: We can also delete the undesirable deployments using the “delete” command.

For example :

NOTE: 1) The kubectl logs command is used to view the logs of a pod's container. It is helpful for debugging issues such as crashes, errors, or unexpected behavior within containers.

2) The kubectl describe command provides a detailed overview of a resource (such as a pod, deployment, or service) in the cluster. It includes metadata, status, events, and other useful information that helps to debug problems and understand resource configurations.

Concept of Rollout in Kubernetes:

In Kubernetes, "rollout" refers to the process of updating a deployment to use a new version of an application or configuration. Kubernetes manages the rollout automatically to ensure that your application remains available during the update.

Let use see an example :

1. Create a Deployment

Let’s create a deployment with version 1 of the application.

kubectl create deployment my-app --image=nginx:1.20

Check the pods:

kubectl get pods

2. Update the Deployment

Now, update the application to a new version (nginx:1.21).

kubectl set image deployment/my-app nginx=nginx:1.21

3. Rollout Status

You can track the progress of the rollout with:

kubectl rollout status deployment/my-app

This shows whether the update was successful or if there are any issues.

After the update, new pods will run nginx:1.21, while the old ones are terminated.

4. Rollback if Needed

If the new version has issues, you can rollback to the previous version.

kubectl rollout undo deployment/my-app

This restores the previous version (nginx:1.20).

Rollout Benefits

  • Zero Downtime: Old pods are gradually replaced with new ones to ensure continuous availability.

  • Rollback: Quickly revert to a stable version if something goes wrong.

  • Control: Manage updates at your own pace using commands like pause and resume.

Rollback in Kubernetes

A rollback in Kubernetes reverts a deployment to a previous version if a newer version has issues like bugs, misconfiguration, or instability. Kubernetes maintains a history of revisions for each deployment, making it easy to switch back to an earlier version. Example: Point 4 above .

Self-Healing in Kubernetes

Self-healing in Kubernetes means the system automatically detects and fixes problems with your application to ensure it remains healthy and available. Kubernetes uses built-in mechanisms to monitor your application's state and restore it if something goes wrong.

Kubernetes monitors your application's desired state (defined in your deployment) and compares it to the actual state:

  1. Desired State: The state you want.

  2. Actual State: The state Kubernetes observes in the cluster.

If the actual state doesn't match the desired state (e.g., a pod crashes), Kubernetes automatically:

  • Restarts failed pods.

  • Reschedules pods on different nodes if a node fails.

  • Recreates pods if they are deleted accidentally.

Example : Self-Healing in Action :-

Step 1: Create a Deployment

Deploy an application with 3 replicas:

Check the pods:


Step 2: Simulate a Failure

Manually delete one of the pods:

Check the pods again:

  • What Happened? Kubernetes noticed one pod was missing (actual state ≠ desired state) and created a new pod to maintain 3 replicas. (Note: In the above example , we can see the AGEs of the pods to understand the phenomenon).

YAML Configuration for Deployment and Service in Kubernetes

In Kubernetes, YAML files are used to define configurations for deploying applications and exposing them as services. These configurations describe what Kubernetes should do and the desired state of your application. Using YAML configurations for Kubernetes deployments and services streamlines application management by making it simpler, consistent, and scalable. It allows developers and DevOps teams to focus on innovation rather than managing infrastructure manually.

Step 1: Deployment YAML

This file creates a deployment with 2 replicas of an Nginx application:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-nginx
  template:
    metadata:
      labels:
        app: my-nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

Key Points:

  • replicas: 2: Runs 2 instances of the app.

  • selector: Matches pods with the app: my-nginx label.

  • containers:

    • image: nginx:latest: Pulls the Nginx image from Docker Hub.

    • containerPort: 80: Exposes port 80 inside the pod.

Step 2: Service YAML

This file exposes the pods created by the deployment:

apiVersion: v1
kind: Service
metadata:
  name: my-nginx-service
spec:
  selector:
    app: my-nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: LoadBalancer

Key Points:

  • selector: Matches pods with the app: my-nginx label.

  • ports:

    • port: 80: The port the service listens on.

    • targetPort: 80: The port the pods listen on.

  • type: LoadBalancer: Exposes the service to the internet.

How to Apply These Files

  1. Save the deployment YAML as deployment.yaml and the service YAML as service.yaml.

  2. Apply them using kubectl: kubectl get deployments and kubectl get services.

Note: We can see above : my-nginx-deployment and my-nginx-service

What is a Multi-Container App?

A multi-container app is an application where multiple containers work together to achieve a specific functionality. These containers are usually part of the same Pod in Kubernetes and share resources. Example: A Simple Web App with a Logger . Imagine a scenario where:

  • Container 1: Runs an Nginx web server to serve a website.

  • Container 2: Runs a logging service that collects and logs all the HTTP requests.

Step 1: YAML Configuration for Multi-Container Pod

Here’s the YAML file (multi-container.yml) to deploy the above example:

apiVersion: v1
kind: Pod
metadata:
  name: multi-container-pod
  labels:
    app: multi-container-app
spec:
  containers:
  - name: web-server
    image: nginx:latest
    ports:
    - containerPort: 80
  - name: logger
    image: busybox
    command: ["sh", "-c", "while true; do echo $(date) 'Access Log'; sleep 5; done"]

Explanation of YAML File

  1. Kind: Pod

    • We are creating a Pod that runs multiple containers.
  2. Containers Section:

    • web-server: Runs the Nginx image and listens on port 80.

    • logger: Runs the busybox image with a command to simulate logging by printing logs every 5 seconds.

Deploy and check the POD.

Check the logs of the logger container:

Step 2: Expose the Nginx Web Server:

Step 3: Verify Logs

Check the logs of the logger container again. You should see the simulated access logs being generated continuously.

NOTE : 1) What the Logs Show:
The logger container is running a simple command that outputs the current date and time, followed by the text "Access Log," every 5 seconds.

2) Purpose:

  • The logger container is acting as a logging utility within the pod.

  • It's periodically logging a timestamp along with the string "Access Log" to simulate logging activity.

Two Ways to Run Multi-Container Applications in Kubernetes


1. Multiple Containers in the Same Pod :

The one which we did above.

2. Multiple Containers in Separate Pods

Description:

  • Each container is deployed in its own Pod.

  • Communication happens via Kubernetes Services (e.g., ClusterIP, NodePort).

  • Used when containers are loosely coupled but need to interact (e.g., a front-end container and a back-end container).

Example Use Case:
A React front-end container running in one Pod and a Node.js API back-end container running in another Pod.

Deployment Example: Separate Pods with Services

  1. Front-End Pod (React): front-end.yaml
apiVersion: v1
kind: Pod
metadata:
  name: front-end
  labels:
    app: front-end
spec:
  containers:
  - name: react-app
    image: node:16
    command: ["sh", "-c", "npx create-react-app my-app && cd my-app && npm start"]
    ports:
    - containerPort: 3000
  1. Back-End Pod (Node.js): back-end.yaml
apiVersion: v1
kind: Pod
metadata:
  name: back-end
  labels:
    app: back-end
spec:
  containers:
  - name: node-api
    image: node:16
    command: ["sh", "-c", "while true; do echo 'API Running'; sleep 5; done"]
    ports:
    - containerPort: 5000
  1. Front-End Service: front-end-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: front-end-service
spec:
  selector:
    app: front-end
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3000
  type: NodePort
  1. Back-End Service: back-end-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: back-end-service
spec:
  selector:
    app: back-end
  ports:
  - protocol: TCP
    port: 80
    targetPort: 5000
  type: ClusterIP

Upon deployment , we get:

Access the front-end using the NodePort: minikube service front-end-service

IMPORTANT NOTE: command ["sh", "-c", "npx create-react-app my-app && cd my-app && npm start"] in front-end.yaml:

This is trying to create a new React app every time the pod starts, which might not be what you want in production. It will also take a long time each time the pod restarts, since create-react-app installs dependencies and sets up the app. For a more efficient deployment, you should consider using a pre-built React app or mount an already built app into the container.

Steps to Build and Use a Pre-Built React App:

  1. Build the React App Locally: First, you need to build the React app on your local machine.

    • Navigate to your React app directory on your local machine.

    • Run the following commands:

        npx create-react-app my-app  # only the first time
        cd my-app
        npm run build
      

This will create a build/ directory inside your project with all the static files needed for production.

  1. Use a Pre-Built Docker Image for React: You can create a custom Dockerfile that copies the build folder into the container and serves it using a web server like nginx.

    Example Dockerfile:

     # Use nginx as the base image
     FROM nginx:alpine
    
     # Copy the built React app into the nginx container
     COPY ./my-app/build /usr/share/nginx/html
    
     # Expose the default nginx port
     EXPOSE 80
    

    Steps:

    • Build the Docker image locally:

        docker build -t my-react-app .
      
    • Push it to a container registry like Docker Hub or your private registry:

        docker push my-react-app
      
  2. Update the front-end.yaml to Use the Custom Image:

    Now, modify your front-end.yaml to use the pre-built image instead of running npx create-react-app.

    Updated front-end.yaml:

     yamlCopy codeapiVersion: v1
     kind: Pod
     metadata:
       name: front-end
       labels:
         app: front-end
     spec:
       containers:
       - name: react-app
         image: my-react-app:latest  # Use the pre-built image from your registry
         ports:
         - containerPort: 80  # Serve the React app on port 80
    

    Apply the updated pod configuration:

     bashCopy codekubectl apply -f front-end.yaml
    

ConfigMaps

What are ConfigMaps?

  • ConfigMaps in Kubernetes are used to store configuration data that your application can consume as environment variables, command-line arguments, or configuration files.

  • You can think of them like a key-value pair storage, where the key is a configuration name, and the value is the setting or configuration for that key.

Simple Example: Let's say you have a web application that connects to a database. You want to store the database configuration (like the host, username, and password) outside your application code. You can store these in a ConfigMap.

Example:

apiVersion: v1
kind: ConfigMap
metadata:
  name: db-config
data:
  DB_HOST: "localhost"
  DB_USER: "admin"
  DB_PASS: "password123"

You can then use the above ConfigMap in your pod by referencing the values inside the container.

apiVersion: v1
kind: Pod
metadata:
  name: web-app
spec:
  containers:
  - name: web-container
    image: my-web-app
    envFrom:
    - configMapRef:
        name: db-config

In this case, the environment variables DB_HOST, DB_USER, and DB_PASS will be automatically populated inside the container from the ConfigMap.

Volume and Data

What are Volumes?

  • A volume in Kubernetes is a storage resource that can be used by containers to store data.

  • Volumes are useful when you want to persist data beyond the lifecycle of a container. By default, when a container is deleted, its data is lost. A volume allows data to persist even if the container restarts.

Simple Example: Imagine you're running a container that generates logs. You don't want to lose those logs if the container restarts, so you store them in a volume.

Example:

apiVersion: v1
kind: Pod
metadata:
  name: log-generator
spec:
  containers:
  - name: log-container
    image: log-generator-image
    volumeMounts:
    - mountPath: "/var/log"
      name: log-storage
  volumes:
  - name: log-storage
    emptyDir: {}

In this example:

  • The emptyDir volume is created when the pod starts and is used to store logs.

  • The logs will persist in the volume even if the container is restarted, but the volume will be deleted if the pod is deleted.

Persistent Volume (PV)

What is a Persistent Volume (PV)?

  • A Persistent Volume (PV) is a piece of storage in the cluster that has a lifecycle independent of any pod. It's like a long-term storage that can be used by multiple pods.

  • PVs are useful when you want to ensure data persists beyond the pod or node lifecycle, such as for a database that needs permanent storage.

  • Simple Example: Think of a database where you need to store data permanently. A Persistent Volume allows the database to keep its data even if the pod is recreated or moved to another node.

    Example:

      apiVersion: v1
      kind: PersistentVolume
      metadata:
        name: my-pv
      spec:
        capacity:
          storage: 5Gi
        accessModes:
          - ReadWriteOnce
        hostPath:
          path: "/data/pv"
    

    This Persistent Volume is created on the node where Kubernetes is running, and it stores data in the /data/pv path on the host machine.

    You can use this PV in a pod by creating a PersistentVolumeClaim (PVC).

    PersistentVolumeClaim (PVC):

      apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
        name: my-pvc
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 5Gi
    

    Now, the PVC can be used in a pod:

      apiVersion: v1
      kind: Pod
      metadata:
        name: database-pod
      spec:
        containers:
        - name: database
          image: my-database-image
          volumeMounts:
          - mountPath: "/data"
            name: database-storage
        volumes:
        - name: database-storage
          persistentVolumeClaim:
            claimName: my-pvc
    

    Here, the data in the /data folder will be stored in the Persistent Volume, ensuring that it persists even if the pod is deleted.

  • The above YAML configuration defines a Kubernetes pod with:

    • A container named "database" that uses the "my-database-image" Docker image.

    • A volume named "database-storage" that is mounted at the /data directory inside the container.

    • The volume is backed by a Persistent Volume Claim (PVC) named "my-pvc", ensuring that data in the /data directory persists even if the pod is restarted.

  • NOTE : Types of access modes for PV :

    • ReadWriteOnce (RWO): The volume can be mounted as read-write by a single node.

    • ReadOnlyMany (ROX): The volume can be mounted as read-only by multiple nodes.

    • ReadWriteMany (RWX): The volume can be mounted as read-write by multiple nodes.

What is Scaling in Kubernetes?

Scaling in Kubernetes means increasing or decreasing the number of replicas of your application to handle more traffic or save resources. This is done automatically or manually, depending on your configuration.


Types of Scaling in Kubernetes

  1. Horizontal Scaling:

    • Increases or decreases the number of pods running your application.

    • Example: If one pod can't handle traffic, Kubernetes can add more pods.

  2. Vertical Scaling:

    • Adjusts the CPU or memory resources allocated to a pod.

    • Example: If your application is slow, you can increase the pod's memory.


Example of Horizontal Scaling (Manual)

Scenario: You have a web app running with 2 replicas, but you expect a lot of traffic, so you scale it to 5 replicas.

YAML File Example:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 2  # Start with 2 replicas
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web-container
        image: nginx

Scaling Command:

kubectl scale deployment web-app --replicas=5

Now, Kubernetes creates 3 more pods, making it 5 pods in total.


Example of Horizontal Scaling (Automatic)

Scenario: Your web app experiences fluctuating traffic. You want Kubernetes to automatically scale the number of replicas based on CPU usage.

YAML File for Autoscaler:

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: web-app-autoscaler
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70  # Scale if CPU usage exceeds 70%

Deploy the autoscaler:

kubectl apply -f hpa.yaml

Now, Kubernetes will monitor CPU usage and adjust the replicas between 2 and 10 based on traffic.


Example of Vertical Scaling

Scenario: Your database pod needs more memory to process large queries.

Command to Edit Resources:

kubectl edit deployment database

Update Resources:

resources:
  requests:
    memory: "1Gi"  # Minimum memory
    cpu: "500m"
  limits:
    memory: "2Gi"  # Maximum memory
    cpu: "1"

Now, Kubernetes adjusts the resources for the database pod.


Summary

  • Horizontal Scaling: Adding/removing pods for your app.

  • Vertical Scaling: Changing resource limits (CPU/memory) for existing pods.

  • Autoscaling: Automatically adjusting resources based on demand.