Microservices Continuous Delivery with Docker and Jenkins

Microservices Continuous Delivery with Docker and Jenkins

Docker, Microservices, Continuous Delivery are currently some of the most popular topics in the world of programming. In an environment consisting of dozens of microservices communicating with each other it seems to be particularly important for the automation of the testing, building and deployment process. Docker is an excellent solution for microservices, because it can create and run isolated containers with service. Today, I’m going to show you how to create a basic continuous delivery pipeline for sample microservices using the most popular software automation tool – Jenkins.

Example of microservices

Before I get into the main topic of the article I say a few words about structure and tools used for sample microservices creation. Sample application consists of two sample microservices communicating with each other (account, customer), discovery server (Eureka) and API gateway (Zuul). It was implemented using Spring Boot and Spring Cloud frameworks. Its source code is available on GitHub. Spring Cloud has support for microservices discovery and gateway out of the box – we only have to define right dependencies inside maven project configuration file (pom.xml ). The picture illustrating the adopted solution architecture is visible below. Both customer, account REST API services, discovery server and gateway running inside separated docker containers. Gateway is the entry point to the microservices system. It is interacting with all other services. It proxies requests to the selected microservices searching its addresses in discovery service. In case of existing more than one instance of each account or customer microservice the request is load balanced with Ribbon and Feign client. Account and customer services are registering themselves into the discovery server after startup. There is also a possibility of interaction between them, for example if we would like to find and return all customer’s account details.

Image title

I wouldn’t like to go into the details of those microservices implementation with Spring Boot and Spring Cloud frameworks. If you are interested in detailed description of the sample application development you can read it in my blog post here. Generally, Spring framework has full support for microservices with all Netflix OSS tools like Ribbon, Hystrix and Eureka. In the blog post I described how to implement service discovery, distributed tracing, load balancing, logging trace ID propagation, API gateway for microservices with those solutions.

Dockerfiles

Each service in the sample source code has Dockerfile with docker image build definition. It’s really simple. Here’s Dockerfile for account service. We use openjdk as a base image. Jar file from target is added to the image and then run using java -jar command. Service is running on port 2222which is exposed outside.

FROM openjdk
MAINTAINER Piotr Minkowski <piotr.minkowski@gmail.com>
ADD target/account-service.jar account-service.jar
ENTRYPOINT ["java", "-jar", "/account-service.jar"]
EXPOSE 2222
 

We also had to set main class in the JAR manifest. We achieve it using spring-boot-maven-plugin in module pom.xml. The fragment is visible below. We also set build finalNameto cut off version number from target JAR file. Dockerfile and maven build definition is pretty similar for all other microservices.

<build>
   <finalName>account-service</finalName>
   <plugins>
      <plugin>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-maven-plugin</artifactId>
         <configuration>
            <mainClass>pl.piomin.microservices.account.Application</mainClass>
            <addResources>true</addResources>
         </configuration>
         <executions>
            <execution>
               <goals>
                  <goal>repackage</goal>
               </goals>
            </execution>
         </executions>
      </plugin>
   </plugins>
</build>
 

Jenkins pipelines

We use Pipeline Plugin for building continous delivery for our microservices. In addition to the standard plugins set on Jenkins we also need Docker Pipeline Plugin by CloudBees. There are four pipelines defined as you can see in the picture below.

Image title

Here’s the pipeline definition written in Groovy language for discovery service. We have 5 stages of execution. Inside Checkout stage we are pulling changes for remote Git repository of the project. Then the project is built with mvn clean install command and also the maven version is read from pom.xml. In the Image stage we build the docker image from the discovery service Dockerfile and then push it to a local registry. In the fourth step we are running built image with default port exposed and hostname visible for linked docker containers. Finally, the account pipeline is started with no wait option, which means that the source pipeline is finished and won’t wait for the account pipeline execution finish.

node {

    withMaven(maven:'maven') {

        stage('Checkout') {
            git url: 'https://github.com/piomin/sample-spring-microservices.git', credentialsId: 'github-piomin', branch: 'master'
        }

        stage('Build') {
            sh 'mvn clean install'

            def pom = readMavenPom file:'pom.xml'
            print pom.version
            env.version = pom.version
        }

        stage('Image') {
            dir ('discovery-service') {
                def app = docker.build "localhost:5000/discovery-service:${env.version}"
                app.push()
            }
        }

        stage ('Run') {
            docker.image("localhost:5000/discovery-service:${env.version}").run('-p 8761:8761 -h discovery --name discovery')
        }

        stage ('Final') {
            build job: 'account-service-pipeline', wait: false
        }      

    }

} 

