Introduction

In this article, we will explore how to deploy a Node.js application using Kustomize on a Minikube cluster. We will cover the project structure, Dockerfile setup, Kubernetes manifests, and how to use Kustomize to manage different configurations for development, staging, and production environments. We will also discuss the advantages of using Kustomize over other alternatives.

Prerequisites

Before we begin, ensure you have the following installed on your machine:

Project Structure

Here is the structure of our project:

profile-app/
├── config/
│   ├── config.json
├── k8s/
│   ├── base/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   ├── pvc.yaml
│   │   ├── namespace.yaml
│   │   ├── kustomization.yaml
│   ├── overlays/
│   │   ├── dev/
│   │   │   ├── config.json
│   │   │   ├── kustomization.yaml
│   │   ├── prod/
│   │   │   ├── config.json
│   │   │   ├── kustomization.yaml
│   │   ├── staging/
│   │   │   ├── config.json
│   │   │   ├── kustomization.yaml
├── app.js
├── package.json
├── Dockerfile
├── docs/
│   ├── readme.md

Application Code

app.js

const express = require("express");
const fs = require("fs");

const app = express();
const PORT = process.env.PORT || 3001;

// Read the mounted config file
const CONFIG_PATH = "./config/config.json";
let config = { name: "Noel Bansikah", role: "DevOps Engineer", nameColor: "black", roleColor: "gray", environment: "development" };

// Check if the config file exists
if (fs.existsSync(CONFIG_PATH)) {
  config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
}

// Define a color map for meaningful colors
const colorMap = {
  black: "#000000",
  gray: "#6c757d",
  blue: "#007BFF",
  yellow: "#FFC107",
  red: "#DC3545",
  green: "#28A745"
};

// Get the colors from the config or default to black and gray
const nameColor = colorMap[config.nameColor] || colorMap.black;
const roleColor = colorMap[config.roleColor] || colorMap.gray;

app.get("/", (req, res) => {
  res.send(`
    
      
        ${nameColor};">Hello, I am ${config.name} 🚀
        ${roleColor};">Role: ${config.role}
        Environment: ${config.environment}
        GitHub: bansikah22
        GitLab: bansikah22
        LinkedIn: bansikah22
      
    
  `);
});

app.listen(PORT, () => console.log(`Server running on port ${PORT}...`));



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Dockerfile

# Stage 1: Build stage
FROM node:18-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production
COPY app.js ./

# Stage 2: Production stage
FROM node:18-slim
WORKDIR /app
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/node_modules /app/node_modules
COPY --from=build /app/app.js /app/app.js
CMD ["node", "app.js"]
EXPOSE 3001



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Building and Pushing the Docker Image
To build and push the Docker image to Docker Hub, follow these steps:

Build the Docker image:


docker build -t /profile-app:latest .



    Enter fullscreen mode
    


    Exit fullscreen mode
    






Push the Docker image to Docker Hub:


docker push /profile-app:latest



    Enter fullscreen mode
    


    Exit fullscreen mode
    




Replace  with your actual Docker Hub username.
  
  
  Kubernetes Manifests

  
  
  Base Manifests
The base directory contains the common configuration that is shared across all environments. This includes the deployment, service, PVC, and namespace definitions.
  
  
  k8s/base/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: profile-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: profile-app
  template:
    metadata:
      labels:
        app: profile-app
    spec:
      containers:
      - name: profile-app
        image: /profile-app:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 3001
        volumeMounts:
        - name: config-volume
          mountPath: /app/config
          subPath: config.json
          readOnly: false
      volumes:
      - name: config-volume
        configMap:
          name: profile-app-config



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  k8s/base/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: profile-service
spec:
  selector:
    app: profile-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3001
  type: ClusterIP



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  k8s/base/pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: profile-app-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  k8s/base/namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: profile-app



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  k8s/base/kustomization.yaml
The kustomization.yaml file in the base directory defines the resources and images used in the base configuration.

resources:
  - deployment.yaml
  - service.yaml
  - pvc.yaml
  - namespace.yaml

images:
  - name: profile-app
    newName: /profile-app
    newTag: latest



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Overlays
The overlays directory contains environment-specific configurations. Each environment (dev, prod, staging) has its own directory with a config.json file and a kustomization.yaml file.
  
  
  k8s/overlays/dev/config.json

