Todo: MERN in k8s
Published in
4 min readJul 19, 2022
This article aims to explain this small, containerized Todo project written in ReactJS and NestJS and how they work together in a Kubernetes cluster. A basic understanding of the said technologies is recommended but is not required. Note that the app only provides basic features since its purpose is for demonstration only.
These are the repo and Docker image links if you’re only interested in the project. For the explanation, see other sections below.
- Repo: https://github.com/alvinlucillo/k8s-todo
- Docker Hub: https://hub.docker.com/u/alvinlucillo
Already contains Docker images alvinlucillo/k8stodo-frontend:latest and alvinlucillo/k8stodo-backend:latest
Article Contents
1. Setting up the environment and the app
2. Project structure
3. Infrastructure
1. Setting up the environment and the app
- Before you run the app, you need to set up your environment first. Install the following. Note that the instructions may vary depending on your OS.
- Kubernetes: https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
- Docker: https://docs.docker.com/engine/install/ubuntu/
- Node/npm: https://nodejs.org/en/download/package-manager/
- Minikube: https://minikube.sigs.k8s.io/docs/start/ - Start minikube:
minikube start
- Enable ingress:
minikube addons enable ingress
- Inside the repo folder, enter
make build-app
to build the containers - Wait until all services are up; check it by running
kubectl get all
- It should look like this:
NAME READY STATUS RESTARTS AGE
pod/backend-depl-6dd885554c-g696m 1/1 Running 0 8m46s
pod/frontend-depl-69dbc48fcc-qqgwc 1/1 Running 0 8m46s
pod/mongo-depl-765bbddfc5-wkhbz 1/1 Running 0 8m46sNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend-srv ClusterIP 10.98.185.46 <none> 3001/TCP 8m46s
service/frontend-srv ClusterIP 10.97.44.16 <none> 3000/TCP 8m46s
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10h
service/mongo-srv ClusterIP 10.99.217.211 <none> 27017/TCP 8m46sNAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/backend-depl 1/1 1 1 8m46s
deployment.apps/frontend-depl 1/1 1 1 8m46s
deployment.apps/mongo-depl 1/1 1 1 8m46sNAME DESIRED CURRENT READY AGE
replicaset.apps/backend-depl-6dd885554c 1 1 1 8m46s
replicaset.apps/frontend-depl-69dbc48fcc 1 1 1 8m46s
replicaset.apps/mongo-depl-765bbddfc5 1 1 1 8m46s
- Access the app via the IP address of the minikube cluster. Get the IP address of the minikube cluster via
minikube ip
. If the IP address is 192.168.49.2, access it via http://192.168.49.2/. - When you access the app, it should look like this:
2. Project Structure
k8s-todo
│ Makefile — file with commands for building the app and images
└───backend — NestJS app for backend services
│
└───frontend — React app for frontend
│
└───infra — YAML files to create Kubernetes resources
3. Infrastructure
- Let’s start with ingress definition first:
infra/ingress-srv.yaml
— ingress routes the requests depending on the specified path
— so if the request has the path 192.168.49.2/, it will redirect the request to frontend-srv becausepath: /
means anything after the forward-slash except when there are other rules (check out the path for backend-srv).
—ingress-srv.yaml
defines which router pattern belongs to which service and to what port the request should be sent to
— for example, 192.168.49.2/api/todos will redirect the request to backend-srv at port 3001
— now what happens to the request? Here comes the service. - Services are defined in
infra/backend-depl.yaml
andinfra/frontend-depl.yaml
— ingress sends the request to a Kubernetes service
— a service allows pods to be accessible via the exposed ports
—port
defines the port to which a request is sent to reach the pod; if a pod or other systems want to reach the pod, they need to use the definedport
(except if there’s anodePort
which can be used too, but this project doesn’t use it)
—targetPort
defines the port that the pod is listening to; for example, if a Node application is listening to 3001, the container where the Node application is deployed should also listen to 3001. Therefore, if we want to let the request reach the Node app, we should use 3001 as thetargetPort
— what happens after a pod receives the request? Let’s talk about deployment and then the pod - Deployments and pods are defined also in
infra/backend-depl.yaml
andinfra/frontend-depl.yaml
— A deployment contains a pod and dictates how many instances (i.e., replicas) of the pod will be created based on the image and what are the images to be containerized (i.e., put an image into a container — it’s like running a NodeJS app in a new server)
— A pod may contain one or more containers but usually just one
— A container is like a VM (to put it simply but they’re different) mounted with a snapshot (what we call an image)
— An image contains the application files; it’s usually ready-made system files for use. - When you run
make build-app
, it does the following:
— creates the Kubernetes resources inside the infra folder; resources are the pods, services, and deployments
— pulls up the images from the Docker Hub (alvinlucillo/k8stodo-frontend:latest and alvinlucillo/k8stodo-backend:latest); these images are built before I released this article so you can make use of it. However, you’re free to create your own based on the frontend and backend of this repo (use the commands I created in the Make file)