Feature branches CI with Openshift and Jenkins pipeline
Ben's Corner
A few months ago, I setup feature branches CI with Openshift using Jenkins pipeline and Openshift client tools.
Our company is on an intranet, so this is after experience with CI platforms like Atlassian Bamboo and Bitbucket pipelines, as well as orchestration platforms like Docker Swarm and plain Kubernetes.
In my opinion, this is the best setup for private clouds, as it is mostly code as configuration and it’s all open-source. So after half a year of tweaking it, I wanted to share my Jenkinsfile and development workflow.
Another important point of this setup is that it allows me to deploy previous builds, which was an issue I have had since migrating away from Atlassian Bamboo, which had a very handy deployment feature despite its numerous flaws.
node('agents') { //Build parameters for builds and/or deployments, add more if necessary properties([ choice(name:'DoBuild', choices:'true\nfalse', description:'Build or deploy a previous image'), choice(name:'WhichBuild', choices: (env.BUILD_NUMBER.toInteger()..1).join(\n), description:'If deploying, which previous build'), choice(name:'WhereToDeploy', choices:'feature-branch\ndev-server\nopenshift-int\nopenshift-ppd\npre-prod-tar\nNONE', description:'Where to deploy, NONE for no deploy') ])
//Then, login to Openshift, prepare the YAML files and execute oc commands withCredentials([string(credentialsId:'registry-token', variable:'TOKEN')]) { sh "oc login ${registry-address} --token==$TOKEN" }
sh "sed -i 's/BRANCHNAME/${deployName}/g' deploy/deployment.yml" sh "sed -i 's/BRANCHNAME/${deployName}/g' deploy/service.yml" sh "sed -i 's/BRANCHNAME/${deployName}/g' deploy/route.yml" sh "sed -i 's/DEPLOYNAME/${deployName}/g' deploy/deployment.yml" sh "sed -i 's/DEPLOYNAME/${deployName}/g' deploy/service.yml" sh "sed -i 's/DEPLOYNAME/${deployName}/g' deploy/route.yml"
sh "oc project ${groupName}" sh "oc apply -f deploy/deployment.yml" sh "oc apply -f deploy/service.yml" sh "oc apply -f deploy/route.yml"
//Finally, login and push the image to the registry docker.withRegistry('https://registry.address', 'docker-registry') { sh "docker pull ${imageName}-${buildToDeploy}" sh "docker tag ${imageName}-${buildToDeploy} ${imageName}" sh "docker push ${imageName}"//Openshift will use the image tagged :latest } } elseif(whereToDeploy == 'dev-server') { //If deploying to a simple server, use SSH to pull and run the image, then wait for healthCheck to resolve withCredentials([[$class:'UsernamePasswordMultiBinding', credentialsId:'docker-registry', usernameVariable:'USERNAME', passwordVariable:'PASSWORD']]) { withCredentials([[$class:'UsernamePasswordMultiBinding', credentialsId:'server-login', usernameVariable:'SRVUSERNAME', passwordVariable:'SRVPASSWORD']]) { sh """ sshpass -p${SRVPASSWORD} ssh -o StrictKeyChecking=no -o UserKnownHostsFile=/dev/null ${SRVUSERNAME}@${deploySrv} '\ docker login p ${PASSWORD} -u ${USERNAME} ${registryAddress}; \ docker system prune -f; docker kill ${projectName}; docker rm ${projectName}; \ docker pull ${imageName}-${buildToDeploy}; \ docker run --detach --name ${projectName} --hostname=\$HOSTNAME ${imageName}-${buildToDeploy}'; bash -c 'until [[ "\$curl -s --output /dev/null --head --fail -w ''%{http_code}'' \ http://${whereToDeploy}/healthCheck )" = 200 ]] ; do echo '.'; sleep 5; done; \ echo 'Server is up'; """ } } } elseif(whereToDeploy == 'pre-prod-tar') { //If deploying a TAR for production, pull, tag, save and upload to an FTP server sh """ docker login p ${PASSWORD} -u ${USERNAME} ${registryAddress}; \ docker pull ${imageName}-${buildToDeploy}; \ docker tag ${imageName}-${buildToDeploy} ${groupName}:${projectName}-${buildToDeploy}; \ docker save ${groupName}:${projectName}-${buildToDeploy} ${groupName}:${projectName}-${buildToDeploy}.tar; \ curl -T ${groupName}:${projectName}-${buildToDeploy}.tar ftp://${ftpServer}; """ } }
The other crucial part of this setup is the collection of files that describe the deployment. They are basically typical deployment/service/route Openshift configuration files. As an indication, these are my files:
Other files can be adjoined to these files, like ConfigMaps, secrets, nodeport services, etc… I have described some of these in previous articles.
Workflow
The workflow enabled by this setup should be straightforward by now:
Git pushes trigger a build.
By default, builds deploy to feature branches.
By using builds with parameters, there is an option to deploy to integration or pre-production deployments, as well as saving images to tarballs and uploading to an FTP server for separate production deployment.
Debugging with Openshift can hardly be automated unless you want to go with one pod everywhere (among other constraints). That’s why there is a choice to deploy to traditional dev servers for easier debugging.
Many improvements can be made, such as tagging builds or using various plugins. Jenkins has endless possibilities after all. But these are mostly not relevant to this post.
There is a lot of tweaking to be done before this setup can be functional, but when it runs it’s flawless.
You can also see these files on the Github repo. If you have any question or remark, leave a comment, or send me an email.