How to Deploy Microservices (Java, Node.js, Python) Effectively on Kubernetes

Deploying a multi-service architecture with Java, Node.js, and Python on Kubernetes can be tricky. From containerization to networking and scaling, each service needs careful orchestration. Learn how to streamline deployments, optimize resources, and ensure seamless communication between services.

Table of contents

Microservices-based architectures are becoming increasingly popular in today's fast-paced software development environment. A typical multi-service architecture may consist of different services implemented using different programming languages. In this blog post, we'll walk through the key steps and best practices for deploying a multi-service architecture using Java, Node.js, and Python applications on Kubernetes.

To simplify and streamline this complex deployment process, we’ll also explore how Devtron can help you manage, monitor, and scale multi-service applications effortlessly on Kubernetes.

Prerequisites:

Before diving into the deployment, ensure you have the following:

  1. Kubernetes Cluster: Set up a Kubernetes cluster. This can be done using any Kubernetes service (GKE, EKS, AKS) or your own on-premise Kubernetes (e.g., MicroK8s, Minikube).
  2. Docker: Containerize each service (Java, Node.js, Python) using Docker.
  3. kubectl: The command-line tool for interacting with the Kubernetes API.

Deploying a Multi-Service Architecture Application on Kubernetes

In this section, we’ll walk through how to deploy a Java, Node.js, and Python service on Kubernetes effectively, covering:

  • Containerizing each service using Docker
  • Creating Kubernetes Deployment files for each service
  • Exposing services inside the Kubernetes cluster
  • Using Ingress to route external traffic

Step 1: Containerizing Your Applications

Before deploying to Kubernetes, we need to containerize each service using Docker.

Containerizing a Java Application (Spring Boot)

A typical Spring Boot application is packaged as a JAR file. Here’s a Dockerfile to containerize it:

FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/your-java-app.jar /app.jar
CMD ["java", "-jar", "/app.jar"]

Steps:

  1. The base image openjdk:11-jre-slim provides a lightweight Java runtime.
  2. The WORKDIR is set to /app.
  3. The application JAR is copied into the container.
  4. The CMD command runs the application inside the container.

Now, build and push the image:

docker build -t your-dockerhub-username/java-app .
docker push your-dockerhub-username/java-app

Containerizing a Node.js Application

For a Node.js service, the Dockerfile looks like this:

FROM node:16
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "app.js"]

Steps:

  1. Uses the official Node.js 16 image.
  2. Copies package.json and installs dependencies.
  3. Copies all source files and runs the app.js entry point.

Now, build and push the image:

docker build -t your-dockerhub-username/nodejs-app .
docker push your-dockerhub-username/nodejs-app

Containerizing a Python Application (Flask)

For a Python service using Flask, here’s a Dockerfile:

FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]

Steps:

  1. Uses Python 3.9 as the base image.
  2. Installs dependencies from requirements.txt.
  3. Copies the application files and starts the Flask app.

Now, build and push the image:

docker build -t your-dockerhub-username/python-app .
docker push your-dockerhub-username/python-app

Step 2: Creating Kubernetes Deployments

In order to deploy the Django application to Kubernetes, you first need to create a YAML manifest for the Kubernetes deployment and service objects. If you are unfamiliar with these concepts, please check out our blog to learn about the Kubernetes workload objects.

To keep our project structured and maintainable, we will create a dedicated folder called manifests to store all our Kubernetes YAML configuration files.

Java App Deployment 

We will call this manifest java-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: java-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: java-app
  template:
    metadata:
      labels:
        app: java-app
    spec:
      containers:
      - name: java-app
        image: your-dockerhub-username/java-app
        ports:
        - containerPort: 8080

Let’s understand what’s happening in the above YAML manifest.

  • Replicas: Runs 3 instances of the Java app.
  • Selector: Ensures pods are managed by this deployment.
  • Container: Pulls the Java image and exposes port 8080.

Node.js App Deployment

We will call this manifest nodejs-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodejs-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nodejs-app
  template:
    metadata:
      labels:
        app: nodejs-app
    spec:
      containers:
      - name: nodejs-app
        image: your-dockerhub-username/nodejs-app
        ports:
        - containerPort: 3000

Let’s understand what’s happening in the above YAML manifest.

  • Replicas: Runs 3 instances of the Node app.
  • Selector: Ensures pods are managed by this deployment.
  • Container: Pulls the Node image and exposes port 3000.

