In this article, I will be working you through how to dockerizing a node.js application, using Kubernetes to orchestrate your containers, automating delivery using continuous integration, continuous delivery (CI/CD) using CircleCI and deploy to Google Cloud platform
This also served as my final project for the just concluded She Code Africa Cloud School Cohort 4.
Requirements
Docker Desktop
Account on CircleCI
Kubectl downloaded on your local machine
Cloud account, for this article I used Google cloud
Google cloud terminal Google SDK
Account on GitHub
Text editor, I used VS-code. You can use any text editor you are comfortable with it.
Step 1: Building the Application
The first thing we will do is to build the app. This application will be a simple index file and a node.js file.
Copy and paste these codes and save in an index.html file
<html>
<head>
<title>My first CICD Project</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="col-md-10 col-md-offset-1" style="margin-top:20px">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">MY first CICD Project</h3>
</div>
<div class="panel-body">
<div class="alert alert-success">
<p>This is a basic app used to demonstrate CI/CD on Google Cloud using K8s, CircleCI and Docker </p>
</div>
<div class="col-md-9">
<p>Author:</p>
<div class="col-md-3">
<a href="https://twitter.com/delan_hype">Dooshima</a>
</div>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
</body>
</html>
The node.js file will contain the following code, copy and paste the code in a server.js
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const bodyParser = require('body-parser');
/* eslint-disable no-console */
const port = process.env.PORT || 3000;
const app = express();
app.use(morgan('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: 'true' }));
app.use(bodyParser.json({ type: 'application/vnd.api+json' }));
app.use(express.static(path.join(__dirname, './')));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, './index.html'));
});
app.listen(port, (err) => {
if (err) {
console.log(err);
} else {
console.log(`App at: http://localhost:${port}`);
}
});
module.exports = app;
Next is to do is to create a package.json file, copy an paste this code in the file
{
"name": "dockerized nodejs application",
"version": "1.0.0",
"description": "This is a basic guide on dockerizing a node.js app, deploying to gcloud and setting a CI/CD.",
"main": "server.js",
"scripts": {
"start": "nodemon",
"test": "mocha"
},
"keywords": [
"gcloud",
"node"
],
"author": "Dooshima",
"license": "MIT",
"dependencies": {
"body-parser": "^1.17.2",
"express": "^4.15.3",
"morgan": "^1.8.2"
},
"devDependencies": {
"chai": "^4.1.2",
"mocha": "^10.2.0",
"request": "^2.88.0"
}
}
To keep updating the application when there are changes, we will use nodemon, so we will need to install nodemon
Run the code npm install nodemon
.
There are other dependencies that will be needed to for the application to run, to get them installed, run the code npm install .
and this will get all the dependencies installed.
Well done, we have finished building our application, next is ensure the application is running locally.
Run the code npm start
If you followed through and didn't miss any step, your terminal
should give you an output similar to this:
> dockerized nodejs application@1.0.0 start
> nodemon
[nodemon] 2.0.22
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server.js`
App at: http://localhost:3000
Navigate to the port your application is running on, and you can view your application in your browser.
Step 2: Dockerizing the Application
Now that our application is up and running, the next thing we will do is to dockerize the application
In the Requirements section of this application there is a link to download Docker Desktop if you don't Docker Desktop on your local machine already.
When dockerizing an image, we need a Dockerfile in your root directory; also remember that a Dockerfile starts with From
which indicates where your base image should be gotten from.
For the purpose of this application, our base image is node since our application is a node.js application
Copy the code below and paste in a Dockerfile which will be used to build the image
FROM node
ENV NPM_CONFIG_LOGLEVEL warn
RUN mkdir -p /usr/src/app
EXPOSE 3000
WORKDIR /usr/src/app
ADD package.json /usr/src/app/
RUN npm install --production
ADD . /usr/src/app/
ENTRYPOINT ["npm", "start"]
The docker build command will be used to build the docker image.
I am using the Visual Studio code terminal to build my image since I already have Docker installed on VS-code. To do that, you can check out this article. Alternatively you can use PowerShell terminal (ensure you are in the directory where your files are).
Run docker build -t sca-image .
to build your image where 'sca-image' is the name of our image.
Once the build is successful, your will see building finished alongside with the time taken to build the image in your terminal.
Check your Docker Desktop and if your build was successful, you will see the image in your image repository as seen below
Well done, we have dockerized our image let's take a look at our dockerized image.
When your image is dockerized and running, it has become a container which you can see in your container section of your Docker Desktop.
To see the running image (container) you will run the command
docker run -d -P --name docker sca-image
to run the image.
In your Docker Desktop the status of your image should change from unused which is what we had in the first image to in use as shown in the picture below:
Click on the image status, in use or go to the container section of your Docker Desktop so you can see your container.
Under the port section of your docker container, click on the port so you can see your dockerized image running in your browser
The port of the dockerized image(32768) is different from the normal port of the node.js application(3000).
Our dockerized is running on the Docker Desktop, however, we want it deployed to a cloud platform.
I will be using Google cloud platform for the seek of this article.
There is a link to sign up on Google cloud platform under the Requirements section if you don't have an account already.
On the Google platform, the Google container registry is where our image will be hosted.
On your Google cloud platform, create a project or select any project of your choice if you have one already
To create a project check out this article
To push the image to the Google cloud platform, I will be using the Google cloud SDK.
Run the gcloud init
to initialize gcloud in Google cloud terminal and follow the instructions in the terminal to set your project to correspond with the project ID you are using.
On your Google cloud platform, enable the container registry API so that images can be pushed it as shown in the image below:
The next thing is to push the image to the container registry, but before we do that, we will first tag the image.
docker tag sca-image gcr.io/sca-project-389601/sca-image:0.1.0
sca-project-389601 is the project ID which you can get from your project overview
gcr.io is the hostname
sca-image:0.1.0 is the image name
Now that the image is tagged, we will push it to Google cloud platform using this line of code
docker -- push gcr.io/sca-project-389601/sca-image:0.1.0
If your push was successful, your image should appear in the container registry of your repository
Well done on coming this far.
Step 3: Container Orchestration using Kubernetes
Now that we have succeeded in dockerizing our application and deploying on Google cloud platform, we will need orchestrate our containers using Kubernetes.
To run Kubernetes you need to install Kubectl on your local machine otherwise you will run into errors.
Right click on your Google SDK, run as administrator and install kubectl by running this line of code gcloud components install kubectl
To check that kubectl is installed check the version by running, kubectl version --client --output=yaml
which should give you an output in yaml
The next thing we are going to do is t create a Kubernetes cluster which this line of code will be used to create
gcloud container clusters create <CLUSTER_NAME> --zone <TIME_ZONE> --project <PROJECT_ID>
To set or check your Region/Zone, you can check out this here
Ensure to set the your cluster zone to be same as your project zone.
Check out this article to set your project Region/Zone
If your cluster was created successfully, you will your SDK Shell will return an output that gives you details about the location, master IP, number of nodes, status, etc.
This line of code gcloud container clusters list
can also give you the list of clusters you have and their status.
Kubernetes Configuration
Kubernetes configuration refers to resources that Kubernetes provides for configuring Pods. You can check out more details here
The code below will be used to configure our Kubernetes. Copy and paste in the deployment.yaml file
apiVersion: v1
kind: Service
metadata:
name: sca-image
labels:
app: sca-image
spec:
ports:
- port: 3000
selector:
app: sca-image
tier: frontend
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sca-image
labels:
app: sca-image
spec:
selector:
matchLabels:
app: sca-image
tier: frontend
strategy:
type: Recreate
template:
metadata:
labels:
app: sca-image
tier: frontend
spec:
containers:
- image: gcr.io/sca-project-389601/sca-image:0.1.0
name: sca-image
ports:
- containerPort: 3000
name: sca-image
Always remember to check the most recent Kubernetes API when you are declaring your configuration.
Run kubectl create -f deployment.yaml
in your the terminal to create your deployment.
Its important to ensure that your deployment was successful.
Run
kubectl get po
to get pod
kubectl get svc
to get service and
kubectl get deployment
to get deployment
Your terminal should give you an output like this
Navigate to the External IP of the image and your should see your image running in your browser
Here is mine:
In the search bar of my browser, you will see the External IP which corresponds to the External IP of the image in my terminal.
Our create cluster can also be viewed on the Google cloud platform
Well done on coming thus far, we are almost done.
Next we set the automation process so that changes to the application can be deployed continuously. To do that, we will be using CircleCI.
Step 4: Setting up Automation using CircleCI
To set up the automation on CircleCI a scripting file will be used to aid deployment which the CircleCI will run as a build step in deployment.
Copy and paste these codes in a deployment.sh file will be used as the scripting file
# !/bin/bash
set -e
echo "Deploying to ${DEPLOYMENT_ENVIRONMENT}"
echo $ACCOUNT_KEY_STAGING > service_key.txt
base64 -i service_key.txt -d > ${HOME}/gcloud-service-key.json
gcloud auth activate-service-account ${ACCOUNT_ID} --key-file ${HOME}/gcloud-service-key.json
gcloud config set project $PROJECT_ID
gcloud --quiet config set container/cluster $CLUSTER_NAME
gcloud config set compute/zone $CLOUDSDK_COMPUTE_ZONE
gcloud --quiet container clusters get-credentials $CLUSTER_NAME
docker build -t gcr.io/${PROJECT_ID}/${REG_ID}:$CIRCLE_SHA1 .
gcloud docker -- push gcr.io/${PROJECT_ID}/${REG_ID}:$CIRCLE_SHA1
kubectl set image deployment/${DEPLOYMENT_NAME} ${CONTAINER_NAME}=gcr.io/${PROJECT_ID}/${REG_ID}:$CIRCLE_SHA1
echo " Successfully deployed to ${DEPLOYMENT_ENVIRONMENT}"
Setting the CircleCI Configuration:
The scripting file provided for the deployment build is scripting file and with undefined variables. The variables will be defined and contained in a config.yml file which will also be used in deploying the application.
In your root directory create a .circleci folder and in the folder create a .circleci/config.yml file with the code below:
version: 2
jobs:
build:
docker:
- image: wecs/circle-ci-gcloud-node:0.1.0
working_directory: ~/workspace
environment:
DEPLOYMENT_NAME: sca-deploy
PROJECT_ID: sca-project-389601
CLUSTER_NAME: sca-cluster
CLUSTER_NAME_STAG: sca-stag
CLOUDSDK_COMPUTE_ZONE: us-west4-a
CONTAINER_NAME: sca-container
IMAGE: sca-image
REG_ID: sca-image
REG_ID_PROD: sca-prod
IMG_TAG: 0.1.0
PROJECT_NAME: sca-project
DEPLOYMENT_ENVIRONMENT: staging
steps:
- checkout
- setup_remote_docker
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-
- run:
name: Install node packages
command: |
npm install
- run:
name: Start app
command: |
npm start &
- run:
name: Run tests
command: |
npm test
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
- run:
name: Build and Deploy
command: |
if [ "${CIRCLE_BRANCH}" == "master" ]; then
./deployment.sh
fi
Push your code to your GitHub repository.
Now we are set to set up the CI/CD pipeline
In the Requirements section, there is a link to sign up on CircleCI if you don't have account already.
Once you have done that, setup the project with the code you are working on under the projects sections.
For the build to be successful, CircleCI will need to authenticate itself on Google cloud.
In your Google cloud console, create a service account
A .json key will be needed to authenticate CircleCI under the keys section of the just created Service account, click on Keys and select add key which will allow you to add a new key.
Once that is done, the json file will be downloaded. Copy the contents of the file and under your project settings, click on environment variables copy and paste the contents of the json key you downloaded earlier.
Once you have done all that, set up project by selecting the repo with the code
I will use the config.yml file in my repo
Click set up project and wait to see the status of your project
You can edit your index.html file to watch how your pipeline will run
With that, we have finished setting up our CI/CD pipeline
Well done and thank you for following to the end.
Keeping checking out this blog as I bring you DevOps content
Top comments (0)