February 15, 2012

Implementing a real build pipeline with Jenkins

Jenkins is a popular, open source, continuous integration (CI) server. I quite often find it being used in the wild. Equally often I find builds that are too complex, time consuming. Most teams try to break it down into a pipeline, either under the spirit of quick-feedback or as an attempt to kickstart some sort of continuous delivery process. The knee reflex reaction is to look for a plugin that implements a pipeline and I too ended up installing the BuildPipeline only to realise that it doesn't really do what I'd like it to. Hence I've decided to invest a bit of time and see whether it was possible to implement a real pipeline using the bits and bobs that are already available and avoiding to cut code.

Good news is that yes it's possible and it isn't too hard. The Jenkins community has made tremendous progress and we can leverage their efforts. The bad news is that there's a myriad of plugins out there and trawling through them all can be a laborious task, hopefully this blog entry will save you some time.

A pipeline to be a real one needs:
  • to ensure that the change-set version that started the pipeline is the same used in all steps, this should be preferably done with only one check-out from source control
  • steps that can be triggered independently and without the need to re-run previous steps (this is a controversial item but reality is that few builds are always stable)
  • to trigger steps automatically or manually according to need (e.g. you may not want to trigger a deploy to a TEST environment an indadvertedly stuff up manual tests)
  • only one number that represents a given build across all pipeline steps
  • artefacts to be produced only once and reused in later steps that require them
  • to allow easily identifying what code is in the build. I reckon that using the change-set number is good, conversely git SHA's aren't that readable

Here's the list of plug-ins I used to create a real pipeline in Jenkins:
The Token Macro Plugin let's you provide an expression that evaluates to text. This is a dependency for Build Name Setter and it's installed by default in Jenkins. The gotcha here was that prior to version 1.432 it doesn't have the the key tag - ENV - that allows using environment variables. It also doesn't show it as a plugin on the Plugins page, when it fails it doesn't tell you why :( Solution here is to install a version higher than 1.432 which has the proper ENV implementation.

With Build Name Setter Plugin I chose the following number format: PL#123-456. PL# is for PipeLine number. 123 is the change-set number that triggered it. This way I know exactly which code is in the build just by looking at this number. The 456 part is the regular Jenkins BUILD_NUMBER and it gives the run of a given step. So imagine a pipe line with 3 steps that got triggered by commit number 4922. Individual step numbers look like this: PL#4922-7654, PL#4922-324 and PL#4922-8. Step 1 could be a fast unit-test step, hence builds a lot, step 2 could be functional tests, so it builds less often and step 3 deploys to staging, and rarely happens. To configure Build Name Setter find the 'Build Environment' section of the build configuration, check 'Set Build Name' and enter an expression that will be evaluated to text:


In 'PL#${ENV,var="CHANGESET_NUMBER"}-${BUILD_NUMBER}' CHANGESET_NUMBER is an environment variable created by the source control plugin and set at check out time. You may have to figure out the details of the specific source control plugin you're using. BUILD_NUMBER is given by Jenkins.

Clone Workspace SCM Plugin allows you to reuse the workspace of a job, in the pipeline, a step, in another one. This avoids going to SCM again and re-checking out the code. This is key to a real pipeline. Remember? One check-out only.
After you installed Clone Workspace a Post-build Action should be available. i.e.:


You have to think about the workspace as it progresses in the pipeline. If the pipeline makes modifications to the workspace and there modifications are needed in future steps, then you need to re-clone again - very pleonastic :) If the workspace never changes then one clone at the end of step 1 should do the trick.
The subsequent steps will need to refer to the cloned workspace. For instance in Step 2 you should set the Source Code Management section to use the cloned version instead of pulling it from source control, i.e.:


I used Parameterized Trigger Plugin to invoke downstream steps and to pass the needed information so that the step can run. In this example all we need to pass is the change-set number. To configure it look for the 'Trigger parameterized build on other projects' on the Post-build Actions:


The called step should also state that it needs a parameter to work. This parameter will be met from the invoking step when triggered automatically and if the step is triggered manually you'll need to provide it. To set it up look for the 'This build is parameterized' in the main section of the job configuration screen:


Finally to ensure that the version building is the expected one I used a conditional check. First in step one I have a simple script that writes a file with the version number in the workspace. The file name is the change-set number itself. Note that because we're cloning the workspace this file will always be copied across but won't exist in source control per se. Second I have Conditional BuildStep Plugin check that the change-set number file exists and that its name matches the passed change-set parameter. This way the step won't accidentally run the wrong thing, this is particularly relevant when manually triggering steps. The Conditional BuildStep can be found under the build section.


Other tasks I found important to do were:
  • aggregation of test results. To do this you can configure the self explanatory 'Aggregate downstream test results' Post-build Action
  • archive artefacts in a central location/share that can be accessed from Jenkins slaves. To do this I used CopyArchiver Plugin


There are downsides of using Jenkins to implement a pipeline. One of them is that there are heaps of configuration to do and lots of repetition. Another problem is that the configuration itself isn't maintained in source control and figuring out who changed what can be tricky. I recommend making sure that you at least have Jenkins back-ups configured.

4 comments:

  1. Thanks for this - very useful. You can get your job and system config in source control easily with the SCM Sync plugin: https://wiki.jenkins-ci.org/display/JENKINS/SCM+Sync+configuration+plugin

    ReplyDelete
  2. Hi,

    Thanks for putting together this guide. I've been trying to implement a deployment pipleline with Jenkins but I'm having an issue using the "Clone Workspace" plugin. The problem is that let's say I have PL1 running up to "Deploy to PRD" but waiting for a manual deployment than later I run another pipeline build, PL2, if both PL version are at the same step, I won't be able to use PL1 because PL2 overwrote PL1 codes.

    The problem is that you can not choose previous tasks if future jobs have already been executed. How do you solve this? Or in your pipeline you only have 1 row?

    Regards

    ReplyDelete
    Replies
    1. There's a few ways out of it depending on the artefacts produced and what's executed once the manual step is triggered. You could store the artefacts and folder in a different location and explicitly say which version to use. Or you could have a symlink created to point to the version you want to run. Either way what I'd try to do it to ensure that the prod step would be independent of version or embed the step in the artefacts themselves, so this way you'd be free to trigger it whenever necessary. HTH

      Delete
  3. Thanks a lot for researching all that, this just saved me a lot of trouble!

    ReplyDelete