Understanding Kubernetes Services: Deploying a Django Todo Web App Step-by-Step

Understanding Kubernetes Services: Deploying a Django Todo Web App Step-by-Step


📍Introduction

Welcome to my Kubernetes blog series, where I share my Kubernetes learnings and try to deep dive into topics. In this blog, we will explore the K8s service in detail and understand the difference between them by doing a hands-on project of deploying a Django Todo Web Application. Let's get started!🚀


📍Types of Services:

There are 4 types of services:

  1. ClusterIP

  2. NodePort

  3. LoadBalancer

  4. ExternalName

We will be seeing about the first 3 services with a hands-on project.

✔Cluster IP

The default type of service is ClusterIP so there is no need to mention the type while defining this service type. ClusterIP service provides a service proxy using the Cluster IP address to access the application at the service port. This solves the issue of directly accessing the pod IP address which is dynamically allocated causing it to change when the pod is crashed. It provides access to the application inside the cluster therefore, you can access it only when you are logged in to your K8s Cluster.

If you are using Minikube you can log into the cluster using the minikube ssh command. If you are using Kubeadm there is no need for login. It is mainly used for intra-cluster communication between pods or services. Examples: Database services, and microservices communication.

ClusterIP service provides the following functionalities:

  • Service Discovery

  • Load Balancing

✔NodePort

The NodePort service is mainly used to access our application outside the K8s cluster i.e. without logging into the cluster. When we define a NodePort service, it exposes a port called nodePort that we had defined in its manifest files on all the nodes, where we can access the application using the Node IP.

The nodePort defined in its Manifest file ranges between 30000-32767. It can be used by the organization. It is useful for development and testing or when you need external access to services in a non-production environment. Examples: Web applications, and APIs for testing.

NodePort service provides the following functionalities:

  • Service Discovery

  • Load Balancing

  • Exposing to the organization.

✔LoadBalancer

We were accessing the application within the cluster or the organization using the above 2 services. To access the application outside the system by an external user who can use a Public IP we use the LoadBalancer service. There are some other methods to make the application running inside a cluster externally accessible. This service can only be used when you are running the Kubernetes cluster on any Cloud Service Provider Platform like AWS, Azure, Google Cloud, etc.

Kubernetes does not provide a load balancing component, LoadBalancer service helps us to use a Load balancing component of the Cloud Service. This component allows the external traffic coming from the User via the Public/External IP to access the application from the browser.

LoadBalancer service provides the following functionalities:

  • Service Discovery

  • Load Balancing

  • Exposing to the outside world

For the ExternalName service, you can visit the official Kubernetes documentation on services: K8s Services

Now let's do a hands-on project and understand the above services in practice.🚀


📍Django Todo Web Application

Step 1: Set Up a Minikube or a Kubeadm cluster on your AWS EC2 instance. I will be executing this project on Minikube. You can refer to this guide: https://github.com/LondheShubham153/kubestarter

Step 2: Clone the project using the git clone <HTTP_URL> from this GitHub repository: https://github.com/LondheShubham153/django-todo-cicd

Step 3: Go to the k8s directory inside the project:

Step 4: Modify the deployment.yml file by adding a django namespace. Copy the below manifest file inside the deployment.yml file using the Vim editor.

apiVersion: apps/v1

kind: Deployment

metadata:

  name: todo-deployment

  namespace: django

  labels:

    app: todo-app

spec:

  replicas: 3

  selector:

    matchLabels:

      app: todo-app

  template:

    metadata:

      namespace: django

      labels:

        app: todo-app

    spec:

      containers:

      - name: todo-app

        image: trainwithshubham/django-todo:latest

        ports:

        - containerPort: 8000

Step 5: Similarly modify the service.yml file by adding the django namespace with the following content.

apiVersion: v1
kind: Service
metadata:
  name: todo-service
  namespace: django
spec:
  type: NodePort
  selector:
    app: todo-app
  ports:
      # By default and for convenience, the `targetPort` is set to the same value as the `port` field.
    - port: 80
      targetPort: 8000
      # Optional field
      # By default and for convenience, the Kubernetes control plane will allocate a port from a range (default: 30000-32767)
      nodePort: 30007