Python Deployment

We will call this manifest python-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: python-app
  template:
    metadata:
      labels:
        app: python-app
    spec:
      containers:
      - name: python-app
        image: your-dockerhub-username/python-app
        ports:
        - containerPort: 5000

Let’s understand what’s happening in the above YAML manifest.

  • Replicas: Runs 3 instances of the Python app.
  • Selector: Ensures pods are managed by this deployment.
  • Container: Pulls the Python image and exposes port 5000.

Next, we need to create the service manifest. The service will be used to expose the application pods to the outside world. We will be creating a service with the type NodePort. Please check out our blog on Kubernetes services if you are unfamiliar with them and the types of services.

Step 3: Exposing Services via Kubernetes Service Objects

A Kubernetes Service is an abstraction that enables communication between different parts of an application inside a cluster.

Without a Service, the pods created by the Deployment would not have a stable IP address, making inter-service communication difficult.

Kubernetes provides three main types of services:

  1. ClusterIP (default) → Makes the service accessible only within the cluster.
  2. NodePort → Exposes the service on a static port on each node.
  3. LoadBalancer → Exposes the service to the internet (useful for external APIs).

Each service must be exposed internally so the microservices can communicate.

Java Service (ClusterIP)

We will save the following service manifest in a file called java-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: java-app-service
spec:
  selector:
    app: java-app
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
  type: NodePort

Let’s break down what’s happening within this service manifest.

  • Kind: The type of Kubernetes object that is being created.
  • metadata.name: The name of this service object. In this case, it’s called java-app-service
  • Spec.selector: The pod labels that should be exposed by this service
  • Ports: Defining the protocol type and port numbers that should be exposed for the application
  • Type: The type of service that is being created.

Node.js Service (ClusterIP)

We will save the following service manifest in a file called nodejs-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: nodejs-app-service
spec:
  selector:
    app: nodejs-app
  ports:
    - protocol: TCP
      port: 3000
      targetPort: 3000
  type: NodePort

Let’s break down what’s happening within this service manifest.

  • Kind: The type of Kubernetes object that is being created.
  • metadata.name: The name of this service object. In this case, it’s called nodejs-app-service
  • Spec.selector: The pod labels that should be exposed by this service
  • Ports: Defining the protocol type and port numbers that should be exposed for the application
  • Type: The type of service that is being created.

Python Service (ClusterIP)

We will save the following service manifest in a file called python-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: python-app-service
spec:
  selector:
    app: python-app
  ports:
    - protocol: TCP
      port: 5000
      targetPort: 5000
  type: NodePort

Let’s break down what’s happening within this service manifest.

  • Kind: The type of Kubernetes object that is being created.
  • metadata.name: The name of this service object. In this case, it’s called python-app-service
  • Spec.selector: The pod labels that should be exposed by this service
  • Ports: Defining the protocol type and port numbers that should be exposed for the application
  • Type: The type of service that is being created.

Now that we have deployed our applications and created a Kubernetes Service of type NodePort, the next step is to expose it externally using Ingress.

An Ingress Controller helps route traffic from the internet to the appropriate services inside the Kubernetes cluster based on domain names or paths.

If you are unfamiliar with how Ingress works, check out our blog on Kubernetes Ingress for a detailed explanation.

Step 4: Exposing Services Using Ingress

An Ingress Controller allows us to route external traffic to our services using hostnames or paths.

Example Ingress configuration:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-service-ingress
spec:
  rules:
    - host: java-app.local
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: java-app-service
              port:
                number: 8080
    - host: nodejs-app.local
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: nodejs-app-service
              port:
                number: 3000
    - host: python-app.local
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: python-app-service
              port:
                number: 5000

This configuration enables:

  • java-app.local → Routes traffic to the Java service.
  • nodejs-app.local → Routes traffic to the Node.js service.
  • python-app.local → Routes traffic to the Python service.

Using these manifests, you can deploy the multi-service applications to the Kubernetes cluster. Please run the below command to apply the deployment and service manifest to your Kubernetes cluster.

kubectl apply -f manifests/

With this, you have successfully deployed the multi-service applications to the Kubernetes cluster.

Deploy applications with Devtron

