Monday 3 October 2016

Integrated Continuous Integration (CI) Solution With Jenkins, Maven, docker-maven-plugin, Private Docker Registry


In previous blog, I shared how to run Jenkins + Maven in Docker container here and we could already automate the Maven-based build automatically in our Jenkins CI server, which is great to kick off for the CI process.

But, while reviewing what we're going to achieve as below diagram, we realize that having a Spring Boot-powered jar file is not what we want, we want to have a Docker image built and registered into our private Docker Registry so that we're fully ready for the coming Continuous Deployment!

Integrated Jenkins + Maven + docker-maven-plugin

So let's focus on the highlighted part on above diagram to build an integrated Jenkins + Maven + docker-maven-plugin solution, by using Docker container technology also!

Build Up Our Private Docker Registry

Docker has provided a great Docker image for building up our private Docker Registry, here.
Let's do it by simply running through below commands:
$ sudo mkdir /data/registry
$ sudo chown -R devops:docker /data/registry
$ whoami
devops
$ pwd
/home/devops
$ vim docker-compose.yml
-------------------------------
# Private Docker Registry
docker-registry:
  image: registry
  volumes:
    - /data/registry:/var/lib/registry
  ports:
    - "5000:5000"
-------------------------------

It's almost done, let's spin it up:
$ docker-compose up

And try it out:
$ curl http://127.0.0.1:5000/
"\"docker-registry server\""

Well, our private Docker Registry works but wait…the Docker client can’t push image to it yet.
We need to make some changes at Docker's configuration before pushing images:
$ sudo vim /etc/default/docker
---------------------------
DOCKER_OPTS="--insecure-registry 192.168.56.118:5000"
---------------------------

And then restart the Docker daemon:
$ sudo service docker restart

Now our private Docker Registry is ready to rock.

But how to verify whether our Docker Registry is really working as what we expected?
It must be: 1) Ready for publishing; and 2) Ready for searching.

The idea is to use our greatest public available "hello-world" image which is lightweight enough to try, 960B!

$ docker pull hello-world
$ docker images
...
hello-world    latest                     690ed74de00f        11 months ago       960 B

$ docker tag 690ed74de00f 192.168.56.118:5000/hello-world
$ docker images
...
192.168.56.118:5000/hello-world    latest  690ed74de00f        11 months ago       960 B
hello-world                        latest  690ed74de00f        11 months ago       960 B