Step 6: Create the django namespace using the command kubectl create namespace django and verifying the namespace using the kubectl get namespace command:

Step 7: Apply the deployment manifest using the kubectl apply -f <fileame> command and verify the applied deployment using kubectl get deployment -n django:

Wait for the pods to be running keep verifying using the above command till you see the deployment and pods in the running state:

Once the pods are running we can access the application running on any of the pods using the pod IP.

Step 8: Use the kubectl get pods -n django -o wide command to get the pod IP:

Step 9: Open a separate terminal of the same instance where Minikube is running, log in to the cluster using minikube ssh command and access the application using the command curl -L <PodIP>:<containerport>:

curl -L <podIP>:8000

You can try to access the application outside the cluster you cannot:

Let's see in practice how the pod IP is changed when a new pod is created once we delete a pod:

And if you try to access the pod with the IP 10.244.0.5 you cannot since that pod is deleted.

Accessing the application with the newly created Pod IP:

Here, we have deleted the pod manually but in production, there will be many scenarios where a pod gets crashed and if the user tries to access the pod using the Pod IP it will not be able to. Therefore, let's create a Service and apply it.

We will be creating a ClusterIP service i.e. the default service:

Step 10: Create a svc.yml file and copy the below Service Manifest file and apply it:

( Try to write the service file on your own, to learn how to write a manifest file you can refer to my blog: Kubernetes Fundamentals: Understanding Pods, Deployments, Services, and Manifests )

apiVersion: v1
kind: Service
metadata:
  name: todo-service
  namespace: django
spec:
  selector:
    app: todo-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8000
  type: ClusterIP

Verify the service using the command kubectl get svc -n django:

Step 11: Access the application using the ClusterIP at the port defined in the service manifest file, where the service is listening at port 80 i.e. the default HTTP port, therefore, you can even directly access only using the ClusterIP (Login to your Minikube cluster ):

If you are using Kubeadm then no need to log in to the cluster.

Now that our service has been defined we can access the application regardless of the Pod IP changes since the service uses service discovery i.e. labels as a selector to apply the service.

Let's try and apply a Service of type NodePort and access the application outside the cluster without logging into it:

Step 12: Delete the ClusterIP service using kubectl delete svc <service_name> -n <namespace>:

Step 13: Apply the service.yaml file i.e. NodePort service present in the k8s directory

Step 14: Now, access the application using the NodeIP at the nodePort. Getting the NodeIP:

Step 15: Access the application using curl -L <NodeIP>:<nodePort>. In our case the nodePort is 30007 defined in the service file.

You can also try to access it on the browser using the same IP, make sure you allow the inbound traffic at port 30007 ( https://<NodeIP>:<nodePort> on the browser )

Now, let's try to access the application using a public IP with LoadBalancer service making it accessible for external users.

Step 16: Delete the NodePort service using kubectl delete svc <service_name> -n <namespace>:

Step 17: Create a service2.yml file and copy the below LoadBalancer Manifest file into it:

apiVersion: v1
kind: Service
metadata:
  name: todo-service
  namespace: django
spec:
  selector:
    app: todo-app
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8000

Step 18: Apply this service2.yml manifest file:

Step 19: Go to your second terminal and execute the minikube tunnel command. Make sure that you have allowed incoming traffic to port 80 at the inbound rules in the security group of your EC2 instance.

Step 20: After this, you will see the External IP:

Now, you can access the application using this External IP at the port 80 or at 31748


📍Facing an issue

I am unable to access the application using the external IP from the above Load Balancer service from my browser:

I am unable to ping from my local machine to the External IP even after adding ICMP rule to the security group of my instance (One of the solutions I got from the ChatGPT)

I am unable to troubleshoot this issue, I would highly appreciate your advice or suggestions regarding how to troubleshoot this issue in the comments section.


📍Conclusion

Thank you for reading this blog! 📖 Hope you have gained some value. If you want to practice Kubernetes make sure you do it on Minikube or at killercoda.com where you get K8s playground for free.

If you enjoyed this blog and found it helpful, please give it a like 👍, share it with your friends, share your thoughts, and give me some valuable feedback.😇 Don't forget to follow me for more such blogs! 🌟


📍Reference