vagrant box add devfest-2017-jenkins \
https://github.com/oufti-playground/lab-vm/releases/download/devfest-2017/jenkins-lab-demo.box
Jenkins is an open source automation server which enables developers around the world to reliably build, test, and deploy their software.
#1 Continuous Integration and Delivery server
Created by Kohsuke Kawaguchi in 2006
Original project: "Hudson", renamed "Jenkins" in 2011
An independent and active community (jenkins.io)
500+ releases to date
150,000+ active installations
300,000+ Jenkins servers
1,200+ plugins
Source: stats.jenkins.io
2016 was the year of Jenkins 2
Jenkins 1 is more than 12 years old
Because Continuous Integration have changed…
jenkins-ci.org !?
slave ➞ agent
"Fire and forget"
"Modern Web":
Target: CI → CD
No breaking changes from Jenkins 1
Smooth upgrade
Plugins compatibility
First time experience improvement
Brand new Wizard
Pipeline-as-Code:
Jenkinsfile
stored in SCM
Groovy DSL: "Code your Pipeline"
Declarative Pipeline
Still Jenkinsfile
Easier
Compatible with Scripted Pipeline
BlueOcean
Brand new GUI
Written in ReactJS
Opinionated
Customer Success Manager @ CloudBees
Explorer of the great things out there
Loves to share his discoveries
Not too old for great adventures
Contact:
Twitter: @JM_Meessen
Github: jmMeessen
Google: jean-marc@meessen-web.org
Training Engineer @ CloudBees
Docker & Apple fanboy. Sorry
Human stack focused
Rock climber
Contact:
Twitter: @DamienDuportal
Github: dduportal
Google: damien.duportal@gmail.com
<sales_pitch>
Software at the "Speed of Ideas", Hub of "Enterprise Jenkins and DevOps", providing:
Jenkins "Enterprise" Distribution
Services around Jenkins
Jenkins World 2017: THE Event for Everything Jenkins and DevOps
August 28-31 2017, San Francisco, CA, USA
Register at Jenkins World 2017 Website with the code JWJMEESSEN for 20% discount
</sales_pitch>
Requires VirtualBox >= 5.1.22
Requires Vagrant >= 1.9.4
From a Terminal, download the VM (1 time, ~1Gb):
vagrant box add devfest-2017-jenkins \
https://github.com/oufti-playground/lab-vm/releases/download/devfest-2017/jenkins-lab-demo.box
From the same Terminal, initialize the VM project:
mkdir devfest-2017-jenkins
cd devfest-2017-jenkins
vagrant init -m -f devfest-2017-jenkins
Start the VM from the devfest-2017-jenkins folder:
$ ls
Vagrantfile
$ pwd
.../devfest-2017-jenkins
$ vagrant up
Access your instance homepage:
Goal: Illustrate a Software Supply Chain with a demo application
Challenge: So many languages/framework/toolchains
Solution:
Opinionated demo application (language, tooling, etc.)
Put everyone on same page with initial exercise
Web application
This is the Spring Boot Starter
Language: Java (OpenJDK 8)
Toolchain: Maven (Maven >= 3.3)
Source code stored inside a local Git repository
Open the local GitServer:
Sign In using the top-right button
User is butler
, same for the password
Browse to the repository. Either:
Click on Explore → butler/demoapp
or Direct URL: http://localhost:10000/gitserver/butler/demoapp
Maven configuration: pom.xml
Application Source code: src/main/java/
Application Templates/HTML: src/main/resources/
Application Test code: src/test/java
Open the DevBox, the Web based command line:
WebSockets must be authorized
Copy the demoapp repository URL from GitServer
Run the following commands:
# Get the git repository
git clone http://localhost:10000/gitserver/butler/demoapp.git
# Browse to the local repository
cd ./demoapp
# Check source code
ls -l
cat pom.xml
Clean the window: clear
Show command history: history
CTRL + R
: search the command history interactively
CTRL + C
: cancel current command and clean line buffer
CTRL + A
: jump to beginning of line
CTRL + E
: jump to end of line
Maven TL;DR:
Provide a standardized workflow
pom.xml
describe the application
Maven Command line : mvn
, expects goals (workflow steps)
mvn dependency:list
Can have flags (configuration on the fly)
mvn dependency:list -fn
Maven goal is compile
Resolve build dependencies
Process source code
Generate classes
Content put in the ./target
folder:
mvn compile
ls -l ./target
Maven goal is test
Execute compile
goal
Compile Unit Test classes
Run Unit Test
Tests Reports put in the ./target/surefire-reports
folder:
mvn test
ls -l ./target/surefire-reports
Maven goal is package
Execute compile
and test
goals
Package the application as specified in pom.xml
The new artifact (generated packages) is stored in ./target
mvn package
ls -ltrh ./target/
Unit / Integration Test ?
Bedtime reading: https://martinfowler.com/tags/testing.html
Maven goal is verify
Execute compile
, test
and package
goals
Resolve integration test dependencies
Run Tests against the packaged application
Tests Reports stored in the ./target/failsafe-reports
folder:
mvn verify
ls -l ./target/failsafe-reports
aka "CI"
Continuous Integration doesn’t get rid of bugs, but it does make them dramatically easier to find and remove.
Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily, leading to multiple integrations per day.
Each integration is verified by an automated build (including test)
Integrate code often, at least daily, to make integration a non-event
Continuously build and integrate, with a feedback loop
Access your Jenkins instance:
Log in as the user butler
(password is the same)
This is the "Jenkins Classic GUI"
Switch to BlueOcean, the new UI
Or click on the top button "Open Blue Ocean"
Create your 1st Pipeline:
Stored in Git
Fetch URL from the Gitserver
Add a User/password credential (butler
/ butler
)
Pipeline is empty (for now): no Jenkinsfile
We want Fast feedback !
Pushed code to repository ? Tell Jenkins to build it now
Let’s use Webhook to the repository
HTTP request Gitserver → Jenkins
From repo. in Gitserver → Settings → Webhooks
Add a new webhook:
Type: Gogs (not Slack)
Payload URL: http://localhost:10000/jenkins/job/demoapp/build?delay=0
When should this webhook be triggered?: I need everything
Pipeline-as-code: We need a Jenkinsfile
Where to start ?
Declarative
Easy syntax
Default syntax
Start with this one
Scripted
Original syntax (~3 years)
"Great Power == Great Responsibility"
Use it when Declarative starts to be weird
Provides the full round trip with SCM
No Pipeline ? Follow the wizard (not Gandalf, fool !)
Already have a Pipeline ? Edit, commit, run it
Needs a compliant SCM
Only Github with BO 1.0.1
Interested ? Open-Source: Contribute !
Git is not supported (yet): let’s hack
Open the hidden BlueOcean Pipeline Editor: Direct URL
Use CTRL + S
(On Mac: CMD +S
) to switch to/from textual version
The "Pipeline Snippet Generator" is useful:
Dynamic generation based on the installed plugins
A pipeline job is required: check the left menu icon on http://localhost:10000/jenkins/job/demoapp
Use the BlueOcean Pipeline Editor and Gitserver
Create a Pipeline that have a single stage "Hello"
This stage have 1 step that prints the message "Hello World"
Copy/Paste this Pipeline in a new file Jenkinsfile
on the repository root
A build will kick off immediately:
pipeline {
agent any
stages {
stage('Hello') {
steps {
echo 'Hello World !'
}
}
}
}
Exercise: Implement a simple build pipeline for demoapp
We want 4 stages, for the 4 Maven goals:
compile
, test
, package
, verify
We need to build on the maven
agent
pipeline {
agent {
node {
label 'maven'
}
}
stages {
stage('Compile') {
steps {
sh 'mvn compile'
}
}
stage('Unit Tests') {
steps {
sh 'mvn test'
}
}
stage('Build') {
steps {
sh 'mvn package'
}
}
stage('Integration Tests') {
steps {
sh 'mvn verify'
}
}
}
}
We want to simplify to 2 stages, based on Unit Tests definition:
Build
: compile, unit test and package the application
Verify
: Run Integration Tests
We also want to archive the generated jar
file
Only if the build is successful
Clues: Keywords post
+ success
(not in Editor),
and archiveArtifacts
pipeline {
agent {
node {
label 'maven'
}
}
stages {
stage('Build') {
steps {
sh 'mvn package'
}
}
stage('Verify') {
steps {
sh 'mvn verify'
}
}
}
post {
success {
archiveArtifacts 'target/demoapp.jar'
}
}
}
We want the integration test reports to be published to Jenkins
Better feedback loop
If Integration Tests are failing, do NOT fail the build
Make it UNSTABLE instead
Clues:
Maven flag -fn
("Fails Never")
keyword junit
(Pipeline keyword)
pipeline {
agent {
node {
label 'maven'
}
}
stages {
stage('Build') {
steps {
sh 'mvn clean compile test package'
}
}
stage('Verify') {
steps {
sh 'mvn verify -fn'
junit '**/target/failsafe-reports/*.xml'
}
}
}
post {
success {
archiveArtifacts 'target/demoapp.jar'
}
}
}
We now want all test reports published
Problem: how to handle Unit test failure ?
We also want to archive artifacts if build is
unstable only due to the Verify
stage
Clues: post
can be used per stage
pipeline {
agent {
node {
label 'maven'
}
}
stages {
stage('Build') {
steps {
sh 'mvn clean compile test package'
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
stage('Verify') {
steps {
sh 'mvn verify -fn'
junit '**/target/failsafe-reports/*.xml'
}
post {
unstable {
archiveArtifacts 'target/demoapp.jar'
}
}
}
}
post {
success {
archiveArtifacts 'target/demoapp.jar'
}
}
}
Validate your changes by making your tests fails.
Edit each one and uncomment the failing block:
Integration: src/master/src/test/java/hello/ApplicationIT.java
Unit Tests: src/master/src/test/java/hello/ApplicationTest.java
Browse the top-level items "Changes", "Tests" and "Artifacts"
Do NOT forget to correct your tests at the end
to the Rescue
Using GitServer, from the repository root
Check the Dockerfile
content
Using Devbox, from the demoapp work directory’s root
Checking images with docker images | grep registry_user
Build an image named registry_user/demoapp:latest
from the repository root:
docker build -t registry_user/demoapp:latest ./
Check again the images
It is a lot of commands !
What about testing the Docker Image ?
The demoapp contains a testing system:
It’s using Bats
Command:
/usr/local/bin/bats ./src/test/bats/docker.bats
aka "CD"
How long would it take your organization to deploy a change that involves just one single line of code?
Reduce deployment risks
Allow more frequent user feedback
Make progress believable by everyone
Continuous Delivery is the next step after Continuous Integration:
Every change to the system can be released for production
Delivery can be done at any time, on any environment
Your team prioritizes keeping the software deployable over working on new features
Both are always confused:
Having a collaborating working relationship with everyone involved
Using Deployment Pipelines, which are automated implementations of your application’s build lifecycle process
Production runs on Docker
Your Ops team use a Docker Registry
Expected Artifact:
Not a jar
file
But a Docker image
Goal: Run the Docker Test Suite
Using a single stage named "Docker", before Integration Tests
Using the agent labelled docker
Challenge: we need the jar
file at "Docker time"
We do not need to archive artifact at the end, unless Integration Test is unstable
Clues: Keywords stash
and unstash
pipeline {
agent { node { label 'maven' }}
stages {
stage('Build') {
steps {
sh 'mvn package'
stash(name: 'app', includes: 'target/demoapp.jar')
}
post { always { junit '**/target/surefire-reports/*.xml' }}
}
stage('Docker') {
agent {
label 'docker'
}
steps {
unstash 'app'
sh '/usr/local/bin/bats ./src/test/bats/docker.bats'
}
}
stage('Verify') {
steps {
sh 'mvn verify -fn'
junit '**/target/failsafe-reports/*.xml'
}
post { unstable { archiveArtifacts 'target/demoapp.jar' }}
}
}
}
Goal: We want a Human Approval before Deploy
Add 2 stages named Approval
and Deploy
:
Approval
will ask for a manual validation,
after Integration Tests
Deploy
will tag and push the Docker Image
to the Docker registry at the URL localhost:5000
:
Clues: Keyword input
Here is the Deploy shell code:
docker tag demoapp:$(git rev-parse --short HEAD) localhost:5000/registry_user/demoapp:latest
docker push localhost:5000/registry_user/demoapp:latest
pipeline {
agent { node { label 'maven' }}
stages {
stage('Build') {
steps { sh 'mvn package'
stash(name: 'app', includes: 'target/demoapp.jar') }
post { always { junit '**/target/surefire-reports/*.xml' }}
}
stage('Docker') {
agent { label 'docker' }
steps { unstash 'app'
sh '/usr/local/bin/bats ./src/test/bats/docker.bats' }
}
stage('Verify') {
steps { sh 'mvn verify -fn'
junit '**/target/failsafe-reports/*.xml' }
post { unstable { archiveArtifacts 'target/demoapp.jar' }}
}
stage('Approval') {
agent none
steps { input 'Is it OK to deploy demoapp ?' }
}
stage('Deploy') {
agent { label 'docker' }
steps {
sh 'docker tag demoapp:$(git rev-parse --short HEAD) localhost:5000/registry_user/demoapp:latest'
sh 'docker push localhost:5000/registry_user/demoapp:latest'
}
}
}
}
Goal: Use Docker to provide the build environment
Use the agent allocation to build and run builds within a Docker container
Use the Dockerfile.build
from the repository
Clues: Keywords agent none
, agent { dockerfile … label …}
pipeline { agent none
stages {
stage('Build') {
agent { dockerfile { filename 'Dockerfile.build'
label 'docker'}}
steps { sh 'mvn package'
stash(name: 'app', includes: 'target/demoapp.jar') }
post { always { junit '**/target/surefire-reports/*.xml' }}
}
stage('Docker') {
agent { label 'docker' }
steps { unstash 'app'
sh '/usr/local/bin/bats ./src/test/bats/docker.bats' }
}
stage('Verify') {
agent { dockerfile { filename 'Dockerfile.build'
label 'docker'}}
steps { sh 'mvn verify -fn'
junit '**/target/failsafe-reports/*.xml' }
post { unstable { archiveArtifacts 'target/demoapp.jar' }}
}
stage('Approval') {
agent none
steps { input 'Is it OK to deploy demoapp ?' }
}
stage('Deploy') {
agent { label 'docker' }
steps { sh 'docker tag demoapp:$(git rev-parse --short HEAD) localhost:5000/registry_user/demoapp:latest'
sh 'docker push localhost:5000/registry_user/demoapp:latest' }
}
}
}
Goal: Share Pipeline across your teams
We want to use Shared Libraries
There is one autoconfigured named deploy
Use the annotation to load the Library, on master branch
Check the library here
Clues: Keywords @Library
, script
@Library('deploy@master') _
pipeline { agent none
stages {
stage('Build') {
agent { dockerfile { filename 'Dockerfile.build'
label 'docker'}}
steps { sh 'mvn package'
stash(name: 'app', includes: 'target/demoapp.jar') }
post { always { junit '**/target/surefire-reports/*.xml' }}
}
stage('Docker') {
agent { label 'docker' }
steps { unstash 'app'
sh '/usr/local/bin/bats ./src/test/bats/docker.bats' }
}
stage('Verify') {
agent { dockerfile { filename 'Dockerfile.build'
label 'docker'}}
steps { sh 'mvn verify -fn'
junit '**/target/failsafe-reports/*.xml' }
post { unstable { archiveArtifacts 'target/demoapp.jar' }}
}
stage('Deploy') {
agent none
steps {
script {
deploy('demoapp','localhost:5000/registry_user')
}
}
}
}
}
Goal: Run Stages in parallels to gain time
We can safely run Docker Smoke and Integration Tests in parallel
To specify a specific agent, use Scripted Pipeline Block and the node allocation
Clues: Keywords parallel
, script
, node
@Library('deploy@master') _
pipeline { agent none
stages {
stage('Build') {
agent { dockerfile { filename 'Dockerfile.build'
label 'docker' }}
steps {
sh 'mvn package'
stash(name: 'app', includes: 'target/demoapp.jar')
}
post { always { junit '**/target/surefire-reports/*.xml' }}
}
stage('Tests') {
steps {
parallel ( "Integration Tests": { script {
node('maven') { checkout scm
sh 'mvn verify -fn'
junit '**/target/failsafe-reports/*.xml'
}}}, "Docker": { script {
node('docker') {
unstash 'app'
withEnv(['DOCKER_HOST=tcp://docker-service:2375']) {
sh '/usr/local/bin/bats ./src/test/bats/docker.bats'
}}}})
}
post { unstable { archiveArtifacts 'target/demoapp.jar' }}
}
stage('Deploy') { agent none
steps { script { deploy('demoapp','localhost:5000/registry_user') } }
}
}
}