Devtron is a comprehensive Kubernetes management platform designed to simplify the entire DevOps lifecycle, from the initial build stages to deployments and beyond. When it comes to deploying multi-service applications, Devtron minimizes the manual processes typically involved. It automates the creation of Dockerfiles, generates Kubernetes manifests, builds the application, and manages the deployment, all while adhering to industry best practices.

Let’s go through the steps of deploying an application on Kubernetes using Devtron.

Prerequisites

Before moving forward, let’s look at all the things you’ll require to follow along:

  1. An Application on GitHub

Devtron is installed and configured on your machine. If you haven’t installed Devtron, feel free to check out the well-managed documentation and join the discord community for help!

Step 1: Create a Devtron application and add the Git Repository

From Devtron’s home page, create a new Devtron application, and name it as demo-application

Devtron can build the application and create the container image. To do this, it needs the URL for the Git Repository. Check out the documentation to learn more about the application creation process.

Step 2: Configure the Build 

Devtron will automatically take the code from the Git repository and build the container image. For this, it requires an OCI Container Registry to push the build container image, and it requires a Dockerfile.

You can add the Container Registry from the Global Configurations.

For the build instructions, Devtron provides you with 3 options.

If you choose to create the Dockerfile within Devtron, you are offered several templates for popular programming languages and frameworks.

Step 3: Deployment Configurations

Next, you want to configure the actual deployment manifest that will be applied to the Kubernetes cluster. Unlike before, where you had to manually write all the YAML files, Devtron provides a YAML template where you can simply put in the configurations you want.

Step 4: Create the Build and Deploy Pipelines

Next, you want to create the CI (continuous integration) and CD (continuous deployment) pipelines.

The CI pipeline will take your application code from the configured Git Repository and branch and will build the Dockerfile and push it to a Docker Repository. You can also configure a security scan within the Build Pipeline.

The Deployment pipeline on the other hand will trigger the Deployment on the Kubernetes clusters in the configured environment.

In both the build and deployment pipelines, you can configure a Pre and Post stage, and configure them to take certain actions. For example, you can configure the Sonarqube plugin to scan the code for vulnerabilities in the Pre-CI stage.

Please check the documentation to learn more about the pipeline configurations.

Step 5: Trigger the Build and Deployment Pipelines

Once all the configurations are complete, all that’s left is to trigger the build stage, and subsequently the deployment.

From the Build & Deploy tab, select the Git material to trigger the build stage. The pipeline will build the container image.

Once the build stage is complete, you can trigger the deployment for the built artifact.

Upon triggering the deployment, Devtron will deploy the application to your GKE cluster, with the configurations that you created earlier.

Once the application is deployed, you will be able to see the application health, deployment status, security vulnerabilities, the Kubernetes resources of the application, and more.

Additionally, Devtron will also show the entire Build as well as Deployment history. In case of any errors in the deployment, you can also trigger a rollback to a previous deployment version.

Conclusion

In this blog, we explored the step-by-step process of configuring and deploying an application on a GKE  cluster. To summarize, we covered:

  • Configuring a GKE cluster
  • Creating Kubernetes manifests
  • Deploying the application on GKE
  • Streamlining deployments with Devtron

Devtron can help accelerate deployment velocity while also ensuring that deployments are more reliable. For working with Kubernetes, Devtron can act as the single solution you need. 

Check out Devtron’s GitHub page, and start deploying applications to Kubernetes.

FAQ

What is a multi-service architecture ?

A multi-service architecture consists of multiple microservices, each responsible for a specific function, deployed independently.

How should I structure my Kubernetes manifests for multi-service applications?

Each service should have:

  1. Deployment (for running containers)
  2. Service (for networking)
  3. ConfigMap/Secret (for configuration)

How do I monitor Java, Node.js, and Python services in Kubernetes?

To monitor Java, Node.js, and Python services in Kubernetes use Prometheus & Grafana for metrics and ELK (Elasticsearch, Logstash, Kibana) or Loki for logging.

How do I optimize Kubernetes costs when running multi-service architectures?

You can optimize your Kubernetes costs by right-sizing your resource requests & limits, by using Cluster Autoscaler to scale nodes dynamically, and by using Spot/Preemptible nodes for cost reduction.

How do I handle persistent storage for databases in Kubernetes?

You can handle persistent storage for databases in Kubernetes by using Persistent Volume (PV) & Persistent Volume Claim (PVC) for databases.

Related articles

Related articles