Monday, 20 October 2025

Deploy PHP Applications on Kubernetes with Nginx and PHP-FPM

Our course you can check :-   Udemy course  


Ques:- 

Application Development team is planning to deploy one of the php-based applications on Kubernetes cluster. As per the recent discussion with DevOps team, they have decided to use nginx and phpfpm. Additionally, they also shared some custom configuration requirements. Below you can find more details. Please complete this task as per requirements mentioned below:

1) Create a service to expose this app, the service type must be NodePort, nodePort should be 30012.

2.) Create a config map named nginx-config for nginx.conf as we want to add some custom settings in nginx.conf.

a) Change the default port 80 to 8096 in nginx.conf.

b) Change the default document root /usr/share/nginx to /var/www/html in nginx.conf.

c) Update the directory index to index  index.html index.htm index.php in nginx.conf.

3.) Create a pod named nginx-phpfpm .

b) Create a shared volume named shared-files that will be used by both containers (nginx and phpfpm) also it should be a emptyDir volume.

c) Map the ConfigMap we declared above as a volume for nginx container. Name the volume as nginx-config-volume, mount path should be /etc/nginx/nginx.conf and subPath should be nginx.conf

d) Nginx container should be named as nginx-container and it should use nginx:latest image. PhpFPM container should be named as php-fpm-container and it should use php:8.2-fpm-alpine image.

e) The shared volume shared-files should be mounted at /var/www/html location in both containers. Copy /opt/index.php from jump host to the nginx document root inside the nginx container, once done you can access the app using App button on the top bar.

You can use any labels as per your choice


Ans:-

Here’s a complete Kubernetes setup based on your requirements for deploying a PHP-based application using nginx and php-fpm:

raj@jumphost ~$ cat pod.yaml


---

#Create the ConfigMap (nginx-config)

apiVersion: v1

kind: ConfigMap

metadata:

  name: nginx-config

data:

  nginx.conf: |

    events {}

    http {

      server {

        listen 8096;

        root /var/www/html;

        index index.html index.htm index.php;


        location / {

          try_files $uri $uri/ =404;

        }


        location ~ \.php$ {

          include fastcgi_params;

          fastcgi_pass 127.0.0.1:9000;

          fastcgi_index index.php;

          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        }

      }

    }


---

#Create the Pod (nginx-phpfpm)

apiVersion: v1

kind: Pod

metadata:

  name: nginx-phpfpm

  labels:

    app: php-nginx-app

spec:

  volumes:

    - name: shared-files

      emptyDir: {}

    - name: nginx-config-volume

      configMap:

        name: nginx-config

        items:

          - key: nginx.conf

            path: nginx.conf

  containers:

    - name: nginx-container

      image: nginx:latest

      volumeMounts:

        - name: shared-files

          mountPath: /var/www/html

        - name: nginx-config-volume

          mountPath: /etc/nginx/nginx.conf

          subPath: nginx.conf

    - name: php-fpm-container

      image: php:8.2-fpm-alpine

      volumeMounts:

        - name: shared-files

          mountPath: /var/www/html


---

#Create the Service (NodePort)

apiVersion: v1

kind: Service

metadata:

  name: php-nginx-service

spec:

  type: NodePort

  selector:

    app: php-nginx-app

  ports:

    - port: 8096

      targetPort: 8096

      nodePort: 30012



raj@jumphost ~$ kubectl apply -f pod.yaml

configmap/nginx-config created

pod/nginx-phpfpm created

service/php-nginx-service created


raj@jumphost ~$ kubectl get all

NAME               READY   STATUS    RESTARTS   AGE

pod/nginx-phpfpm   2/2     Running   0          16s


NAME                        TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE

service/kubernetes          ClusterIP   10.96.0.1      <none>        443/TCP          32m

service/php-nginx-service   NodePort    10.96.42.191   <none>        8096:30012/TCP   16s


raj@jumphost ~$ kubectl get cm

NAME               DATA   AGE

kube-root-ca.crt   1      32m

nginx-config       1      22s


Copy index.php to the shared volume
After the pod is running, execute the following command from the jump host:

raj@jumphost ~$ kubectl cp /opt/index.php nginx-phpfpm:/var/www/html/index.php -c nginx-container

raj@jumphost ~$ 


Accessing the App

Once the file is copied, you can access the app via the Node IP and NodePort (30012)


Conclusion:-

Learn how to deploy a PHP-based web application on a Kubernetes cluster using Nginx and PHP-FPM. This hands-on tutorial walks you through creating a ConfigMap for custom Nginx configuration, setting up a multi-container Pod with shared volumes, and exposing your application using a NodePort service. Perfect for DevOps engineers, application developers, and Kubernetes enthusiasts looking to understand real-world deployment patterns.

What you'll learn:

  • Create and use ConfigMaps for custom Nginx configuration
  • Deploy multi-container Pods with shared volumes
  • Use emptyDir volumes for inter-container communication
  • Expose applications using NodePort services
  • Copy files into running containers using kubectl cp
  • Access deployed PHP apps via browser

