Introduction
In the second part of our guide on How to prepare a NodeJS application for Kubernetes, we’ll dive into creating the Dockerfile and deployment yaml files needed for a smooth Kubernetes deployment. This section will guide you through writing the Dockerfile, crafting the necessary yaml configurations, and ensuring your NodeJS application is fully ready to run in a Kubernetes environment.
Procedure
In the following procedure we’ll see how to create Dockerfiles and yamls for our application.
Dockerfile files
We will need to create two Dockerfile files for our two sides of the application. One is the React side and the other is for NodeJS
Backend Dockerfile
Let’s create the backend NodeJS Dockerfile
:
# Use the official Node.js image as the base image
FROM node:20-bullseye
# Set the working directory
WORKDIR /app
# Copy package.json and package-lock.json files to the working directory
COPY package.json package-lock.json ./
# Install the dependencies
RUN npm install
# Copy the rest of the source code
COPY . .
# Expose the API port
EXPOSE 3000
# Start the backend server
CMD ["npm", "run", "start:backend"]
This will have a container with NodeJS server running in it.
Let’s use the file to build the image
$ cd ./k8s-worldcup/backend # this is important to copy the files for the application to the container.
$ docker build -f Dockerfile -t registry.apps.example.k8s.co.il/worldcup/worldcup-backend:1.0 .
Frontend Dockerfile
Let’s prepare the .dockerignore
file as well so we won’t copy unneeded folders:
node_modules
backend
Let’s create the frontend NodeJS Dockerfile
:
# Use the official Node.js image as the base image
FROM node:20-bullseye
# Set the working directory
WORKDIR /app
# Copy package.json and package-lock.json files to the working directory
COPY package.json package-lock.json ./
# Install the dependencies
RUN npm install
# Copy the rest of the source code
COPY src ./src
COPY public ./public
# Build the frontend React app
RUN npm run build
# Expose the default node port
EXPOSE 3000
# Start the backend server
CMD ["npm", "run", "start:k8s"]
This will have a container with NodeJS server running in it. Same as in the backend, but exposing port 80 intead.
Let’s use the file to build the image
$ cd ../worldcup-frontend
$ docker build -f Dockerfile -t registry.apps.example.k8s.co.il/worldcup/worldcup-frontend:1.0 .
NOTE: If you are running on Mac OSX, please use the following docker build command:
$ docker buildx build --platform linux/amd64 -f Dockerfile -t registry.apps.example.k8s.co.il/worldcup/worldcup-backend:0.1 .
Pushing the image to our registry
In order for the Kubernetes environment to be able to retrieve the images, we will need a registry running. You can use our How to Deploy Harbor Registry on Kubernetes . In order to host these images.
Login to the registry:
$ docker login registry.apps.example.k8s.co.il
Username: harbor-admin
Password:
Login Succeeded
Let’s push our images to our registry:
# backend
$ docker push registry.apps.example.k8s.co.il/worldcup/worldcup-backend:1.0
# frontend
$ docker push registry.apps.example.k8s.co.il/worldcup/worldcup-frontend:1.0
We have now the images prepared for the application to be running on Kubernetes.
Deployment files
Now we can deploy our application on Kubernetes. For this we will create a namespace, deployment, service and ingress.
Namespace
Let’s create a namespace:
apiVersion: v1
kind: Namespace
metadata:
labels:
kubernetes.io/metadata.name: worldcup
name: worldcup
name: worldcup
spec:
You can check it with k get namespaces
command.
NOTE: In case of a private registry with user/pass to access it, you need to create a secret.yaml for the credentials for retrieving the images. This is out of the scope of this document. We assume the image is publicly available.
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: worldcup
name: worldcup
namespace: worldcup
spec:
replicas: 1
selector:
matchLabels:
app: worldcup
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: worldcup
spec:
containers:
- image: registry.apps.example.k8s.co.il/worldcup/worldcup-frontend:0.1
imagePullPolicy: IfNotPresent
name: worldcup
ports:
- containerPort: 3000
protocol: TCP
env:
- name: REACT_APP_API_URL
value: "worldcup-backend.apps.example.k8s.co.il"
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: worldcup-backend
name: worldcup-backend
namespace: worldcup
spec:
replicas: 1
selector:
matchLabels:
app: worldcup-backend
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: worldcup-backend
spec:
containers:
- image: registry.apps.example.k8s.co.il/worldcup/worldcup-backend:0.1
imagePullPolicy: IfNotPresent
name: worldcup-backend
ports:
- containerPort: 3000
protocol: TCP
Let’s check the pods are runnning with k get pods -n worldcup
:
$ k get pods -n worldcup
NAME READY STATUS RESTARTS AGE
worldcup-54fb4bb855-t9npz 1/1 Running 0 13m
worldcup-backend-5b754b66d5-76ng7 1/1 Running 0 13m
Service
We shall now deploy our Service yaml:
apiVersion: v1
kind: Service
metadata:
labels:
app: worldcup
name: worldcup-service
namespace: worldcup
spec:
ports:
- port: 443
protocol: TCP
targetPort: 3000
selector:
app: worldcup
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
labels:
app: worldcup-backend
name: worldcup-backend
namespace: worldcup
spec:
ports:
- port: 443
protocol: TCP
targetPort: 3000
selector:
app: worldcup-backend
type: ClusterIP
We can see the service was created with k get service
:
$ k get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
worldcup-backend ClusterIP 10.101.33.155 <none> 443/TCP 1s
worldcup-service ClusterIP 10.101.36.59 <none> 443/TCP 3s
Ingress
Let’s deploy our ingress. With a certificate as well
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: ca-issuer
kubernetes.io/ingress.class: nginx
name: worldcup-ingress
namespace: worldcup
spec:
rules:
- host: worldcup.apps.example.k8s.co.il
http:
paths:
- backend:
service:
name: worldcup-service
port:
number: 443
path: /
pathType: Prefix
tls:
- hosts:
- worldcup.apps.example.k8s.co.il
secretName: worldcup-tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: ca-issuer
kubernetes.io/ingress.class: nginx
name: worldcup-ingress-backend
namespace: worldcup
spec:
rules:
- host: worldcup-backend.apps.example.k8s.co.il
http:
paths:
- backend:
service:
name: worldcup-backend
port:
number: 443
path: /
pathType: Prefix
tls:
- hosts:
- worldcup-backend.apps.example.k8s.co.il
secretName: worldcup-backend-tls
Let’s check our ingress was created with k get ingress -n worldcup
$ k get ingress -n worldcup
NAME CLASS HOSTS ADDRESS PORTS AGE
worldcup-ingress <none> worldcup.apps.example.k8s.co.il x.x.x.x 80, 443 12m
worldcup-ingress-backend <none> worldcup-backend.apps.example.k8s.co.il x.x.x.x 80, 443 12m
We now should have a running application:
Summary
In this continuation of How to prepare a NodeJS application for Kubernetes? Part 1, we focus on the practical aspects of deployment. We cover how to build a robust Dockerfile and prepare the essential deployment YAML files, ensuring your NodeJS application is Kubernetes-ready. At Octopus Computer Solutions, our Kubernetes expertise helps streamline the deployment process, ensuring your applications run efficiently and securely.
The entire git repository for files are at https://gitlab.octopuscs.com/dpointk/k8s-worldcup
Enjoy!