Deploy back-end api application with database to the kubernetes cluster with Jenkins CD

pongsatt

pongsatt

Posted on November 3, 2018

Deploy back-end api application with database to the kubernetes cluster with Jenkins CD

We will create a simple graphql api application that connects to mongodb. Then deploy to kubernetes cluster using Jenkins CD.

In addition, we will create ingress that support https so we can access this application securely.

This post is part of series "Setup your own kubernetes cluster on VMs".

Note:

This post assume that you follow from previous post

Prerequisite:

  • A running kubernetes cluster with storage-class support
  • Jenkins server with pipeline support
  • Docker private repository

Build Application

We will create a project called "simpleapi" which is a graphql with mongodb application using nodejs.

1. Create project

mkdir simpleapi
cd simpleapi
yarn init -y

2. Build graphql api

yarn add graphql-yoga mongodb

Create "db.js" at root folder with content below.

const mongoClient = require('mongodb').MongoClient

module.exports = {
    users: () => execute((coll) => coll.find().toArray()
        .then(results => results.map(idToString))),
    createUser: (name) => execute((coll) => coll.insertOne({ name })
        .then(result => idToString(result.ops[0])))
}

function execute(fn) {
    return mongoClient.connect(process.env.DB_URL || 'mongodb://localhost:27017/test')
        .then(client => {
            const result = fn(client.db().collection('users'))
            client.close()
            return result
        })
}

function idToString(doc) {
    const {_id, ...rest} = doc
    return {_id: _id.toString(), ...rest}
}

This program connects mongo at url "mongodb://localhost:27017/test" or url from environment name "DB_URL" if provided.

It also contains 2 functions for querying user list and create new user. We will use these functions in "index.js" below.

Create "index.js" at root folder with content below.

const { GraphQLServer } = require('graphql-yoga')
const db = require('./db')

const typeDefs = `
  type User {
      _id: ID
      name: String
  }

  type Query {
    users: [User]
  }

  type Mutation {
      createUser(name: String!): User!
  }
`

const resolvers = {
  Query: {
    users: (_, { }) => db.users()
  },
  Mutation: {
    createUser: (_, {name}) => db.createUser(name)
  }
}

const server = new GraphQLServer({ typeDefs, resolvers })
server.start(() => console.log('Server is running on http://localhost:4000'))

In this program, we define graphql schema with function to query user and user creation and start server at port 4000.

3. Test graphql api

Note:

You need mongodb running at "mongodb://localhost:27017" to be able to test

Start server by running node index.js and open url "http://localhost:4000".

Create user.

Query user.

Add Jenkins pipeline to project

The api application pipeline will need Dockerfile, kubernetes and Jenkins pipeline configurations.

1. Dockerfile

Here is the nodejs based Dockerfile configuration.

FROM node:8.11.0-alpine

WORKDIR /usr/src