Sunday, 19 October 2025

Deploy a Secure LEMP Stack on Kubernetes with Secrets, ConfigMaps & Environment Variables

Our course you can check :-   Udemy course  


Ques:-   

DevOps team want to deploy a static website on Kubernetes cluster. They are going to use Nginx, phpfpm and MySQL for the database. The team had already gathered the requirements and now they want to make this website live. Below you can find more details:

Create some secrets for MySQL.

Create a secret named mysql-root-pass with key/value pairs as below:

name: password

value: R00t

Create a secret named mysql-user-pass with key/value pairs as below:

name: username

value: kodekloud_sam

name: password

value: Rc5C9EyvbU

Create a secret named mysql-db-url with key/value pairs as below:

name: database

value: kodekloud_db5

Create a secret named mysql-host with key/value pairs as below:

name: host

value: mysql-service

Create a config map php-config for php.ini with variables_order = "EGPCS" data.

Create a deployment named lemp-wp.

Create two containers under it. First container must be nginx-php-container using image webdevops/php-nginx:alpine-3-php7 and second container must be mysql-container from image mysql:5.6. Mount php-config configmap in nginx container at /opt/docker/etc/php/php.ini location.

5) Add some environment variables for both containers:

MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD and MYSQL_HOST. Take their values from the secrets you created. Please make sure to use env field (do not use envFrom) to define the name-value pair of environment variables.

6) Create a node port type service lemp-service to expose the web application, nodePort must be 30008.

7) Create a service for mysql named mysql-service and its port must be 3306.

We already have a /tmp/index.php file on jump_host server.

Copy this file into the nginx container under document root i.e /app and replace the dummy values for mysql related variables with the environment variables you have set for mysql related parameters. Please make sure you do not hard code the mysql related details in this file, you must use the environment variables to fetch those values.


Ans:-

Here is your generated Kubernetes YAML manifest for deploying the LEMP stack with:

  • Secrets for MySQL credentials
  • A php-config ConfigMap
  • A lemp-wp Deployment with two containers (Nginx+PHP and MySQL)
  • Environment variables sourced from secrets
  • Services for both the web app and MySQL


raj@jumphost ~$ cat pod.yaml


---

apiVersion: v1

kind: Secret

metadata:

  name: mysql-root-pass

type: Opaque

stringData:

  password: R00t


---

apiVersion: v1

kind: Secret

metadata:

  name: mysql-user-pass

type: Opaque

stringData:

  username: kodekloud_sam

  password: Rc5C9EyvbU


---

apiVersion: v1

kind: Secret

metadata:

  name: mysql-db-url

type: Opaque

stringData:

  database: kodekloud_db5


---

apiVersion: v1

kind: Secret

metadata:

  name: mysql-host

type: Opaque

stringData:

  host: mysql-service


---

apiVersion: v1

kind: ConfigMap

metadata:

  name: php-config

data:

  php.ini: |

    variables_order = "EGPCS"


---

apiVersion: apps/v1

kind: Deployment

metadata:

  name: lemp-wp

spec:

  replicas: 1

  selector:

    matchLabels:

      app: lemp-wp

  template:

    metadata:

      labels:

        app: lemp-wp

    spec:

      containers:

        - name: nginx-php-container

          image: webdevops/php-nginx:alpine-3-php7

          volumeMounts:

            - name: php-config-volume

              mountPath: /opt/docker/etc/php/php.ini

              subPath: php.ini

            - name: app-volume

              mountPath: /app

          env:

            - name: MYSQL_ROOT_PASSWORD

              valueFrom:

                secretKeyRef:

                  name: mysql-root-pass

                  key: password

            - name: MYSQL_DATABASE

              valueFrom:

                secretKeyRef:

                  name: mysql-db-url

                  key: database

            - name: MYSQL_USER

              valueFrom:

                secretKeyRef:

                  name: mysql-user-pass

                  key: username

            - name: MYSQL_PASSWORD

              valueFrom:

                secretKeyRef:

                  name: mysql-user-pass

                  key: password

            - name: MYSQL_HOST

              valueFrom:

                secretKeyRef:

                  name: mysql-host

                  key: host

        - name: mysql-container

          image: mysql:5.6

          env:

            - name: MYSQL_ROOT_PASSWORD

              valueFrom:

                secretKeyRef:

                  name: mysql-root-pass

                  key: password

            - name: MYSQL_DATABASE

              valueFrom:

                secretKeyRef:

                  name: mysql-db-url

                  key: database

            - name: MYSQL_USER

              valueFrom:

                secretKeyRef:

                  name: mysql-user-pass

                  key: username

            - name: MYSQL_PASSWORD

              valueFrom:

                secretKeyRef:

                  name: mysql-user-pass

                  key: password

            - name: MYSQL_HOST

              valueFrom:

                secretKeyRef:

                  name: mysql-host

                  key: host

      volumes:

        - name: php-config-volume

          configMap:

            name: php-config

        - name: app-volume

          emptyDir: {}


---

apiVersion: v1

kind: Service

metadata:

  name: lemp-service

