DEV Community

Back2Basics
Back2Basics

Posted on

Chat Application - Kubernetes

In this blog I will guide you on how to create Docker files and K8s manifests files.

Cloning repo

Clone the repo Chat App Repo

git clone https://github.com/mallikharjuna160003/MERN-Chat-App-deployment-AWS.git
Enter fullscreen mode Exit fullscreen mode

Dockerizing APP

Frontend app

Go the MERN-Chat-App-deployment-AWS/frontend directory.
We

# ------------------- Stage 01 -----------------------
# Use an official Node.js runtime as a parent image
FROM node:20.17.0 AS build

# Set the working directory
WORKDIR /usr/src/app 

# Copy package.json and package-lock.json
COPY package*.json ./

# Install dependencies
RUN npm install --legacy-peer-deps

# Copy the rest of the application code
COPY . .

# Build the application with the legacy OpenSSL provider option
RUN NODE_OPTIONS=--openssl-legacy-provider npm run build

# ------------------ Stage 02 --------------------
# Use Nginx to serve the app
FROM nginx:alpine

# Copy the build files to the Nginx server
COPY --from=build /usr/src/app/build /usr/share/nginx/html

# Expose the port Nginx is running on
EXPOSE 3000

# Start Nginx
CMD ["nginx", "-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode

This is multi stage docker file.

stage 01:

Building the dependencies and copying the source code from local to docker image work directory /usr/src/app.

Stage 02 :

Using nginx:alpine image copy the build directory from stage 01 to the nginx html directory /usr/share/nginx/html for serving static file.

We are exposing the container on port 3000. ["nginx", "-g", "daemon off;"] This is the command that will be executed when the container starts. It's telling the container to run Nginx with the -g flag, which allows you to pass configuration directives directly from the command line. The daemon off; directive ensures that Nginx runs in the foreground, which is necessary when running inside a Docker container since Docker expects the main process to run in the foreground. If Nginx runs as a background process (which is its default behavior), the container would immediately exit because there would be no foreground process.

Let's build the docker image

docker build -t a6j0n/frontend:latest .
Enter fullscreen mode Exit fullscreen mode

Now push it to docker hub. Make sure the docker account created. Here a6j0n is the username for dockerhub. We need to prefix the username for the image. It is convention used when pushing images to dockeruhub.

docker push a6j0n/frontend:latest
Enter fullscreen mode Exit fullscreen mode

list the images build in localhost

docker images
Enter fullscreen mode Exit fullscreen mode

Backend App:

Go the backend directory. Here is the Dockerfile. We are using the node:20-slim image to build the code. Installing the dependencies and copying source code to docker image direcrtory /usr/src/app. It is exposed on port 5000. While running the container from the image the CMD is taking the default arguments as ["node",'server.js']. These arguments can be overridden by docker argument. Best practice to use it run with the ENTRY_POINT.

ENTRYPOINT defines the command that cannot be overridden when the container is started, unless you explicitly specify a new ENTRYPOINT at runtime.

CMD defines default arguments to the ENTRYPOINT or provides the command to run if none is specified at runtime. If you pass additional arguments when running the container, they will replace
the CMD values.

# Use the official Node.js image as the base image
FROM node:20-slim

# Install required dependencies for certificates, curl (for healthchecks), and wget
RUN apt-get update && apt-get install -y \
    curl \
    wget \
    && rm -rf /var/lib/apt/lists/*

# Set the working directory in the container
WORKDIR /usr/src/app

# Copy package.json and package-lock.json
# COPY package*.json ./
COPY ./package.json ./
COPY ./package-lock.json ./

# Install dependencies
RUN npm install --legacy-peer-deps \
    && npm cache clean --force  # Clean npm cache to reduce image size

# Copy the application code
COPY . .

# Expose the application port
EXPOSE 5000

# Start the application
CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

Let's build the docker image

docker build -t a6j0n/backend:latest .
Enter fullscreen mode Exit fullscreen mode

Now push it to docker hub.

docker push a6j0n/backend:latest
Enter fullscreen mode Exit fullscreen mode

list the images build in localhost

docker images
Enter fullscreen mode Exit fullscreen mode

Creating K8s Manifest file

Frontend APP:

  1. We need the default.conf nginx directives file. Create a configmap and pass it as volume mount to the frontend deployment.
server {
    listen 80;
    listen [::]:80;
    server_name localhost;

    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

    location /api {
        proxy_pass http://backend-service:5000;  # Ensure this is correct
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location /socket.io/ {
        proxy_pass http://backend-service:5000;  # Ensure this points to your backend service
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;

        # CORS headers for WebSocket
        add_header 'Access-Control-Allow-Origin' 'http://localhost:3000';
        add_header 'Access-Control-Allow-Credentials' 'true';
    }

    location /app {
        proxy_pass http://localhost:3000;  # React app should be running here
    }

}
Enter fullscreen mode Exit fullscreen mode

store it in the default.conf file. Create configmap from the file. We are working in the default namespace only for this project.

kubectl create configmap nginx-conf --from-file=default.conf
#listing the configmap
kubectl get cm
Enter fullscreen mode Exit fullscreen mode
  1. Creating deployment with the above image build and pushed to dockerhub. Note: run docker login in the host for fetching images from dockerhub.
  2. Mount the configmap as voulume in the deployment.
  3. Creating Serving of ClusterIP.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-deployment
  labels:
    app: frontend
spec:
  replicas: 1  # Define how many frontend pods you want to run
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
        - name: frontend
          image: a6j0n/frontend:latest  # Replace with your actual image name
          ports:
            - containerPort: 80  # Port for your React app (or 3000, depending on your configuration)
          env:
            - name: REACT_APP_API_URL
              value: "http://backend-service:5000"  # Replace with the backend service URL
          volumeMounts:
          - name: nginx-conf
            mountPath: "/etc/nginx/conf.d/default.conf"
            subPath: "default.conf" 
            readOnly: true
      volumes:
      - name: nginx-conf
        configMap:
          name: nginx-conf

---
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  selector:
    app: frontend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80  # Make sure this matches the port in the frontend container
  type: ClusterIP  # You can use LoadBalancer or NodePort for external access

Enter fullscreen mode Exit fullscreen mode

Create Backend Deployment and service.

Create the backend deployment and service with the below manifest files. The backend deployment need the mongo database credentails. So, we create a secrets db-creds. The DB data key is pointing to the base64 encoded strings as values. We are running the backend at port 5000 and service at port 5000.

# db-creds.yaml
apiVersion: v1
data:
  MONGO_INITDB_DATABASE: Y2hhdGFwcA==
  MONGO_INITDB_ROOT_PASSWORD: ZXhhbXBsZQ==
  MONGO_INITDB_ROOT_USERNAME: cm9vdA==
kind: Secret
metadata:
  creationTimestamp: null
  name: db-creds
Enter fullscreen mode Exit fullscreen mode
# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: backend
          image: a6j0n/backend:latest 
          env:
            - name: DOCDB_ENDPOINT
              value: "mongo-service"
            - name: JWT_SECRET 
              value: "piyush"
            - name: PORT
              value: "5000"
            - name: MONGO_INITDB_ROOT_USERNAME 
              valueFrom:
                secretKeyRef:
                  name: db-creds
                  key: MONGO_INITDB_ROOT_USERNAME 
            - name: MONGO_INITDB_ROOT_PASSWORD 
              valueFrom:
                secretKeyRef:
                  name: db-creds
                  key: MONGO_INITDB_ROOT_PASSWORD
            - name: MONGO_INITDB_DATABASE 
              valueFrom:
                secretKeyRef:
                  name: db-creds
                  key: MONGO_INITDB_DATABASE 
          ports:
            - containerPort: 5000

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

Enter fullscreen mode Exit fullscreen mode

create the deployment and service and secrets from the the files created.

kubectl apply -f backend-deployment.yaml
kubectl apply -f db-creds.yaml
# check the deployments,services,secrets
kubectl get deploy,secrets,svc
Enter fullscreen mode Exit fullscreen mode

Database

The application requires the Mongodb. We are create the mongodb deployment with the mongo official images. Mounted the secrets we created earlier. For the backend pods to communicate with DB we also need the DB service running on port 27017.

DB-Deploy.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mongo-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mongo
  template:
    metadata:
      labels:
        app: mongo
    spec:
      containers:
        - name: mongo
          image: mongo:5.0
          env:
            - name: MONGO_INITDB_ROOT_USERNAME 
              valueFrom:
                secretKeyRef:
                  name: db-creds
                  key: MONGO_INITDB_ROOT_USERNAME
            - name: MONGO_INITDB_ROOT_PASSWORD 
              valueFrom:
                secretKeyRef:
                  name: db-creds
                  key: MONGO_INITDB_ROOT_PASSWORD
            - name: MONGO_INITDB_DATABASE 
              valueFrom:
                secretKeyRef:
                  name: db-creds
                  key: MONGO_INITDB_DATABASE
          ports:
            - containerPort: 27017

---
apiVersion: v1
kind: Service
metadata:
  name: mongo-service
spec:
  selector:
    app: mongo
  ports:
    - protocol: TCP
      port: 27017
      targetPort: 27017
  type: ClusterIP  # Headless service for direct communication with pods
Enter fullscreen mode Exit fullscreen mode

Installing Nginx-Ingress controller

This ingress setup need helm. Try installing from the official docs.

  1. Add the nginx-ingress controller to helm repo.
 helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
Enter fullscreen mode Exit fullscreen mode
  1. Update helm repo
helm repo update
Enter fullscreen mode Exit fullscreen mode
  1. Install the ingress-controller. It will auto create the ingress-nginx namespace along with the ingress controller.
helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace
Enter fullscreen mode Exit fullscreen mode
  1. Check all the components were present. The ingress controller namespace has the ingress controller pod, services like admisition controller,ingress controller service, deployment, replicaset.
kubectl get all -n ingress-nginx
Enter fullscreen mode Exit fullscreen mode
  1. Create Ingress resource. The ingress resource can be created in the namespace we want to the services to expose. Here I am running on localhost so host is localhost. The frontend will be serving at / prefix route and backend at /api/ route. The ports are frontend service port 80 and backend service port 5000. The ingress will match the requests in the the order we mentioned routes order.
# ingress-resource.yaml 
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  namespace: default
  annotations:
    #nginx.ingress.kubernetes.io/rewrite-target: /
    ingress.class: "nginx"
spec:
  ingressClassName: nginx  # Add this line to specify the ingress class
  rules:
  - host: localhost 
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

      - path: /api/
        pathType: Prefix 
        backend:
          service:
            name: backend-service
            port:
              number: 5000
Enter fullscreen mode Exit fullscreen mode

Try running the ingress service by portforwarding from 80 to 8080 on localhost.

kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller 8080:8080
Enter fullscreen mode Exit fullscreen mode

Finally we have the application running on localhost.

Image description

Top comments (0)