Vert.x featuring Continuous Delivery with Jenkins and Ansible

This blog entry de­scribes an ap­proach to adopt Con­tin­u­ous De­liv­ery for Vert.x ap­pli­ca­tions using Jenk­ins and An­si­ble by tak­ing ad­van­tage of the Jenk­ins Job DSL and An­si­ble plu­g­ins.

Preamble

This post was writ­ten in con­text of the project ti­tled “De­vOps tool­ing for Vert.x ap­pli­ca­tions”, one of the projects at Vert.x tak­ing place dur­ing the 2016 edi­tion of Google Sum­mer of Code, a pro­gram that aims to bring stu­dents to­gether with open source or­ga­ni­za­tions in order to help them to gain ex­po­sure to soft­ware de­vel­op­ment prac­tices and real-​world chal­lenges.

Introduction

Sys­tem con­fig­u­ra­tion man­age­ment (e.g., An­si­ble) has been re­ally hype in the re­cent years and there is a strong rea­son for that. Con­fig­u­ra­tion man­age­ment fa­cil­i­tates con­fig­ur­ing a new en­vi­ron­ment fol­low­ing a fixed recipe or slightly vary­ing it with the help of pa­ra­me­ters. This has not only the ad­van­tage of being able to do it more fre­quently but re­duces the chance of er­rors than doing it man­u­ally.
Be­yond that, com­bin­ing it with Con­tin­u­ous In­te­gra­tion tools (e.g., Jenk­ins) al­lows mak­ing a de­ploy­ment as soon as a new code­base ver­sion is avail­able, which rep­re­sents the main build­ing block of a Con­tin­u­ous De­liv­ery pipeline, one of the ob­jec­tives of em­brac­ing a De­vOps cul­ture.

Given that Vert.x is a frame­work that con­sists in a few li­braries which can be shipped within a sin­gle fat jar, adopt­ing a De­vOps cul­ture while de­vel­op­ing a Vert.x-​based ap­pli­ca­tion is straight­for­ward.

Overview

As seen in the di­a­gram below, this post de­scribes a method to de­fine a Jenk­ins build job which will react to changes in a code repos­i­tory. After suc­ces­fully build­ing the project, the job will ex­e­cute an An­si­ble play­book to de­ploy the new ap­pli­ca­tion ver­sion to the hosts spec­i­fied within the An­si­ble con­fig­u­ra­tion.

Overview of the continous delivery process

Creating a Jenkins build job using Job DSL

Jenk­ins has cre­ated a con­ve­nient way to de­fine build jobs using a DSL. While this op­tion avoids the has­sle of con­fig­ur­ing build jobs man­u­ally, it sup­ports all fea­tures of the reg­u­lar in­ter­face through its API. It is pos­si­ble to use An­si­ble to­gether with Jenk­ins with the help of the An­si­ble plugin, whose in­struc­tions are also in­cluded in the Job DSL API. Al­ter­na­tively to the Job DSL Plu­gin, An­si­ble can be used in­side the de­f­i­n­i­tion of Jenk­ins Pipeline, one of tool’s most re­cent fea­tures.

Below is a sam­ple job de­f­i­n­i­tion which can be used after cre­at­ing a freestyle job (seed job) and adding a new build step with the DSL script. In the script, there are a few things to no­tice:

  • A name for the job cre­ated by the seed job is given.
  • Spe­cific ver­sions of JDK, Maven, and An­si­ble (avail­able in the en­vi­ron­ment) are used.
  • Git is se­lected as the SCM plat­form and the tar­get repos­i­tory is de­fined. Also, the build job is trig­gered ac­cord­ing to a spe­cific in­ter­val.
  • The Maven pack­age goal is in­voked, which is in­structed to pack­age the ap­pli­ca­tion into a fat jar.
  • Lastly, An­si­ble is used to call a play­book avail­able in the filesys­tem. The app will be de­ployed to the de­fined tar­get hosts and the cre­den­tials (con­fig­ured in Jenk­ins) will be used to log into the tar­get hosts. Ad­di­tion­ally, en­abling the colorizedOutput op­tion will re­sult in a friend­lier for­mat­ting of the re­sults in the con­sole out­put. The con­tents of this play­book will be ad­dressed in the next sec­tion.