spec:

  type: NodePort

  selector:

    app: lemp-wp

  ports:

    - protocol: TCP

      port: 80

      targetPort: 80

      nodePort: 30008


---

apiVersion: v1

kind: Service

metadata:

  name: mysql-service

spec:

  selector:

    app: lemp-wp

  ports:

    - protocol: TCP

      port: 3306

      targetPort: 3306



raj@jumphost ~$ kubectl apply -f pod.yaml 

secret/mysql-root-pass created

secret/mysql-user-pass created

secret/mysql-db-url created

secret/mysql-host created

configmap/php-config created

deployment.apps/lemp-wp created

service/lemp-service created

service/mysql-service created


raj@jumphost ~$ kubectl get all

NAME                           READY   STATUS              RESTARTS   AGE

pod/lemp-wp-7c544f45f5-zdwbs   2/2     Running              0          15s


NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE

service/kubernetes      ClusterIP   10.96.0.1       <none>        443/TCP        14m

service/lemp-service    NodePort    10.96.206.255   <none>        80:30008/TCP   15s

service/mysql-service   ClusterIP   10.96.66.225    <none>        3306/TCP       15s


NAME                      READY   UP-TO-DATE   AVAILABLE   AGE

deployment.apps/lemp-wp   1/1     1            1           15s


NAME                                 DESIRED   CURRENT   READY   AGE

replicaset.apps/lemp-wp-7c544f45f5   1         1         1       15s


Manually modify index.php on your jump host: Replace hardcoded MySQL values with environment variable references like this:

raj@jumphost ~$ cd /tmp

raj@jumphost /tmp$ cat index2.php


<?php

$dbname = getenv("MYSQL_DATABASE");

$dbuser = getenv("MYSQL_USER");

$dbpass = getenv("MYSQL_PASSWORD");

$dbhost = getenv("MYSQL_HOST");


$connect = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname);


if (!$connect) {

    die("Connection failed: " . mysqli_connect_error());

}


$test_query = "SHOW TABLES FROM $dbname";

$result = mysqli_query($connect, $test_query);


if (!$result) {

    die("Query failed: " . mysqli_error($connect));

}


echo "Connected successfully and query executed.";

?>


Copy the file into the running container:

raj@jumphost /tmp$ kubectl cp index2.php lemp-wp-7c544f45f5-zdwbs:/app -c nginx-php-container


raj@jumphost /tmp$ kubectl exec -it lemp-wp-7c544f45f5-zdwbs -c nginx-php-container -- bash


bash-4.3# cd /app

bash-4.3# pwd

/app

bash-4.3# ls

index2.php

bash-4.3# mv index2.php index.php

bash-4.3# ls

index.php

bash-4.3# 




Conclusion:-

In this hands-on DevOps tutorial, you'll learn how to deploy a secure LEMP stack (Linux, Nginx, MySQL, PHP) on a Kubernetes cluster. This course walks you through:

  • Creating and managing Kubernetes Secrets for MySQL credentials
  • Using ConfigMaps to configure PHP settings
  • Deploying a multi-container Deployment with Nginx, PHP-FPM, and MySQL
  • Injecting secrets into containers using environment variables
  • Mounting configuration files into containers
  • Exposing your static website using a NodePort service
  • Dynamically configuring your PHP application using environment variables

By the end of this video, you'll have a fully functional and secure LEMP-based static website running on Kubernetes — a must-have skill for any aspiring DevOps engineer.

Securely Store and Use License Keys in Kubernetes with Secrets and Volume Mounts

Our course you can check :-   Udemy course  


Ques:-  

DevOps team is working to deploy some tools in Kubernetes cluster. Some of the tools are licence based so that licence information needs to be stored securely within Kubernetes cluster. Therefore, the team wants to utilize Kubernetes secrets to store those secrets. Below you can find more details about the requirements:

We already have a secret key file blog.txt under /opt location on jump host. Create a generic secret named blog, it should contain the password/license-number present in blog.txt file.

Also create a pod named secret-datacenter.

Configure pod's spec as container name should be secret-container-datacenter, image should be debian with latest tag (remember to mention the tag with image). Use sleep command for container so that it remains in running state. Consume the created secret and mount it under /opt/demo within the container.

To verify you can exec into the container secret-container-datacenter, to check the secret key under the mounted path /opt/demo. 


Ans:-

Here’s the complete Kubernetes YAML manifest to meet your requirements:


Assuming you're on the jump host and blog.txt contains the license/password:


raj@jumphost ~$ cat /opt/blog.txt

5ecur3


This creates a secret named blog with a key blog and the value being the contents of blog.txt.


raj@jumphost ~$ kubectl create secret generic blog --from-file=blog=/opt/blog.txt

secret/blog created


Pod Definition with Secret Mounted


raj@jumphost ~$ cat pod.yaml


apiVersion: v1

kind: Pod

metadata:

  name: secret-datacenter