{
  "name": "Noel Bansikah",
  "role": "DevOps Engineer and Software Developer",
  "nameColor": "blue",
  "roleColor": "green",
  "environment": "development"
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  k8s/overlays/dev/kustomization.yaml

namespace: dev
resources:
  - ../../base

configMapGenerator:
  - name: profile-app-config
    files:
      - config.json=config.json



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  k8s/overlays/prod/config.json

{
  "name": "Noel Bansikah",
  "role": "Senior DevOps Engineer",
  "nameColor": "red",
  "roleColor": "yellow",
  "environment": "production"
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  k8s/overlays/prod/kustomization.yaml

namespace: prod
resources:
  - ../../base

configMapGenerator:
  - name: profile-app-config
    files:
      - config.json=config.json



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  k8s/overlays/staging/config.json

{
  "name": "Noel Bansikah",
  "role": "DevOps Engineer",
  "nameColor": "green",
  "roleColor": "blue",
  "environment": "staging"
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  k8s/overlays/staging/kustomization.yaml

namespace: staging
resources:
  - ../../base

configMapGenerator:
  - name: profile-app-config
    files:
      - config.json=config.json



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Using Kustomize
Kustomize is a tool that allows you to customize Kubernetes resource configurations. It provides a way to manage different configurations for different environments without duplicating YAML files. Kustomize is built into kubectl, making it easy to use.
  
  
  Advantages of Kustomize


Declarative Management: Kustomize allows you to manage Kubernetes resources declaratively.

Environment-Specific Configurations: You can manage different configurations for different environments using overlays.

No Templating: Kustomize does not use templating, making it easier to understand and maintain.

Built into kubectl: Kustomize is integrated into kubectl, so you don't need to install any additional tools.

  
  
  Alternatives to Kustomize


Helm: Helm is a package manager for Kubernetes that uses templating to manage configurations. While Helm is powerful and widely used, it can be more complex to manage compared to Kustomize.

Ksonnet: Ksonnet was another tool for managing Kubernetes configurations, but it has been deprecated in favor of Kustomize and Helm.

  
  
  Why We Prefer Kustomize
We prefer Kustomize for our application because it provides a simple and declarative way to manage configurations for different environments. It is easy to use, integrated into kubectl, and does not require templating, making it easier to maintain.
  
  
  Deploying the Application

  
  
  1️⃣ Start Minikube

minikube start



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  2️⃣ Deploy to Kubernetes

kubectl apply -k k8s/overlays/dev
kubectl apply -k k8s/overlays/prod
kubectl apply -k k8s/overlays/staging



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  3️⃣ Test the App

  
  
  Port Forwarding

kubectl port-forward -n dev svc/profile-service 8080:80
kubectl port-forward -n staging svc/profile-service 8081:80
kubectl port-forward -n prod svc/profile-service 8082:80



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Access the App
Open your browser and navigate to:
Development: http://localhost:8080


Staging: http://localhost:8081


Production: http://localhost:8082


  
  
  4️⃣ Verify Deployments

kubectl get pods -n dev
kubectl get pods -n staging
kubectl get pods -n prod

kubectl get services -n dev
kubectl get services -n staging
kubectl get services -n prod



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  5️⃣ Check Logs to Confirm Colors

kubectl logs -n dev deployment/profile-app
kubectl logs -n staging deployment/profile-app
kubectl logs -n prod deployment/profile-app



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  6️⃣ Test Persistent Storage

  
  
  Check the Volume Mount

kubectl exec -n dev -it $(kubectl get pod -n dev -l app=profile-app -o jsonpath='{.items[0].metadata.name}') -- ls /app/config



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Restart the Pod & Check if Config Persists

kubectl delete pod -n dev -l app=profile-app
kubectl get pods -n dev -w



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  7️⃣ Verify the Updated Config

kubectl exec -n dev -it $(kubectl get pod -n dev -l app=profile-app -o jsonpath='{.items[0].metadata.name}') -- curl profile-service



    Enter fullscreen mode
    


    Exit fullscreen mode
    




Open http://localhost:8080 in your browser to see the updated configuration! 🎉
  
  
  Conclusion
In this article, we explored how to deploy a Node.js application using Kustomize on a Minikube cluster. We covered the project structure, Dockerfile setup, Kubernetes manifests, and how to use Kustomize to manage different configurations for development, staging, and production environments. We also discussed the advantages of using Kustomize over other alternatives.
LInk to the CodeIf you have any questions or face any challenges, feel free to ask in the comments section below. Happy coding!
  
  
  References

Kustomize Documentation
Minikube Documentation
Kubernetes Documentation
Happy coding! 🎉