job('vertx-microservices-workshop-job') {
    jdk('JDK8')
    scm {
        git('git://github.com/ricardohmon/vertx-microservices-workshop.git')
    }
    triggers {
        scm('*/15 * * * *')
    }
    steps {

      def mvnInst = 'M3.3.9'  
      maven {  
        goals('package')  
        mavenInstallation(mvnInst)  
      }  
      ansiblePlaybook('/ansible/playbook.yml') {  
        inventoryPath('/ansible/hosts')  
        ansibleName('Ansible2.0')  
        credentialsId('vagrant-key')  
        colorizedOutput(true)  
      }  

    }  
}

Deploying Vert.x app using Ansible

An An­si­ble Play­book re­sults quite con­ve­nient to de­ploy a Vert.x ap­pli­ca­tion to a num­ber of hosts while still tak­ing con­sid­er­a­tions for each of them. Below is a sam­ple play­book that de­ploys the re­spec­tive ap­pli­ca­tion to each of the hosts de­scribed in an in­ven­tory file. The play­book com­prises the fol­low­ing tasks and takes the listed con­sid­er­a­tions:

1) A task that tar­gets only hosts with a data­base.

  • The tar­get hosts is spec­i­fied with the name of the host (or hosts group) de­fined in the in­ven­tory file.

2) Ac­tual ap­pli­ca­tion de­ploy­ment task. Here, sev­eral con­sid­er­a­tions are done:

  • The ap­pli­ca­tion may re­quire that only one host is up­dated at the time.
    This can be achieved with the serial op­tion, while the order of the de­ploy­ment to hosts can be en­forced in the hosts op­tion.
Host processing order

Even though we could have de­clared all hosts, An­si­ble does not pro­vide an ex­plicit way to spec­ify the order.

  • Java is a sys­tem re­quire­ment for our Vert.x ap­pli­ca­tions.
    Be­sides in­stalling it (keep read­ing), we need to de­clare the JAVA_HOME en­vi­ron­ment vari­able.
  • A de­ploy­ment may just rep­re­sent an up­date to an al­ready run­ning ap­pli­ca­tion (Con­tin­u­ous De­ploy­ment), hence it is con­ve­nient to stop the pre­vi­ous ap­pli­ca­tion in­side the pre_tasks and take post-​deployment ac­tions in the post_tasks. Vert.x ships with the con­ve­nient start/stop/list com­mands that re­sult very help­ful here. We can use the list com­mand and ex­tract (using regex) the id of the run­ning ap­pli­ca­tion of its out­put to stop it be­fore de­ploy­ing a new ver­sion.

If our so­lu­tion in­cludes a load bal­ancer or proxy, we could deal with them at this step as de­scribed in An­si­ble’s best prac­tices for rolling up­dates

  • Call to a role that makes the ac­tual ap­pli­ca­tion de­ploy­ment. The Jenk­ins An­si­ble Plu­gin in­cludes, be­tween oth­ers, a WORKSPACE en­vi­ron­ment vari­able, which may re­sult very help­ful in the fol­low­ing tasks, as shown later.
  # 1) Special task for the service with a db
- hosts: audit-service
  remote_user: vagrant
  become: yes
  roles:
    - db-setup

  # 2) Common tasks for all hosts