spec:

  containers:

    - name: secret-container-datacenter

      image: debian:latest

      command: ["sleep", "3600"]

      volumeMounts:

        - name: blog-secret

          mountPath: /opt/demo

          readOnly: true

  volumes:

    - name: blog-secret

      secret:

        secretName: blog


raj@jumphost ~$ kubectl apply -f pod.yaml

pod/secret-datacenter created


raj@jumphost ~$ kubectl get all

NAME                    READY   STATUS    RESTARTS   AGE

pod/secret-datacenter   1/1     Running   0          9s


Once the pod is running, you can verify the secret is mounted:


raj@jumphost ~$ kubectl exec -it secret-datacenter -- bash


Inside the container:You should see the contents of blog.txt.


root@secret-datacenter:/# cat /opt/demo/blog

5ecur3

root@secret-datacenter:/# 


Conclusion:-

In this hands-on tutorial, you'll learn how to securely manage sensitive data like license keys or passwords in a Kubernetes cluster using Kubernetes Secrets. We’ll walk through:

  • Creating a generic secret from a license key file (blog.txt)
  • Deploying a pod (secret-datacenter) with a Debian container
  • Mounting the secret as a volume inside the container at /opt/demo
  • Verifying the secret inside the running container

This video is ideal for DevOps engineers and Kubernetes practitioners who want to learn best practices for handling confidential data in containerized environments.


Saturday, 18 October 2025

Deploying a Web App with Persistent Storage in Kubernetes: PV, PVC, Pod & NodePort Explained

Our course you can check :-   Udemy course  


Ques:- 

DevOps team is working on a Kubernetes template to deploy a web application on the cluster. There are some requirements to create/use persistent volumes to store the application code, and the template needs to be designed accordingly. Please find more details below:

Create a PersistentVolume named as pv-xfusion. Configure the spec as storage class should be manual, set capacity to 3Gi, set access mode to ReadWriteOnce, volume type should be hostPath and set path to /mnt/security .

Create a PersistentVolumeClaim named as pvc-xfusion. Configure the spec as storage class should be manual, request 3Gi of the storage, set access mode to ReadWriteOnce.

Create a pod named as pod-xfusion, mount the persistent volume you created with claim name pvc-xfusion at document root of the web server, the container within the pod should be named as container-xfusion using image nginx with latest tag only (remember to mention the tag i.e nginx:latest).

Create a node port type service named web-xfusion using node port 30008 to expose the web server running within the pod.


Ans:-

Here's a complete Kubernetes YAML template that fulfills all the requirements you've listed:


Note:- We have used Init container for:-

