DEV Community

Cover image for How to Dockerize a node.js application, use Kubernetes to Orchestrate Containers, setup CI/CD and deploy on Cloud
gbamwuandooshima@gmail.com
gbamwuandooshima@gmail.com

Posted on

How to Dockerize a node.js application, use Kubernetes to Orchestrate Containers, setup CI/CD and deploy on Cloud

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>
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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"
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Navigate to the port your application is running on, and you can view your application in your browser.
node.js app

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"]
Enter fullscreen mode Exit fullscreen mode

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
docker image
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:
image in use
Click on the image status, in use or go to the container section of your Docker Desktop so you can see your container.
container
Under the port section of your docker container, click on the port so you can see your dockerized image running in your browser
dockerized image 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:
container API

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

image-repo

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
Enter fullscreen mode Exit fullscreen mode

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
deployment
Navigate to the External IP of the image and your should see your image running in your browser

Here is mine:

Kubectl deploy
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

cluster-on-cloud

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}"
Enter fullscreen mode Exit fullscreen mode

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  
Enter fullscreen mode Exit fullscreen mode

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.

setup project
For the build to be successful, CircleCI will need to authenticate itself on Google cloud.
In your Google cloud console, create a service account

service acct
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.
adding 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.

key upload
Once you have done all that, set up project by selecting the repo with the code
project setup
I will use the config.yml file in my repo
config.yml
Click set up project and wait to see the status of your project

status
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)