CI/CD with Jenkins pipeline & Nodejs into K8S (Part-1)
Tags: Jenkins, Jenkins Pipeline, Nodejs, Continuous Delivery, Continuous Integration

CI/CD — DevOps

This is a series on how to CI/CD a Nodejs application using Jenkins as an integration server locally on a personal computer

We’re going to see how to continuously deploy a Node.js application with Jenkins based on changes made to a GitHub repository. So let’s figure out the plan here. We’re going to build a Node.js app and upload it to Github. When changes are made to this repository, Jenkins will build the application and deploy or run the application.

In this part, we will see what is CI/CD, build a simple NodeJs application, build a docker image, and push it to DockerHub and deploy it manually into Minikube

Prerequisite:

NodeJs 8+

Docker installed locally(docker desktop or docker toolbox on windows)

Minikube

What is CI/CD?

Continuous Integration (CI) is a development practice that requires developers to integrate code into a shared repository several times a day. Each check-in is then verified by an automated build, allowing teams to detect problems early. By integrating regularly, you can detect errors quickly, and locate them more easily. Continuous delivery (CD) is an extension of continuous integration to make sure that you can release new changes to your customers quickly in a sustainable way. This means that on top of having automated your testing, you also have automated your release process and you can deploy your application at any point of time by clicking on a button.

Let's start building a simple node app to deliver a “Hello world” message.

Create a folder and run the npm init command to initialize a package.json file.

//package.json

{

"name": "hello-world",

"version": "1.0.0",

"description": "",

"main": "server.js",

"scripts": {

"test": "echo \"Error: no test specified\" && exit 1"

},

"author": "sampath",

"license": "ISC",

}

2.Install express module

npm i express --save

Now the packag.json looks like

{

"name": "hello-world",

"version": "1.0.0",

"description": "",

"main": "server.js",

"scripts": {

"test": "echo \"Error: no test specified\" && exit 1"

},

"author": "sampath",

"license": "ISC",

"dependencies": {

"express": "^4.17.1"

}

}

3.Create a file named server.js and put the following code

const express = require('express');

const PORT = 8000;

const app = express();

app.get('/', (req, res) => {

res.send('Hello Guys!!');

});

app.listen(PORT);

console.log(`Running on ${PORT}`);

Run the code using command node server.js

This should give output as “ Hello Guys!! ”

Now we have an application ready let’s create a docker file to build a docker image

4. Create a Dockerfile

The dockerfile consists of a sequence of steps to build a docker image

FROM node:12

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 8000

CMD [ "node", "server.js" ]

Each step creates a layer in the docker image. Let’s see what each step does

1. with FROM keyword, we are telling Docker to use this image as the base image. 12 is the version of node

2. WORKDIR, tells docker the working directory of our image (Here, it is usr/src/app). CMD or RUN commands execute in this folder

3. Here, we are copying the package.json file into the current directory.

4. RUN executes a command on the working directory that’s defined above. The npm install command installs required dependencies defined in the package.json, which we’ve just copied to /app directory

5. Now, we copy the files in the root directory to /app directory where we’re using all the commands.

6. EXPOSE 8000, here it informs the user container (using this image) that it needs to open port 8000.

7. CMD stands for command, and here we’re running node server.js as we had seen a little while ago in this article to start the NodeJS server or run file

create a dockerignore file in the same directory as your Dockerfile and write the following content.

node_modules

This will prevent your local modules from being copied onto your Docker image and possibly overwriting modules installed within your image.

5.Build a Docker image

Go to the directory that has your Dockerfile and run the following command to build the Docker image. The -t flag lets you tag your image so it's easier to find later using the docker images command:

docker build -t <your username>/nodejs-helloworld .

Now check your image using docker images command:

6. Run the Image

Running your image with -d runs the container in detached mode, leaving the container running in the background. The -p flag redirects a public port to a private port inside the container. Run the image you previously built:

docker run -p 8000:8000 -d <your username>/nodejs-helloworld

TO check if it is running go to browser and type URL:

<your_docker_ipaddress>:8000

You should see the output as Hello Guys !!

7.Push to DockerHub Registry

Login to your docker hub through the command line. To push to your registry the username must be the same..

docker push <your username>/nodejs-helloworld

This pushes to your registry and attaches the latest tag to it by default.

8.Create a deployment file

To deploy it into Kubernetes let us write a deployment.yaml file .

apiVersion: apps/v1

kind: Deployment

metadata:

name: nodejs-deployment

labels:

app: helloworld

spec:

replicas: 1

selector:

matchLabels:

app: helloworld

template:

metadata:

labels:

app: helloworld

spec:

containers:

- name: helloworld

image: <your-username>/node-helloworld

ports:

- containerPort: 8000 --- apiVersion: v1

kind: Service

metadata:

name: nodejs-service

labels:

app: helloworld

spec:

type: NodePort

ports:

- port: 8000

protocol: TCP

targetPort: 8000

nodePort: 32121

selector:

app: helloworld

Breakdown of yaml file

Describe which API version you’re using to create this object i.e., deployment — we’re using apps/v1 What kind of object you’re creating. In our case, it’s Deployment. In the second part of YAML file it is Service Metadata is used to organize the object. The name of our Deployment is nodejs-deployment Spec is used to define the specification of the object.

How many pods you want to deploy in the cluster under this Deployment. In our case, we want to deploy one pod running containers from our image.

selector field defines how the Deployment finds which Pods to manage. In this case, you simply select a label that is defined in the Pod template

The template field contains the following sub-fields:

field contains the following sub-fields: The Pods are labeled app: helloworld using the .metadata.labels field.

using the field. Create one container and name it helloworld using the .spec.template.spec.containers[0].name field.

using the field. The Pod template’s specification, or .template.spec field indicates that the Pods run one container, helloworld which runs the image we specified from DockerHub

5. Then we write Service to expose the deployment.

In the spec.type we specify nodeport. This helps to expose our pod.

The targetPort has to match the container port in the deployment part.

We can specify the nodeport manually between 30000 to 32767.Else it will automatically assign within the range.

9. Start the Minikube

use Kubectl command to apply the yaml file.

kubectl apply -f deployment.yaml

The output is

This creates a Pod with 1 replica and a service file. For the first time it takes some time.

To see the output of the pod run command minikube service nodejs-service

This opens up the url in the browser. Here you see the output as “Hello Guys!!”

10. Push it to your Git Repository

Wrapping It up

Now, we have created a NodeJs app and created a docker image and pushed it to docker repo and deployed the image in the Kubernetes.

In the next part, we will see what is Jenkins and how to write a pipeline to automate the CI/CD upon changes in GitHub.

You can find the sourcecode here.

Hope you understood it well…

ThankYou !!