This container will automatically create an index.html file inside the mounted volume before the Nginx container starts.
 

  • The initContainer runs first and writes an HTML file to the volume.
  • The Nginx container then serves this file from /usr/share/nginx/html.

  • ---

    #PersistentVolume (pv-xfusion)

    apiVersion: v1

    kind: PersistentVolume

    metadata:

      name: pv-xfusion

    spec:

      storageClassName: manual

      capacity:

        storage: 3Gi

      accessModes:

        - ReadWriteOnce

      hostPath:

        path: /mnt/security


    ---

    #PersistentVolumeClaim (pvc-xfusion)

    apiVersion: v1

    kind: PersistentVolumeClaim

    metadata:

      name: pvc-xfusion

    spec:

      storageClassName: manual

      accessModes:

        - ReadWriteOnce

      resources:

        requests:

          storage: 3Gi


    ---

    #Pod (pod-xfusion) with volume mounted at web server root and Init container 

    apiVersion: v1

    kind: Pod

    metadata:

      name: pod-xfusion

      labels:

        app: pod-xfusion

    spec:

      initContainers:

        - name: init-xfusion

          image: busybox

          command: ['sh', '-c', 'echo "<h1>Hello from Raj Gupta!</h1>" > /mnt/security/index.html']

          volumeMounts:

            - mountPath: /mnt/security

              name: xfusion-storage

      containers:

        - name: container-xfusion

          image: nginx:latest

          volumeMounts:

            - mountPath: /usr/share/nginx/html

              name: xfusion-storage

      volumes:

        - name: xfusion-storage

          persistentVolumeClaim:

            claimName: pvc-xfusion


    ---

    #NodePort Service (web-xfusion) on port 30008

    apiVersion: v1

    kind: Service

    metadata:

      name: web-xfusion

    spec:

      type: NodePort

      selector:

        # Ensure this matches the pod's labels if you add any

        # For now, we assume no labels, so selector will match pod name

        # Alternatively, you can add a label to the pod and match it here

        app: pod-xfusion

      ports:

        - protocol: TCP

          port: 80

          targetPort: 80

          nodePort: 30008


    Conclusion:-

    Learn how to deploy a web application on a Kubernetes cluster using PersistentVolumes and PersistentVolumeClaims to store application data. This hands-on tutorial walks you through:

    • Creating a PersistentVolume (pv-xfusion) using hostPath
    • Defining a PersistentVolumeClaim (pvc-xfusion) with storage class manual
    • Deploying an Nginx pod (pod-xfusion) with volume mounted at the web server root
    • Automating content creation using an initContainer
    • Exposing the pod using a NodePort service (web-xfusion) on port 30008

    By the end of this video, you'll understand how to manage persistent storage and expose services in Kubernetes effectively.

    Thursday, 16 October 2025

    Kubernetes Deployment Update & Rollback: Real-World DevOps Scenario

    Our course you can check :-   Udemy course  


    Ques:-   

    There is a production deployment planned for next week. DevOps team wants to test the deployment update and rollback on Dev environment first so that they can identify the risks in advance. Below you can find more details about the plan they want to execute.

    Create a namespace datacenter. Create a deployment called httpd-deploy under this new namespace, It should have one container called httpd, use httpd:2.4.28 image and 3 replicas. The deployment should use RollingUpdate strategy with maxSurge=1, and maxUnavailable=2. Also create a NodePort type service named httpd-service and expose the deployment on nodePort: 30008.

    Now upgrade the deployment to version httpd:2.4.43 using a rolling update.

    Finally, once all pods are updated undo the recent update and roll back to the previous/original version.


    Ans:-

    Here’s a step-by-step guide to help you execute the deployment update and rollback scenario in your Dev Kubernetes environment:

    Step 1: Create Namespace
    raj@jumphost ~$ kubectl create namespace datacenter
    namespace/datacenter created

    Step 2: Create Deployment httpd-deploy and service
    raj@jumphost ~$ cat pod.yaml

    ---
    # Save this as httpd-deploy.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: httpd-deploy
      namespace: datacenter
    spec:
      replicas: 3
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxSurge: 1
          maxUnavailable: 2
      selector:
        matchLabels:
          app: httpd
      template:
        metadata:
          labels:
            app: httpd
        spec:
          containers:
          - name: httpd
            image: httpd:2.4.28
            ports:
            - containerPort: 80

    ---
    # Save this as httpd-service.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: httpd-service
      namespace: datacenter
    spec:
      type: NodePort
      selector:
        app: httpd
      ports:
      - port: 80
        targetPort: 80
        nodePort: 30008

    raj@jumphost ~$ kubectl apply -f pod.yaml
    deployment.apps/httpd-deploy created
    service/httpd-service created

    raj@jumphost ~$ kubectl describe deploy httpd-deploy -n datacenter | grep -i image
        Image:         httpd:2.4.28

    Step 3: Upgrade Deployment to httpd:2.4.43
    raj@jumphost ~$ kubectl set image deployment/httpd-deploy httpd=httpd:2.4.43 -n datacenter
    deployment.apps/httpd-deploy image updated

    raj@jumphost ~$ kubectl describe deploy httpd-deploy -n datacenter | grep -i image
        Image:         httpd:2.4.43

    You can monitor the rollout status:
    raj@jumphost ~$ kubectl rollout status deployment/httpd-deploy -n datacenter
    deployment "httpd-deploy" successfully rolled out

    Step 4: Rollback to Previous Version
    raj@jumphost ~$ kubectl rollout undo deployment/httpd-deploy -n datacenter
    deployment.apps/httpd-deploy rolled back

    raj@jumphost ~$ kubectl describe deploy httpd-deploy -n datacenter | grep -i image
        Image:         httpd:2.4.28

    Optional: Verify Revision History
    raj@jumphost ~$ kubectl rollout history deployment/httpd-deploy -n datacenter
    deployment.apps/httpd-deploy 
    REVISION  CHANGE-CAUSE
    2         <none>
    3         <none>

    raj@jumphost ~$ 


    Here’s a detailed explanation of the Kubernetes deployment strategy block:


    strategy:
      type: RollingUpdate
      rollingUpdate:
        maxSurge: 1
        maxUnavailable: 2

    This configuration defines how Kubernetes updates the pods during a deployment change (like upgrading the image version). Let’s break it down:


    🔄 type: RollingUpdate

    This tells Kubernetes to use a rolling update strategy, which means:

    • Pods are updated gradually, not all at once.
    • It ensures high availability during the update.
    • Old pods are terminated only after new pods are up and running.

    ⚙️ rollingUpdate Parameters

    maxSurge: 1

    • This defines the maximum number of extra pods that can be created above the desired number of replicas during the update.
    • In your case, the deployment has 3 replicas, so during the update, Kubernetes can temporarily run up to 4 pods (3 existing + 1 new).
    • This helps speed up the update while maintaining availability.

    maxUnavailable: 2

    • This defines the maximum number of pods that can be unavailable during the update.
    • In your case, up to 2 pods can be taken down at a time while new ones are being created.
    • This allows Kubernetes to replace pods faster, but it also means only 1 pod will be serving traffic during the update.

    📊 How It Works Together

    Let’s say you’re updating from httpd:2.4.28 to httpd:2.4.43:

    1. Kubernetes starts by creating 1 new pod (due to maxSurge: 1).
    2. It then terminates up to 2 old pods (due to maxUnavailable: 2).
    3. This cycle continues until all old pods are replaced with new ones.
    4. At any point, there will be at least 1 pod running, ensuring minimal downtime.

    🛡️ Why This Matters in Production

    • maxSurge helps maintain availability by adding new pods before removing old ones.
    • maxUnavailable controls how many pods can be offline, balancing speed and reliability.
    • This strategy is ideal for critical services where zero downtime is preferred but fast rollout is also needed.

    Conclusion:-

    Learn how to safely test and execute deployment updates and rollbacks in Kubernetes using a real-world DevOps scenario. In this hands-on tutorial, we’ll walk through:

    • Creating a dedicated namespace for testing
    • Deploying an httpd application with a rolling update strategy
    • Exposing the deployment via a NodePort service
    • Performing a version upgrade using kubectl set image
    • Rolling back to the previous version without breaking revision history

    This video is ideal for DevOps engineers, SREs, and Kubernetes learners who want to master deployment strategies and risk mitigation techniques before production releases.

    Wednesday, 15 October 2025

    Deploying a Guestbook Application on Kubernetes: Redis & Frontend Setup Explained

    Our course you can check :-   Udemy course  


    Ques:-  

    Application development team has finished development of one of the applications and it is ready for deployment. It is a guestbook application that will be used to manage entries for guests/visitors. As per discussion with the DevOps team, they have finalized the infrastructure that will be deployed on Kubernetes cluster. Below you can find more details about it.

    BACK-END TIER

    Create a deployment named redis-master for Redis master.

    a.) Replicas count should be 1.

    b.) Container name should be master-redis-datacenter and it should use image redis.

    c.) Request resources as CPU should be 100m and Memory should be 100Mi.

    d.) Container port should be redis default port i.e 6379.

    Create a service named redis-master for Redis master. Port and targetPort should be Redis default port i.e 6379.

    Create another deployment named redis-slave for Redis slave.

    a.) Replicas count should be 2.

    b.) Container name should be slave-redis-datacenter and it should use gcr.io/google_samples/gb-redisslave:v3 image.

    c.) Requests resources as CPU should be 100m and Memory should be 100Mi.

    d.) Define an environment variable named GET_HOSTS_FROM and its value should be dns.

    e.) Container port should be Redis default port i.e 6379.

    Create another service named redis-slave. It should use Redis default port i.e 6379.

    FRONT END TIER

    Create a deployment named frontend.

    a.) Replicas count should be 3.

    b.) Container name should be php-redis-datacenter and it should use gcr.io/google-samples/gb-frontend@sha256:a908df8486ff66f2c4daa0d3d8a2fa09846a1fc8efd65649c0109695c7c5cbff image.

    c.) Request resources as CPU should be 100m and Memory should be 100Mi.

    d.) Define an environment variable named as GET_HOSTS_FROM and its value should be dns.

    e.) Container port should be 80.

    Create a service named frontend. Its type should be NodePort, port should be 80 and its nodePort should be 30009.


    Ans:-

    Here's the complete set of Kubernetes YAML manifests for deploying the guestbook application as described:


    raj@jumphost ~$ cat pod.yaml


    ---

    #Redis Master Deployment

    apiVersion: apps/v1

    kind: Deployment

    metadata:

      name: redis-master

    spec:

      replicas: 1

      selector:

        matchLabels:

          app: redis-master

      template:

        metadata:

          labels:

            app: redis-master

        spec:

          containers:

          - name: master-redis-datacenter

            image: redis

            resources:

              requests:

                cpu: "100m"

                memory: "100Mi"

            ports:

            - containerPort: 6379


    ---

    #Redis Master Service

    apiVersion: v1

    kind: Service

    metadata:

      name: redis-master

    spec:

      selector:

        app: redis-master

      ports:

      - port: 6379

        targetPort: 6379


    ---

    #Redis Slave Deployment

    apiVersion: apps/v1

    kind: Deployment

    metadata:

      name: redis-slave

    spec:

      replicas: 2

      selector:

        matchLabels:

          app: redis-slave

      template:

        metadata:

          labels:

            app: redis-slave

        spec:

          containers:

          - name: slave-redis-datacenter

            image: gcr.io/google_samples/gb-redisslave:v3

            resources:

              requests:

                cpu: "100m"

                memory: "100Mi"

            env:

            - name: GET_HOSTS_FROM

              value: "dns"

            ports:

            - containerPort: 6379


    ---

    #Redis Slave Service

    apiVersion: v1

    kind: Service

    metadata:

      name: redis-slave

    spec:

      selector:

        app: redis-slave

      ports:

      - port: 6379

        targetPort: 6379


    ---

    #Frontend Deployment

    apiVersion: apps/v1

    kind: Deployment

    metadata:

      name: frontend

    spec:

      replicas: 3

      selector:

        matchLabels:

          app: frontend

      template:

        metadata:

          labels:

            app: frontend

        spec:

          containers:

          - name: php-redis-datacenter

            image: gcr.io/google-samples/gb-frontend@sha256:a908df8486ff66f2c4daa0d3d8a2fa09846a1fc8efd65649c0109695c7c5cbff

            resources:

              requests:

                cpu: "100m"

                memory: "100Mi"

            env:

            - name: GET_HOSTS_FROM

              value: "dns"

            ports:

            - containerPort: 80


    ---

    #Frontend Service

    apiVersion: v1

    kind: Service

    metadata:

      name: frontend

    spec:

      type: NodePort

      selector:

        app: frontend

      ports:

      - port: 80

        targetPort: 80

        nodePort: 30009


    raj@jumphost ~$ kubectl apply -f pod.yaml 

    deployment.apps/redis-master created

    service/redis-master created

    deployment.apps/redis-slave created

    service/redis-slave created

    deployment.apps/frontend created

    service/frontend created


    raj@jumphost ~$ kubectl get all

    NAME                                READY   STATUS    RESTARTS   AGE

    pod/frontend-6bb8bc956b-7gjmg       1/1     Running   0          64s

    pod/frontend-6bb8bc956b-hqkj5       1/1     Running   0          64s

    pod/frontend-6bb8bc956b-ptgnf       1/1     Running   0          64s

    pod/redis-master-686856dd49-qf9lh   1/1     Running   0          64s

    pod/redis-slave-668f85fd-bbqgj      1/1     Running   0          64s

    pod/redis-slave-668f85fd-pfd49      1/1     Running   0          64s


    NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE

    service/frontend       NodePort    10.96.151.213   <none>        80:30009/TCP   64s

    service/kubernetes     ClusterIP   10.96.0.1       <none>        443/TCP        21m

    service/redis-master   ClusterIP   10.96.207.252   <none>        6379/TCP       64s

    service/redis-slave    ClusterIP   10.96.17.4      <none>        6379/TCP       64s


    NAME                           READY   UP-TO-DATE   AVAILABLE   AGE

    deployment.apps/frontend       3/3     3            3           64s

    deployment.apps/redis-master   1/1     1            1           64s

    deployment.apps/redis-slave    2/2     2            2           64s


    NAME                                      DESIRED   CURRENT   READY   AGE

    replicaset.apps/frontend-6bb8bc956b       3         3         3       64s

    replicaset.apps/redis-master-686856dd49   1         1         1       64s

    replicaset.apps/redis-slave-668f85fd      2         2         2       64s

    raj@jumphost ~$ 


    Conclusion:-

    Learn how to deploy a complete guestbook application on a Kubernetes cluster using Redis for backend data storage and a PHP-based frontend. This hands-on tutorial walks you through creating deployments and services for Redis master, Redis slave, and the frontend application. You'll understand how to configure container resources, environment variables, and expose services using NodePort.

    ✅ What you'll learn:

    • Creating Kubernetes deployments for Redis master and slave
    • Setting resource requests for containers
    • Defining environment variables in pods
    • Exposing services with ClusterIP and NodePort
    • Deploying a frontend application connected to Redis

    Perfect for DevOps engineers, SREs, and developers looking to gain practical experience with Kubernetes application deployment.

    Deploy MySQL on Kubernetes: Persistent Storage, Secrets & NodePort Access

    Our course you can check :-   Udemy course  


    Ques:-  

    A new MySQL server needs to be deployed on Kubernetes cluster. DevOps team was working on to gather the requirements. Recently they were able to finalize the requirements and shared them with the team members to start working on it. Below you can find the details:

    1.) Create a PersistentVolume mysql-pv, its capacity should be 250Mi, set other parameters as per your preference.

    2.) Create a PersistentVolumeClaim to request this PersistentVolume storage. Name it as mysql-pv-claim and request a 250Mi of storage. Set other parameters as per your preference.

    3.) Create a deployment named mysql-deployment, use any mysql image as per your preference. Mount the PersistentVolume at mount path /var/lib/mysql.

    4.) Create a NodePort type service named mysql and set nodePort to 30007.

    5.) Create a secret named mysql-root-pass having a key pair value, where key is password and its value is YUIidhb667, create another secret named mysql-user-pass having some key pair values, where frist key is username and its value is kodekloud_pop, second key is password and value is 8FmzjvFU6S, create one more secret named mysql-db-url, key name is database and value is kodekloud_db3

    6.) Define some Environment variables within the container:

    a) name: MYSQL_ROOT_PASSWORD, should pick value from secretKeyRef name: mysql-root-pass and key: password

    b) name: MYSQL_DATABASE, should pick value from secretKeyRef name: mysql-db-url and key: database

    c) name: MYSQL_USER, should pick value from secretKeyRef name: mysql-user-pass key key: username

    d) name: MYSQL_PASSWORD, should pick value from secretKeyRef name: mysql-user-pass and key: password


    Ans:-

    Here’s the complete set of Kubernetes YAML manifests to fulfill the MySQL deployment requirements you described:


    raj@jumphost ~$ cat pod.yaml


    ---

    #PersistentVolume (PV)

    apiVersion: v1

    kind: PersistentVolume

    metadata:

      name: mysql-pv

    spec:

      capacity:

        storage: 250Mi

      accessModes:

        - ReadWriteOnce

      persistentVolumeReclaimPolicy: Retain

      hostPath:

        path: /mnt/data/MySQL


    ---

    #PersistentVolumeClaim (PVC)

    apiVersion: v1

    kind: PersistentVolumeClaim

    metadata:

      name: mysql-pv-claim

    spec:

      accessModes:

        - ReadWriteOnce

      resources:

        requests:

          storage: 250Mi


    ---

    #Secrets

    apiVersion: v1

    kind: Secret

    metadata:

      name: mysql-root-pass

    type: Opaque

    data:

      password: WVVJaWRoYjY2Nw==  # Base64 encoded value of YUIidhb667


    ---

    apiVersion: v1

    kind: Secret

    metadata:

      name: mysql-user-pass

    type: Opaque

    data:

      username: a29kZWtsb3VkX3BvcA==  # Base64 encoded value of kodekloud_pop

      password: OEZtenp2RlU2Uw==      # Base64 encoded value of 8FmzjvFU6S


    ---

    apiVersion: v1

    kind: Secret

    metadata:

      name: mysql-db-url

    type: Opaque

    data:

      database: a29kZWtsb3VkX2RiMw==  # Base64 encoded value of kodekloud_db3


    ---

    #MySQL Deployment

    apiVersion: apps/v1

    kind: Deployment

    metadata:

      name: mysql-deployment

    spec:

      replicas: 1

      selector:

        matchLabels:

          app: mysql

      template:

        metadata:

          labels:

            app: mysql

        spec:

          containers:

          - name: mysql

            image: mysql:5.7

            ports:

            - containerPort: 3306

            env:

            - name: MYSQL_ROOT_PASSWORD

              valueFrom:

                secretKeyRef:

                  name: mysql-root-pass

                  key: password

            - name: MYSQL_DATABASE

              valueFrom:

                secretKeyRef:

                  name: mysql-db-url

                  key: database

            - name: MYSQL_USER

              valueFrom:

                secretKeyRef:

                  name: mysql-user-pass

                  key: username

            - name: MYSQL_PASSWORD

              valueFrom:

                secretKeyRef:

                  name: mysql-user-pass

                  key: password

            volumeMounts:

            - name: mysql-storage

              mountPath: /var/lib/mysql

          volumes:

          - name: mysql-storage

            persistentVolumeClaim:

              claimName: mysql-pv-claim


    ---

    #MySQL NodePort Service

    apiVersion: v1

    kind: Service

    metadata:

      name: mysql

    spec:

      type: NodePort

      selector:

        app: mysql

      ports:

      - port: 3306

        targetPort: 3306

        nodePort: 30007


    raj@jumphost ~$ kubectl apply -f pod.yaml 

    persistentvolume/mysql-pv created

    persistentvolumeclaim/mysql-pv-claim created

    secret/mysql-root-pass created

    secret/mysql-user-pass created

    secret/mysql-db-url created

    deployment.apps/mysql-deployment created

    service/mysql created


    raj@jumphost ~$ kubectl get all

    NAME                                    READY   STATUS    RESTARTS   AGE

    pod/mysql-deployment-6fb8c46c45-mcs74   1/1     Running   0          3m10s


    NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE

    service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP          28m

    service/mysql        NodePort    10.96.142.199   <none>        3306:30007/TCP   3m10s


    NAME                               READY   UP-TO-DATE   AVAILABLE   AGE

    deployment.apps/mysql-deployment   1/1     1            1           3m10s


    NAME                                          DESIRED   CURRENT   READY   AGE

    replicaset.apps/mysql-deployment-6fb8c46c45   1         1         1       3m10s


    raj@jumphost ~$ kubectl get secret

    NAME              TYPE     DATA   AGE

    mysql-db-url      Opaque   1      32s

    mysql-root-pass   Opaque   1      32s

    mysql-user-pass   Opaque   2      32s


    raj@jumphost ~$ kubectl get pvc

    NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE

    mysql-pv-claim   Bound    pvc-ef9dcc3e-94d0-4403-a3c8-a0714974acfe   250Mi      RWO            standard       38s


    raj@jumphost ~$ kubectl get pv

    NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                    STORAGECLASS   REASON   AGE

    mysql-pv                                   250Mi      RWO            Retain           Available                                                    59s

    pvc-ef9dcc3e-94d0-4403-a3c8-a0714974acfe   250Mi      RWO            Delete           Bound       default/mysql-pv-claim   standard                56s

    raj@jumphost ~$ 


    Conclusion:-

    In this practical Kubernetes tutorial, you'll learn how to deploy a MySQL server with secure credentials, persistent storage, and external access via NodePort. This video is ideal for DevOps engineers, cloud architects, and developers who want to understand how to manage stateful applications in Kubernetes.

    What you'll learn:

    • Creating a PersistentVolume and PersistentVolumeClaim for MySQL data
    • Using Kubernetes Secrets to securely manage MySQL credentials
    • Deploying MySQL with environment variables sourced from secrets
    • Mounting volumes for data persistence
    • Exposing MySQL using a NodePort service for external access

    By the end of this video, you'll have a fully functional MySQL deployment running in your Kubernetes cluster, ready for development or testing environments.