Introduction to Jenkins as Code
Introduction to Jenkins
Automation has always been important within the Operations/SRE workflow, typically automation is written using tools such as bash/python and required quite a bit of knowledge to understand. There are a few issues with doing automation this way:
- These tools do not provide a clear way to represent history unless we require the user to think very abstractly in regards to logging and development
- CLI interfaces can be intimidating to some users (why should we always be the ones checking on job status/reading the logs? Let the non technical users do it!)
- Continuous Integration is quite an abstract concept and often depends upon many tools working together, some aimed at a development workflow, some aimed at the operations workflow which were often designed and written by people with very different perspectives.
A tool was needed to be able to pack up these abstract concepts and glue them together, ideally adding an abstraction layer to make it approachable from both developers and operations staff. Throughout the mid to late 2000-s a project known as ‘Hudson’ was engineered at Sun to try and solve these problems, it introduced the abstract concept of a ‘build’ which could tie together other tools used by Development/Operations, pushing the unified ‘DevOps’ way of building and deploying software, along with introducing a GUI and a unified concept of ‘history’.
In 2009 Oracle bought out Sun, this triggered a debate as to how Hudson would be licensed/worked on going forwards, after the dust settled the core Hudson development team forked the project and continued to work on the project under the new name ‘Jenkins’ utilizing the extremely liberal MIT license. Development has continued in the open ever since, there is very little/no reason to use Hudson since the fork.
As time has marched on and we are now in an age where almost all tooling used by operations staff in modern companies is code based (k8s, puppet, ansible, terraform etc) Jenkins has aged rather poorly, here is a small list of why Jenkins became a pain:
- Since work is GUI driven we cannot version control jobs, in the age of git being our central VCS for every other part of the environment this is unacceptable
- We cannot port jobs to other instances/machines without tediuously copying each job over via the GUI
- We have to manage our permissions within Jenkins, when we could use our version control software as a central IAM point
- Jenkins plugins are notiously difficult to manage, they have lots of codependencies and upgrading even minor versions can break an entire environment
- Jenkins often becomes a ‘pet’ server that is hard to manage, this is due to the amount of ad-hoc configuration that happens within the GUI that is difficult/overly complex to automate and depends a great deal upon support from whichever plugin you’re attempting to automate
To mitigate most of these problems a variety of ‘Jenkins as Code’ solutions have been created, most of these solutions are buit on top of the Groovy interface that is exposed to Jenkins core. For those unfamiliar with Groovy it is a subset of Java, with syntax designed to be as close to Java as possible, the documentation for Groovy is located here. Next we will cover some differences between the different ‘Jenkins as Code’ offerings:
Jenkins Job Builder:
- Takes descriptions of Jenkins jobs in either YAML or JSON format
- Written in Python
- Is not supported by the Jenkins core team, therefore suffers from limited support or adoption rates
- Dependant upon macros/other tools to meet the functionality of other options
- Simple jobs and templates are easily readable and accessible
- Since jobs are defined within templates it is very easy to wrap those templates in scripts
- Cannot automate the creation of jobs, requires a ‘seed’ job to initiate the job
- Job DSL is a offered as a Jenkins plugin, and is not part of Jenkins core
- Netflix used this plugin extensively and it is supported by the Jenkins core team, this lead to a wide range of adoption
- Is sometimes considered to be a more primitive representation of the newer DSLs
- Uses Groovy
- Job DSL cannot initialize jobs and requires a ‘seed job’ to initialize the job
- This solution effectively solves problems number 1 and 2 written above, but does nothing to solve problems 3 and 4
Jenkins Configuration as Code (JCasC):
This tool is a little different to the others as it allows you to define in code the configuration of the Jenkins instance rather than defining jobs within Jenkins, it also enables you to ‘initialize’ the jobs within code, this is something missing from all the other plugins listed here as you will still have to follow the manual step of creating the job and pointing it at a repo/pasting the contents of the Jenkinsfile into the script box. A few considerations:
- Allows you to initialize jobs (which is something none of the other plugins here have built in)
- Allows you to automate the installation and configuration of plugins
- Automate the setup of users/LDAP connections
- Uses a yaml file to define the state the Jenkins server should be in
- Is a tool independent of your Jenkins environment
Whilest this plugin solves different problems to the others, it solves problem 5 on the list above and helps considerably to mitigate problems 2, 3, and 4. If you have the need to spin up Jenkins servers on short notice (such as a container image to test plugins before applying them to prod) then this tool is incredible. Read more about it here.
Here will we look at a couple of DSLs included as part of the Jenkins Pipeline update, the update itself added significant improvements to Jenkins core which requires a bit of context to be able to explain, we will go off into a small tangent here in order to explain it.
The Jenkins Pipeline plugin is a suite of plugins bundled together with the goal of improving Jenkins usefulness as a CI tool, it is included by default with versions of Jenkins newer than 2.0. Along with other changes it introduced a few much needed improvements to Jenkins:
- Has built in parallelism, the user can determine which steps to run in parallel within the code
- Docker integration
- Shared libraries to make writing Jenkinsfiles (we’ll get to those in a moment) more DRY
- Provided multiple DSLs for creating Jobs
- Provides better visualisation for job progress, for example:
The Pipelines DSL comes in two flavours, which share some similarities. Both DSLs use a ‘Jenkinsfile’ by default and can use shared libraries which operate in the same way as libraries in any other software development environment. Here we will cover some of the differences between the two:
- Very similar syntax to Groovy
- More of a general purpose DSL than the Declarative syntax
- Flow control is managed by conditionals and switch statements
- Follows an imperative model
- Is built on top of Groovy, however this DSL is far more opinionated and rigid as to how code should be written
- Designed to be much simpler and more approachable than the scripted pipelines
- Uses steps to define computation logic, rather than using conditions to control flow, this is why it is considered to be ‘declarative’
- The declarative approach can be confusing to many developers who are not used to working with declarative languages
- Allows you to use scripted pipelines syntax within steps should you need some additional complexity/functionality
- Is newer than scripted pipelines and doesn’t have quite as much adoption
To really summarize my opinion on these tools I would go with one of/both the Pipelines DSLs and JCasC should you require it. The Job DSL is not supported as strongly now that the newer Pipelines DSLs have been released, with many newer plugins being supported by Pipelines from the get go and a large rate of adoption for more of the ‘core’ plugins.
The use of JCasC is a no brainer if you have the need to reproduce your Jenkins environment and mitigates the ‘pet server’ problem. This requirement can also be mitigated depending upon your deployment method, so I feel it only really fits teams that put a big focus on getting their infrastructure into code rather than using things like storage persistence in the cloud and making backups. In most scenarios it is preferable to just have a backup of the Jenkins server to keep things like job history and considerations for some jobs around persistent workspaces.
When choosing between which Pipelines DSL to use, I would opt for the Jenkins Declarative Pipelines syntax as a primary, it is far easier to ‘pick up and go’ with than the scripted syntax, whilest also giving you the ability to drop down into the scripted syntax if you need any more complex functionality. The only difficulty I’ve had so far with the Declarative syntax is the lack of questions on Stackoverflow, but there are more coming as the adoption rate increases!