COPY ./*.js package.json yarn.lock ./
RUN yarn install

EXPOSE 4000
CMD [ "node", "index.js"]

1. Kubernetes for database

Create file "k8s/db.yml" with content below.

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: simpleapi-db-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "ssdnfs"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
---

apiVersion: v1
kind: ReplicationController
metadata:
  labels:
    name: simpleapi-db
  name: simpleapi-db
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: simpleapi-db
    spec:
      containers:
      - image: mongo
        name: mongo
        ports:
        - name: mongo
          containerPort: 27017
        volumeMounts:
            - name: mongo-persistent-storage
              mountPath: /data/db
      volumes:
        - name: mongo-persistent-storage
          persistentVolumeClaim:
            claimName: simpleapi-db-claim
---
apiVersion: v1
kind: Service
metadata:
  labels:
    name: simpleapi-db
  name: simpleapi-db
spec:
  ports:
    - port: 27017
      targetPort: 27017
  selector:
    app: simpleapi-db

This configuration will create Persistent Volume Claim that use storage class "ssdnfs" we created from previous post. We also define mongodb instance and its service.

We can access this db within cluster using "simpleapi-db:27017".

2. Kubernetes for application

Create file "k8s/api.yml" with content.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: simpleapi
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: simpleapi
    spec:
      containers:
        - name: simpleapi
          image: "<imageTag>"
          env:
          - name: DB_URL
            value: "mongodb://simpleapi-db:27017/test"
          ports:
          - name: http
            containerPort: 4000
          readinessProbe:
            httpGet:
              path: /
              port: 4000
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /
              port: 4000
            initialDelaySeconds: 3
            periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
  name: simpleapi
spec:
  ports:
    - name: http
      port: 80
      targetPort: 4000
  selector:
    app: simpleapi

This config composes of a Deployment and Service. We can access this application through service "simpleapi:80".

We also override database url with environment "DB_URL".

will be replaced in the build process since it's generated later.

3. Kubernetes for ingress

As promise, this api will be able to access through https via ingress configuration (Setup from previous post).

Create file "k8s/ingress.yml".

Notes:

If you apply letsencrypt certificate using http01 protocol, you will need to uncomment/comment annotations.
Replace "simpleapi.yourdomain.com" with your real domain name
You will need to create DNS record for "simpleapi.yourdomain.com" so that it can apply for certificate (See previous post for more detail)

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: simpleapi-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"

    # use http01 protocol. uncomment line below to use http01
    # kubernetes.io/tls-acme: "true"

    # use dns01 protocol. comment 2 lines below if use http01
    certmanager.k8s.io/acme-challenge-type: "dns01"
    certmanager.k8s.io/acme-dns01-provider: "aws-route53"

spec:
  tls:
  - hosts:
    - simpleapi.yourdomain.com
    secretName: simpleapi-tls
  rules:
  # The host must be identical to the above one
  - host: simpleapi.yourdomain.com
    http:
      paths:
      - path: /
        backend:
          # The name of your service
          serviceName: simpleapi
          servicePort: 80

4. Jenkinsfile

Create "Jenkinsfile" at the root folder.

Notes:

Replace "192.168.1.105:8082" with your private registry
You need to create "User/password" credential name "myregUserPass" to access your Docker repository in Jenkins

pipeline {
    environment {
        IMAGE_TAG = "${env.BUILD_NUMBER}"
        REGISTRY = "192.168.1.105:8082"
        APP = "simpleapi"
    }
    agent { label 'docker' }

    stages {
        stage('build and push image') {
            steps {
                script {
                    docker.withRegistry("http://${REGISTRY}", 'myregUserPass') {
                        docker.build("${APP}").push("${IMAGE_TAG}")
                    }
                }
            }
        }
        stage('deploy') {
            steps {
                sh("sed -i.bak 's|<imageTag>|${REGISTRY}/${APP}:${IMAGE_TAG}|' ./k8s/api.yml")
                sh("kubectl apply -f k8s/")
            }
        }

    }

}

There are 2 stages in this pipeline.

  1. build and push image: build and push new image to docker private repo
  2. deploy: replace and apply all yml files

At this point, your project folders should look like this.

5. Publish project to git repository

You can push to any git. We will use this to configure Jenkins in next step.

Configure kubernetes for docker private registry

Kubernetes cluster will need to know user and password in order to pull image from it. We can set it up by patching service account that pull the image, in this case, default service account.

Create secret

On your master node.

Notes:

Replace with your private registry server
Replace with your registry data

kubectl create secret docker-registry dockerconfig --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>

kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "dockerconfig"}]}'

Create Jenkins Pipeline

Now, we will create new pipeline project in Jenkins called "simpleapi".

Set git repository and credential.

Run build and wait until success.

Run kubectl get secret. If you see "simpleapi-tls", means we can access our application through "https://simpleapi.yourdomain.com".

Summary

This is the last post of this series. We learned how to setup cluster until create front and back end with ingress and dynamic storage. I hope these posts provide some values for you. Thanks.

💖 💪 🙅 🚩
pongsatt
pongsatt

Posted on November 3, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related