DEV Community

Cover image for Deploying a Node.js Application with Kustomize on Minikube
Tandap Noel Bansikah
Tandap Noel Bansikah

Posted on

Deploying a Node.js Application with Kustomize on Minikube

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
Enter fullscreen mode Exit fullscreen mode

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(`
    <html>
      <body style="background-color: white; text-align: center; padding: 50px;">
        <h1 style="color: ${nameColor};">Hello, I am ${config.name} 🚀</h1>
        <h2 style="color: ${roleColor};">Role: ${config.role}</h2>
        <h3>Environment: ${config.environment}</h3>
        <p>GitHub: <a href="https://github.com/bansikah22">bansikah22</a></p>
        <p>GitLab: <a href="https://gitlab.com/tandapnoelbansikah">bansikah22</a></p>
        <p>LinkedIn: <a href="https://linkedin.com/in/tandapnoelbansikah">bansikah22</a></p>
      </body>
    </html>
  `);
});

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 ./
RUN npm install

# Stage 2: Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=build /app/node_modules /app/node_modules
COPY 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:

  1. Build the Docker image:
docker build -t <your-dockerhub-username>/profile-app:latest .
Enter fullscreen mode Exit fullscreen mode
  1. Push the Docker image to Docker Hub:
docker push <your-dockerhub-username>/profile-app:latest
Enter fullscreen mode Exit fullscreen mode

Replace <your-dockerhub-username> 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: <your-dockerhub-username>/profile-app:latest
        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: <your-dockerhub-username>/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:

Dev

Staging

Production

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

Verify Mount

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 Code

If you have any questions or face any challenges, feel free to ask in the comments section below. Happy coding!

References

Happy coding! 🎉

Top comments (0)