Account pipeline is very similar. The main difference is inside the fourth stage where the account-service container is linked to the discovery container. We need to link that container, because account-service is registering itself in the discovery server and must be able to connect it using hostname.

node {

    withMaven(maven:'maven') {

        stage('Checkout') {
            git url: 'https://github.com/piomin/sample-spring-microservices.git', credentialsId: 'github-piomin', branch: 'master'
        }

        stage('Build') {
            sh 'mvn clean install'

            def pom = readMavenPom file:'pom.xml'
            print pom.version
            env.version = pom.version
        }

        stage('Image') {
            dir ('account-service') {
                def app = docker.build "localhost:5000/account-service:${env.version}"
                app.push()
            }
        }

        stage ('Run') {
            docker.image("localhost:5000/account-service:${env.version}").run('-p 2222:2222 -h account --name account --link discovery')
        }

        stage ('Final') {
            build job: 'customer-service-pipeline', wait: false
        }      

    }

} 

Similar pipelines are also defined for customer and gateway service. They are available in the main project catalog on each microservice as Jenkinsfile . Every image which is built during pipeline execution is also pushed to the local Docker registry. To enable local registry on our host we need to pull and run a Docker registry image and also use that registry address as an image name prefix while pulling or pushing. Local registry is exposed on its default 5000 port. You can see the list of pushed images to the local registry by calling its REST API, for example http://localhost:5000/v2/_catalog.

$ docker run -d --name registry -p 5000:5000 registry

Testing

You should launch the build on discovery-service-pipeline. This pipeline will not only run build for discovery service but also call start next pipeline build (account-service-pipeline) at the end.The same rule is configured for account-service-pipeline which calls customer-service-pipeline and for customer-service-pipeline which call gateway-service-pipeline. So, after all pipelines finish you can check the list of running docker containers by calling docker ps command. You should have seen 5 containers: local registry and our four microservices. You can also check the logs of each container by running command docker logs , for example docker logs account . If everything works fine you should be able to call some service like http://localhost:2222/accounts or via Zuul gateway http://localhost:8765/account/account.

CONTAINER ID        IMAGE                                           COMMAND                  CREATED             STATUS              PORTS                    NAMES
fa3b9e408bb4        localhost:5000/gateway-service:1.0-SNAPSHOT     "java -jar /gatewa..."   About an hour ago   Up About an hour    0.0.0.0:8765->8765/tcp   gateway
cc9e2b44fe44        localhost:5000/customer-service:1.0-SNAPSHOT    "java -jar /custom..."   About an hour ago   Up About an hour    0.0.0.0:3333->3333/tcp   customer
49657f4531de        localhost:5000/account-service:1.0-SNAPSHOT     "java -jar /accoun..."   About an hour ago   Up About an hour    0.0.0.0:2222->2222/tcp   account
fe07b8dfe96c        localhost:5000/discovery-service:1.0-SNAPSHOT   "java -jar /discov..."   About an hour ago   Up About an hour    0.0.0.0:8761->8761/tcp   discovery
f9a7691ddbba        registry 

Conclusion

I have presented the basic sample of Continuous Delivery environment for microservices using Docker and Jenkins. You can easily find out the limitations of presented solution, for example we has to linked docker containers with each other to enable communication between them or all of the tools and microservices are running on the same machine. For more advanced sample we could use Jenkins slaves running on different machines or docker containers (more here), tools like Kubernetes for orchestration and clustering, maybe Docker-in-Docker containers for simulating multiple docker machines. I hope that article is a fine introduction to the microservices Continuous Delivery and helps you to understand the basics of this idea. I think that you could expect more advanced articles about that subject near the future.

0 COMMENTS

comments user
Harsh Jain

Hi , Thanks for sharing detailed explanation and example for micro services spring boot and spring cloud architecture. I saw your jenkins and docker scripts for multi modules i.e. but while deploying it via jenkins how do we identify whether changes were in some particular module service and only that should be build and deploy instead of whole project (Web Hook will tell jenkins that changes were made in this repo not in particular module ) . I am looking for solution to this problem . It will be a great help If you could suggest something .

comments user
Harsh Jain

Hi , Thanks for sharing detailed explanation and example for micro services spring boot and spring cloud architecture. I saw your jenkins and docker scripts for multi modules i.e. but while deploying it via jenkins how do we identify whether changes were in some particular module service and only that should be build and deploy instead of whole project (Web Hook will tell jenkins that changes were made in this repo not in particular module ) . I am looking for solution to this problem . It will be a great help If you could suggest something .

comments user
Luke Lanterme

Hi There

Great article.

I have cloned your project and busy walking through the code. When I build the pipeline in Jenkins, I keep getting the following error:

[discovery-service] Running shell script
+ docker build -t localhost:5000/discovery-service:1.0-SNAPSHOT .
/tmp/jenkins/workspace/Microservice-Build/discovery-service@tmp/durable-7efa0db4/script.sh: line 2: docker: command not found

I have no idea how to solve it.

Please can you advise.

Thanks

    comments user
    Piotr Mińkowski

    Hi,
    Well, did you install docker on your machine?

comments user
Luke Lanterme

Hi There

Great article.

I have cloned your project and busy walking through the code. When I build the pipeline in Jenkins, I keep getting the following error:

[discovery-service] Running shell script
+ docker build -t localhost:5000/discovery-service:1.0-SNAPSHOT .
/tmp/jenkins/workspace/Microservice-Build/discovery-service@tmp/durable-7efa0db4/script.sh: line 2: docker: command not found

I have no idea how to solve it.

Please can you advise.

Thanks

    comments user
    Piotr Mińkowski

    Hi,
    Well, did you install docker on your machine?

comments user
Alireza Hanifi

After a build, it says the port is already allocated.
Because last instance is run.
Whats your suggestion?

    comments user
    Piotr Mińkowski

    Kill the running instance. It is just example

comments user
Alireza Hanifi

After a build, it says the port is already allocated.
Because last instance is run.
Whats your suggestion?

    comments user
    Piotr Mińkowski

    Kill the running instance. It is just example

comments user
Alla Sasi Kanth

Looking for an explanation for what exactly these 2 stages do

stage(‘Image’) {
dir (‘discovery-service’) {
def app = docker.build “localhost:5000/discovery-service:${env.version}”
app.push()
}
}
stage (‘Run’) {
docker.image(“localhost:5000/discovery-service:${env.version}”).run(‘-p 8761:8761 -h discovery –name discovery’)
}

    comments user
    Piotr Mińkowski

    Stage ‘Image’ builds Docker image and push it to the Docker images repository. Stage ‘Run’ just start new Docker container from that image.

comments user
Alla Sasi Kanth

Looking for an explanation for what exactly these 2 stages do

stage(‘Image’) {
dir (‘discovery-service’) {
def app = docker.build “localhost:5000/discovery-service:${env.version}”
app.push()
}
}
stage (‘Run’) {
docker.image(“localhost:5000/discovery-service:${env.version}”).run(‘-p 8761:8761 -h discovery –name discovery’)
}

    comments user
    Piotr Mińkowski

    Stage ‘Image’ builds Docker image and push it to the Docker images repository. Stage ‘Run’ just start new Docker container from that image.

comments user
maineffort

Hi , this has indeed been very useful for understanding CI concepts with Jenkins, thanks a lot for your efforts. I have a few observations however
1. While building I encountered some hurdles which might benefit others, for examples groovy scripts might fail to execute except if security is turned off as explained here https://stackoverflow.com/questions/38276341/jenkins-ci-pipeline-scripts-not-permitted-to-use-method-groovy-lang-groovyobject

2. I had to remove “using hostname ” and “[code language=”java”]” from the customer-service-pipeline else build gives errors.
3. The pipelines have created in the local Jekins server must have the same names with your example else the names have to be adapted accordingly in the Jenkinsfiles.
4. Maven installation configured for the Jenkins server must have the same name with the maven name in the jenkinfiles.

comments user
maineffort

Hi , this has indeed been very useful for understanding CI concepts with Jenkins, thanks a lot for your efforts. I have a few observations however
1. While building I encountered some hurdles which might benefit others, for examples groovy scripts might fail to execute except if security is turned off as explained here https://stackoverflow.com/questions/38276341/jenkins-ci-pipeline-scripts-not-permitted-to-use-method-groovy-lang-groovyobject

2. I had to remove “using hostname ” and “[code language=”java”]” from the customer-service-pipeline else build gives errors.
3. The pipelines have created in the local Jekins server must have the same names with your example else the names have to be adapted accordingly in the Jenkinsfiles.
4. Maven installation configured for the Jenkins server must have the same name with the maven name in the jenkinfiles.

comments user
harsh

very nice!

comments user
harsh

very nice!

comments user
s.v. manikandan

Hi, Thanks for this great article. Please advise on how to configure each jenkins pipeline with the respective Jenkinsfile script path.

    comments user
    Piotr Mińkowski

    Thanks 🙂 I don’t exactly understand what you mean in this question.

comments user
s.v. manikandan

Hi, Thanks for this great article. Please advise on how to configure each jenkins pipeline with the respective Jenkinsfile script path.

    comments user
    Piotr Mińkowski

    Thanks 🙂 I don’t exactly understand what you mean in this question.

Leave a Reply