- hosts: quote-generator:portfolio-service:compulsive-traders:audit-service:trader-dashboard
  remote_user: vagrant
  become: yes
  serial: 1
  environment:
    JAVA_HOME: /usr/lib/jvm/jre-1.8.0-openjdk/

  pre_tasks:
  - name: Check if the app jar exists in the target already
    stat: path=/usr/share/vertx_app/app-fatjar.jar
    register: st
  - name: List running Vert.x applications
    command: java -jar /usr/share/vertx_app/app-fatjar.jar list
    register: running_app_list
    when: st.stat.exists == True
  - name: Stop app if it is already running (avoid multiple running instances)
    command: java -jar /usr/share/vertx_app/app-fatjar.jar stop {% raw %}{{ item | regex_replace('^(?P<V_id>.[8]-.[4]-.[4].[4].[12])\t.*', '\\g<V_id>') }}{% endraw %}
    with_items: "{% raw %}{{ running_app_list.stdout_lines|default([]) }}{% endraw %}"
    when: st.stat.exists == True and (item | regex_replace('.*\t(.*)$', '\\1') | match('.*/app-fatjar.jar$'))

  # Main role
  roles:
    - { role: vertx-app-deployment, jenkins_job_workspace: "{{ lookup('env', 'WORKSPACE') }}" }

  post_tasks:
  - name: List again running Vert.x applications
    command: java -jar /usr/share/vertx_app/app-fatjar.jar list

Once we took care of the ac­tions shown be­fore, the re­main­ing tasks (in­cluded in the main de­ploy­ment role) re­duce to the fol­low­ing:

1) Pre­pare the tar­get ma­chine with the proper en­vi­ron­ment to run our ap­pli­ca­tion. This in­cludes:

  • Set up Java (pretty con­ve­nient to do it through a pack­age man­ager).
  • Copy the Vert.x ap­pli­ca­tion pack­age to the ap­pro­pri­ate folder (quite sim­ple using a fat jar). The ac­tual name and lo­ca­tion of the jar pack­age in the Jenk­ins en­vi­ron­ment can be de­fined using host-​specific vari­ables.
  • In case nec­es­sary, copy the re­quired con­fig files.
- name: Install Java 1.8 and some basic dependencies
  yum: name={% raw %}{{ item }}{% endraw %} state=present
  with_items:
   - java-1.8.0-openjdk
- name: Ensure app dir exists
  file: path=/usr/share/vertx_app/ recurse=yes state=directory mode=0744
- name: Copy the Vert.x application jar package
  copy: src={% raw %}{{ app_jar }}{% endraw %} dest=/usr/share/vertx_app/app-fatjar.jar mode=0755
- name: Ensure config dir exists
  file: path=/etc/vertx_app/ recurse=yes state=directory mode=0744
- name: Copy the application config file if needed
  copy: src={% raw %}{{ app_config }}{% endraw %} dest=/etc/vertx_app/config.json mode=0755
  when: app_config is defined

2) Run the ap­pli­ca­tion as a ser­vice in the host­ing ma­chine.

  • Make sure to ig­nore the hang up sig­nal with the help of nohup com­mand. Oth­er­wise, An­si­ble will be stuck at this step.
- name: Run Vert.x application as a service, ignore the SIGHUP signal
  shell: nohup java {% raw %}{{ vertx_opts }}{% endraw %} -jar /usr/share/vertx_app/app-fatjar.jar start {% raw %}{{ launch_params }}{% endraw %}
  register: svc_run_out
- name: Print run output
  debug: var=svc_run_out.stdout_lines
Launching the Vert.x app

This ex­am­ple uses the start com­mand to launch the ap­pli­ca­tion as a ser­vice. This method may re­sult more com­fort­able than cre­at­ing an init.d script or call­ing Vert.x from com­mand line, which would have re­quired to in­stall the Vert.x li­braries in an in­de­pen­dent An­si­ble task.

This de­scribes all the con­fig­u­ra­tion needed to be able to build from a repos­i­tory using Jenk­ins and de­ploy the re­sults to our hosts with An­si­ble.

Sample sources and demo