$ docker push 192.168.56.118:5000/hello-world
The push refers to a repository [192.168.56.118:5000/hello-world]
5f70bf18a086: Image successfully pushed
b652ec3a27e7: Image succewr12334567ssfully pushed
Pushing tag for rev [690ed74de00f] on {http://192.168.56.118:5000/v1/repositories/hello-world/tags/latest}

$ curl http://192.168.56.118:5000/v1/search?q=hello
{"num_results": 1, "query": "hello", "results": 
 [
  {"description": "", "name": "library/hello-world"}
 ]
}

Sure enough, our private Docker Registry is fully ready!

Okay, so far we already have a private Docker Registry, already can do a basic Maven build in Jenkins. What should we do next?

Enable the docker-maven-plugin

It's time to enable our glue, the docker-maven-plugin, in our project.
The purposes of using this plugin are to:

  1. Automate the Docker image build process on top of our typical build which the output might be jar, war. Now, we want to have a Docker image equipped with our application as the deliverable!
  2. Publish our built Docker image to our private Docker Registry where is the starting point of our Continuous Deployment!
<Frankly speaking, there were many issues encountered along my experiment like here but to make long story short, I'll go directly to the points>


Firstly, please refer to my previous blog "Some Practices to Run Maven + Jenkins In Docker Container" here for the basic setup of the environment.
Below successful experiment would be on top of this environment.

Explicitly Specify Docker Host in /etc/default/docker

Edit the /etc/default/docker as below:
#DOCKER_OPTS="--insecure-registry 192.168.56.118:5000"
DOCKER_OPTS="--insecure-registry 192.168.56.118:5000 -H tcp://0.0.0.0:4243 -H unix:///var/run/docker.sock"

And restart the Docker daemon is required, of course:
$ sudo service docker restart

Enable the docker-maven-plugin In pom.xml

There are many Docker Maven plugins available and I simply choose spotify's one, here.
The configuration of the plugin in pom.xml is as below:
<!-- Docker image building plugin -->
<plugin>
 <groupId>com.spotify</groupId>
 <artifactId>docker-maven-plugin</artifactId>
 <version>0.4.13</version>
 <executions>
  <execution>
   <phase>package</phase>
   <goals>
    <goal>build</goal>
   </goals>
  </execution>
 </executions>
 <configuration>
  <dockerHost>${docker.host}</dockerHost>
  
  <baseImage>frolvlad/alpine-oraclejdk8:slim</baseImage>
  <imageName>${docker.registry}/devops/${project.artifactId}</imageName>
  <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint>
  
  <resources>
     <resource>
    <targetPath>/</targetPath>
    <directory>${project.build.directory}</directory>
    <include>${project.build.finalName}.jar</include>
     </resource>
  </resources>
  
  <forceTags>true</forceTags>
  <imageTags>
     <imageTag>${project.version}</imageTag>
  </imageTags>
 </configuration>
</plugin>
Note: For complete pom.xml file, please refer to my GitHub repository, here.

The Jenkins Job

Below screenshots are the major configuration parts of the Jenkins Job.

Source Code Management


Build Triggers


Maven Goals


Console Output

Here I also posted a typical Console Output of the Jenkins Job for reference as a Gist here.

Have A Check On What We Have

Firstly we can check whether our local repository has the cached image:
$ docker images
REPOSITORY                                             TAG                        IMAGE ID            CREATED             SIZE
...
192.168.56.118:5000/devops/springboot-jersey-swagger   1.0.0-SNAPSHOT             203fde08df84        44 minutes ago      191.9 MB
...

Great, then have a check whether this image has been registered into our private Docker Registry:
$ curl http://192.168.56.118:5000/v1/search?q=springboot-jersey-swagger
{"num_results": 1, "query": "springboot-jersey-swagger", "results": 
 [
  {"description": "", "name": "devops/springboot-jersey-swagger"}
 ]
}

Yeah! We made it!

Try Our Docker Image Out Also

To go deeper, we can also take a look on whether our application works or not:
docker run -d -p 8000:8000 192.168.56.118:5000/devops/springboot-jersey-swagger

Open the browser and key in: http://192.168.56.118:8000


Hooray, it works perfectly!

Conclusion

From this experiment we have learned that:
  1. By using a customized Docker container, we can put Jenkins, Maven and docker-maven-plugin together to automate our project build process and the deliverable can be our desired Docker image;
  2. The Docker image can be pushed automatically to our private Docker Registry within the pipeline which will enable us to make the Continuous Deployment happen soon.

So the CI part is done and let's look forward to our CD part soon!
Enjoy your CI experiments also!


8 comments:

  1. Really nice solution and practices, thanks for sharing.

    I'm curious on the docker related configuration in pom.xml (I checked out from your github repo).
    Do you think it's a good practice to hardcode the addresses like docker registry and docker host into the pom.xml?

    ReplyDelete
  2. Thanks for your comment. Well, it really depends.
    As a walk-through exercise, it's pretty easy to put both of them in pom.xml. But in the real world, we will eventually find out what we should do.
    Below are my thoughts for your consideration purposes:
    1. For the docker registry, one organization may have one registry only so it's very straightforward to hardcode it into the pom file and should not be an issue;
    2. For docker host, we should think twice about it. One environment (e.g. SIT/UAT/PROD) may have one docker host so it may not be a good practice to put it into the pom file, use the DOCKER_HOST instead as this plugin honors this env variable also.

    ReplyDelete
  3. Nice article I was impressed by seeing this blog, it was very interesting and it is Thanks for sharing all the information with us all.very useful for me. Thank you for sharing any good knowledge and thanks for fantastic efforts.

    oracle training in chennai

    oracle training institute in chennai

    oracle training in bangalore

    oracle training in hyderabad

    oracle training

    oracle online training

    hadoop training in chennai

    hadoop training in bangalore


    ReplyDelete