MERN to Kubernetes — Part 3
Setup Kubernetes Cluster and deploy MERN app
In Part 1 and Part 2 of this series I setup the MongoDB, the Express backend, and the React frontend for this project. In part 3 I will setup a Kubernetes cluster and deploy the app to it through Gitlab.
For this part of the guide you will need Docker Desktop installed on your workstation, a free Docker Hub account, a Google Cloud Account (if you don’t want to be charged anything for running this app on Google Cloud make sure your Google Cloud Account has the promotional credit), and a free Gitlab account
https://www.docker.com/products/docker-desktop
https://console.cloud.google.com/
Setup Kubernetes Cluster
If you already have access to a Google Cloud Kubernetes cluster then you can skip this section and go to deploying the app. For this demo I will setup a cluster on Google Cloud using the new GKE autopilot.
Login to the google cloud console at https://console.cloud.google.com/
Create a project and setup a billing account
In the navigate bar (upper left) go to Compute -> Kubernetes Engine
Enable the Kubernetes Engine API
Click “Create” and select “Configure” for the “Autopilot” option
Set the cluster name and id as you desire and leave everything else as default
Click the button at the bottom to create the cluster and wait for it to provision
Prepare App for Deployment
In the root directory of the project open the server.js file and find the following line::
app.use(express.static(path.join(__dirname, 'public')));
Replace it with:
app.use(express.static('frontend/build'));
Directly above the line that reads: module.exports = app; Add the following code:
app.get('*', function(req, res, next) {res.sendFile(path.resolve(__dirname, 'frontend', 'build', 'index.html'));});
When the app is deployed to the Kubernetes cluster this code will tell the express sever to send the frontend to the users browser
In the server.js file delete the following lines:
var indexRouter = require('./routes/index');var usersRouter = require('./routes/users');app.use('/', indexRouter);app.use('/users', usersRouter);
In the routes folder delete the file called index.js and users.js. These API endpoints were created by the npx express-generator and we don’t need them. There should only be the grocery.route.js file that you created earlier.
In the root of the project find the file called package.json and open it
Find the line:
"start": "node ./bin/www"
Add a comma to the end and directly below it add the following code:
"build": "cd frontend && npm run build"
In the root of the project delete the folder called public. Be sure you are deleting the public folder in the root (backend) and not the public folder that is inside the frontend folder
Create Dockerfile
In the root of your project create a new file called Dockerfile (no extension) and paste the following into it.
# Production Build# Stage 1: Build react clientFROM node:12-alpine as client# Working directory be appWORKDIR /usr/app/frontend/COPY frontend/package*.json ./# Install dependenciesRUN npm install# copy local files to app folderCOPY frontend/ ./RUN npm run build# Stage 2 : Build ServerFROM node:12-alpineWORKDIR /usr/src/app/COPY --from=client /usr/app/frontend/build/ ./frontend/build/COPY package*.json ./RUN npm installCOPY . .EXPOSE 4000CMD ["npm", "start"]
Go to https://hub.docker.com/ and sign up for a free account
Save the file and run the following commands in a terminal pointed to the root of the project to test the docker container.
$ docker login -u <your docker hub username> -p <your docker hub password>
$ docker build -t <your docker hub username>/<project-name>:latest .
$ docker run -p 4000:4000 <your docker hub username>/<project-name>:latest
If everything is working you should be able to go to localhost:4000 in your browser and use the grocery app.
Now that the docker container for the app is working, it’s time to setup the manifest files and configuration needed to deploy it to the Kubernetes cluster.
Kubernetes configuration
Setup Service Account
Login to your Google Cloud account and make sure that your project is selected in the upper left next to where it says “Google Cloud Platform”
In the navigation menu select “IAM & Admin”
In the left-hand pane select “Service Accounts”
Click “Create Service Account”
Set the name for the service account. I chose gitlab-cicd. Click “Create”
In the “roles” select “Kubernetes Engine Developer” and click “Continue”, then click “Done”
Click on the service account and go to the key section, click “Add Key” -> “Create New Key” and select JSON as the key type. A json file will download with the service account key information. You will use this later when setting up the Gitlab pipeline variables
Kubernetes Deployment Manifest
In the root of the project create a file called <project-name>-deploy.yaml
Open the file and add the following code (make sure exact indentation is preserved and replace all the values of the name and app fields with the name of your project:
apiVersion: apps/v1kind: Deploymentmetadata: name: mern-test labels: app: mern-testspec: replicas: 1 selector: matchLabels: app: mern-test template: metadata: labels: app: mern-test spec: containers: - name: mern-test image: jtp03a/mern-test:latest env: - name: DB_CONN valueFrom: secretKeyRef: name: mongodb-secrets key: mongodb_conn ports: - containerPort: 4000
Setup NodePort Service
In the root of the project create a file called service.yaml
Open the file and add the following, replacing the value in the app and name field with the name of your project
apiVersion: v1kind: Servicemetadata: name: mern-test-servicespec: type: NodePort selector: app: mern-test ports: - protocol: TCP port: 4000
Setup Gitlab
Go to https://gitlab.com/ and sign up for a free tier account
login to your new Gitlab account and create a project
Open your project and and on the left-hand navigation pane go to Settings -> CI/CD
Expand the Variables Section and add the following variables populated with your applicable information
DOCKER_USER — your Docker Hub username
DOCKER_PASSWORD — your Docker Hub password
GCP_PROJECT_ID — Your Google Cloud project ID
GCP_SERVICE_KEY — Copy the full contents of the json file you downloaded earlier when you created a service account into this variable including the open and closing brackets
MONGODB_CONN-your MongoDB connection URI
Create Gitlab CI yaml
In the root of the project create a new file called .gitlab-ci.yml (Be sure to include the leading period)
Open the file and add the following code replacing all the occurrences of <docker hub username> with your docker hub username, <project-name> with your project name, <cluster name> with your GKE cluster name, and <compute zone> with the region for your cluster:
stages: - build - deploybuild: stage: build image: docker:latest services: - name: docker:dind script: - docker login -u ${DOCKER_USER} -p ${DOCKER_PASSWORD} - docker pull <docker hub username>/<project-name>:latest || true - docker build --cache-from <docker hub username>/<project-name>:latest --tag <docker hub username>/<project-name>:latest . - docker push <docker hub username>/<project-name>deploy: stage: deploy image: google/cloud-sdk script: - echo $GCP_SERVICE_KEY > gcloud-service-key.json - gcloud auth activate-service-account --key-file gcloud-service-key.json - gcloud config set project $GCP_PROJECT_ID - gcloud container clusters get-credentials <cluster name> --zone=<compute zone> - echo $MONGODB_CONN > dbconn.txt - kubectl create secret generic mongodb-secrets --from-file=mongodb_conn=./dbconn.txt - kubectl apply -f service.yaml - kubectl apply -f deploy-mern-test.yaml
For subsequent deployments of your app you will need to alter the .gitlab-ci.yml file slightly to prevent errors
After the line:
- gcloud container clusters get-credentials <cluster name> --zone=<compute zone>
Add the following lines:
- kubectl delete deployment <project-name>- kubectl delete secret mongodb-secrets
This will delete the existing secret key for your MongoDB connection and the existing deployment for your app so that they can be updated and recreated with new iterations of your code. Alternatively if you don’t think you will ever need to change your MongoDB connection URI you could just remove the line from the .gitlab-ci.yml that creates the secret for subsequent deployments.
Push to Gitlab
In a terminal pointed to the root directly of the MERN app make sure everything is saved and commit all the changes to your app and add the remote for your Gitlab repository:
$ git add -A
$ git commit -m “demo MERN grocery list app”
**Change your-account and your-repository to match the info for your gitlab account and project**
$ git remote add origin https://gitlab.com/your-account/your-repository.git
$ git push -u origin master
Since you have a .gitlab-ci.yml file in your project this will automatically kick off a pipeline in Gitlab.
You can go to your Gitlab page and the CI/CD-> Pipelines section to see the progress of the pipeline and if it fails see what the errors are.
Setup Ingress
**It is important to note that setting up an ingress costs money outside of the free tier. If you don’t have an account with the trial credit setup you will be charged a small fee to have an ingress setup. You can use the Google Cloud pricing calculator to get a better idea of your costs, https://cloud.google.com/products/calculator, but it looks to me like a ingress for a small project costs about 20 dollars a month.**
The final step is to setup an ingress in Google Cloud so your app can be accessible externally. Make sure the Gitlab pipeline has completed successfully before attempting this step
In the Google Cloud Platform page go to the Navigation menu and find Kubernetes Engine -> Services & Ingress
You should see the NodePort for your app that you created as part of the CI/CD pipeline from the service.yaml file. The end point should be some <ip-address>:4000 TCP
Check the box for that NodePort service and click create ingress at the top of the page.
Ingress type should be set to: External HTTP/S load balancer
Set a name for your ingress. I used mern-test-ingress
Click host and path rules. If you have a domain name you want to use you can click “Add Host and Path Rule” and enter your domain name i.e www.yoursite.com in the host blank. Select the NodePort service for the Backend. If you don’t have a domain name just leave the host path rules as any unmatched and the backends as the NodePort service. Leave the frontend configuration as default and click “Create”
Give the ingress a few minutes to finish creating and once it says “OK” on the “Services & Ingress” you should be able to get your app from the IP address assigned to the ingress or the domain name if you linked it to one. (If you are trying to use a domain name you will need to go to your domain name provider and add an A record for the IP address assigned to your ingress.)
Cleanup
If you dont want to be charged or use up your free credit be sure to delete any services and ingresses in the “Services & Ingress” section and any clusters in the “Clusters” section
Thanks for reading. The next part in the series will deal with setting up code tests using Jest and SonarQube.