The sam­ple con­fig­u­ra­tions pre­sented be­fore are part of a com­plete demo fo­cused on the Vert.x mi­croser­vices work­shop to ex­em­plify a basic Con­tin­u­ous De­liv­ery sce­nario. This set up is avail­able in a repos­i­tory and con­tains, in ad­di­tion, a pre-​configured Jenkins-​based demo ready to host the build job de­scribed the pre­vi­ous sec­tions. The demo sce­nario re­quires Va­grant and Vir­tu­al­box to be launched.

Launch instructions

  • Clone or down­load this repos­i­tory, and launch the demo using vagrant up
git clone https://github.com/ricardohmon/vertx-ansible.git
cd demo
vagrant up

This com­mand will launch a vir­tual ma­chine host­ing Jenk­ins with the re­quired plu­g­ins in­stalled (tools names needed) and also launch five ad­di­tional VMs that will host the mi­croser­vices de­ployed by Jenk­ins.

  • Cre­ate a Jenk­ins freestyle build job using the DSL job script (seed job) found in deployment-jobs/microservices_workshop_dsl.groovy and build it.
Tool configuration assumption

The DSL Job as­sumes the fol­low­ing tools (with names) have been con­fig­ured in Jenk­ins: Java 8 (JDK8), Maven (M3.3.9), An­si­ble (Ansible2.0)

  • After build­ing the seed job, a new job (vertx-microservices-workshop-job) will be cre­ated, which will be in charge of pulling re­cent changes of the project, build­ing it, and de­ploy­ing it.

Demo

Watch the pre­vi­ous demo in ac­tion in the fol­low­ing screen­cast:

Conclusion

Con­tin­u­ous De­liv­ery ap­proach is a must in mod­ern soft­ware de­vel­op­ment life­cy­cles (in­clud­ing Vert.x-​based ap­pli­ca­tions) and a step fur­ther to­wards adopt­ing a De­vOps cul­ture. There are a num­ber of tools that en­able it and one ex­am­ple is the com­bi­na­tion of Jenk­ins + An­si­ble de­scribed in this post.
While Jenk­ins of­fers the pos­si­bil­ity to in­te­grate re­cent changes per­ceived in a code­base and build runnable ar­ti­facts, An­si­ble can help to de­ploy them to host­ing en­vi­ron­ments. The usage of both tools can be cou­pled eas­ily with the help of the Job DSL plug­in, a fea­ture of Jenk­ins that al­lows de­scrib­ing a build job using a domain-​specific lan­guage, which can help to in­te­grate ad­di­tional steps and tools to a CD pipeline.

Fur­ther en­hance­ments can be done to this basic pipeline, such as, in­te­grat­ing the re­cent Pipeline plugin, a fea­ture that al­lows a bet­ter or­ches­tra­tion of CD stages; in­clu­sion of no­ti­fi­ca­tion and alert­ing ser­vices; and, ul­ti­mately a zero-​downtime de­ploy­ment ap­proach, which could be achieved with the help of a proxy; plus, tons of op­tions avail­able trough Jenk­ins plu­g­ins.

Thanks for read­ing!

Next post

OAuth2 got easy

Oauth2 support exists in Eclipse Vert.x since version 3.2.0. The implementation follows the principles that rule the whole vert.x ecosystem.

Read more
Previous post

Vert.x 3.3.3 is released!

We have just released Vert.x 3.3.3, a bug fix release of Vert.x 3.3.x.

Read more
Related posts

Centralized logging for Vert.x applications using the ELK stack

This post entry describes a solution to achieve centralized logging of Vert.x applications using the ELK stack (Logstash, Elasticsearch, and Kibana).

Read more

Getting started with new fabric8 Vert.x Maven Plugin

The all new fabric8 Vert.x Maven Plugin allows you to setup, package, run, start, stop and redeploy easily with a very little configuration resulting in a less verbose pom.xml.

Read more

Unit and Integration Tests

Let’s refresh our mind about what we developed so far in the introduction to vert.x series. We forgot an important task. We didn’t test the API.

Read more