TL;DR Define a git workflow for your development team, targeting our WiP build pipeline
Through this iterative design, we are revealing more concerns, and we need to switch from a top-down design to a bottom-up method to ensure all needs are being met. So far, we know that we have three isolated environments (dev
, pre-prod
, and production
) that will align with well-known branches (master
, candidate
, and release
). This allows daily work in master to be isolated from ‘march to production’ work in the release candidate branch. It also provides a dedicated workspace for production code; the release
branch should always contain the code deployed to production.
With a slow march to production, we also need a place to work on production bugs - we don’t want to interrupt daily work in master
, don’t want to derail a current march to production in candidate
, and we don’t want to pollute the release
branch with intermittent work. We’ll introduce a completely new branch, hotfix
, for just this purpose.
Let’s walk through how developers use these branches daily, and some use cases for our expected work.
All products will enter our Kubernetes clusters through our build pipeline.
Our three production levels (dev
, pre-prod
, prod
) provide their slightly different semantics in corresponding Kubernetes clusters:
dev
: continuous build and deployment of incremental feature branch merges to master
branchpre-prod
: continuous build, deployment, and proving for release candidate
and hotfix
branchesprod
: docker image deployment of customer facing services (images) mirrored by the release
branchAt a high level, feature branches are merged into master
and automatically built and deployed to dev
. When enough features are accumulated, and are at sufficient quality, master
is merged into the release candidate
branch which is automatically built and deployed on the pre-prod
cluster. When the release candidate image is deemed suitable quality, the candidate
branch is pushed to the release
branch to reflect currently deployed code, a GitHub release is created, the proven release candidate image is re-tagged with the GitHub release version, and that image is deployed in prod
.
Let’s look a little deeper at GitHub branches and transitions.
Commits to standard GitHub branches provide triggers for execution of different phases of the Jenkins pipeline.
Branch | Content | Target | Description |
---|---|---|---|
master |
next-up features | dev |
A merge into master , from a feature branch, a PR, or direct commit triggers a build and deploy to dev . |
candidate |
release candidates | pre-prod |
When current work in master is ready for promotion, master is merged into the candidate branch which triggers a build and deploy to pre-prod |
release |
released code | prod |
When a release is ready for production, candidate is merged into release and a user manually triggers the job which applies final image versioning, a GitHub tag and release, and deployment to production. |
hotfix |
production hotfixes | pre-prod |
When a production fault requires an immediate fix, a short-lived hotfix branch is created from the current release tag triggering a build and deploy to pre-prod . When the hotfix is of suitable quality, it is merged into release and the hotfix branch is deleted. |
* |
all other branches | none | When any other branch is committed, the CI portion of pipeline (build, test) is run in the dev environment. For PRs, the results are attached to the PR. |
The pre-release (dev
, pre-prod
) and release (prod
) pipelines address different concerns. Each pipeline is implemented in a common Jenkins library which each product invokes through a common project root level Jenkinsfile
.
The pre-release pipeline focuses on building, testing, and creating docker images. The canonical pre-release pipeline provides the following stages:
Stage | Description |
---|---|
Build | Produce and stash build artifacts for packaging: jars, binaries, etc. |
Test | Unit test the product and produce unit test and coverage reports. |
Package | Produce the docker image. |
Archive | Push the docker image to the portr docker registry. |
Deploy | Create the Helm chart, push to Tiller, schedule a cluster deployment. |
Integration Test | Test the image in the target cluster. |
The release pipeline focuses on image selection (candidate, hotfix, redeploy) and a measured deploy to production. It is always manually initiated. The canonical release pipeline provides the following stages:
Stage | Description |
---|---|
Create Release | Create a new semver version, pull the source image, retag with bare repo name and new version, create a GitHub release. |
Canary Deploy | Create the Helm chart, push to Tiller, schedule a canary datacenter deployment. |
Canary Test | Run smoke tests in the canary datacenter. Prompt the user to continue the deploy or rollback. |
Canary Rollback | Rollback the canary deployment to the previous version. |
Prod Deploy | Create the Helm chart, push to Tiller, schedule a full cluster deployment. |
Prod Test | Run smoke tests on all prod locations. |
NOTE: A production rollback is trivially implmented as a re-deploy of the previous version.
Tying the above ideas together, we arrive at a combined integration, delivery and deployment flow:
master
branch is processed in the dev
pipeline and deploys to the dev
environment.candidate
branch is processed in the release candidate
pipeline and deploys to pre-prod
.release
branch is processed by the release
pipeline and deploys to prod
. Note that no new docker image is built here; the release
branch serves two purposes: provides a Jenkins job target and always reflects the code that is in production.hotfix
branch is processed by the release candidate
pipeline and deploys to pre-prod
(not shown).The Jenkins pipeline responds to git merges and commits. At a high level, the normal feature flow through git looks like:
A hotfix release is an emergency production code change. It adds a branch to our normal flow above to isolate that work, but the release pipeline steps are nearly identical.
Let’s walk through developer life living with this pipeline:
master
branch.master
merge triggers Jenkins to execute all stages in the development
pipeline phase resulting in a deployment to our dev environment.master
triggering more executions of the development
pipeline phase and deployment to the dev environment.master
code is of suitable quality - a release candidate - the master
branch is merged into the candidate
branch.candidate
merge triggers Jenkins to execute all stages in the release candidate
pipeline resulting in a new docker image deployed to our pre-production environment.candidate
triggering more executions of the release candidate
pipeline phase and deployment to the ppe environment. Fixes are also merged down into master
.candidate
code is of suitable quality, and ready to push to production, the candidate
branch is merged into release
.release
pipeline phase including docker image re-tag, GitHub tagging, GitHub release, and deployment to the production environment.The git branch transitions look like:
feature-1
branch.feature-1
is merged into master
triggering a build and deploy to dev
.master
and committed triggering another build and deploy to dev
.master
is merged into candidate
triggering a build and deploy to pre-prod
.candidate
and committed triggering another build and deploy to pre-prod
.candidate
is merged into release
triggering final grooming and a deploy to prod
pre-prod
is merged down into master
.When non-project contributors submit work via pull request, the pull request
section of the pipeline is executed. This abbreviated flow includes the Test stage producing unit test and code coverage results which are added as comments to the pull request. When the PR is found of suitable quality, it is merged into the master
branch and becomes part of the normal flow to production.
NOTE: The Jenkins Multi-branch pipeline plugin automatically discovers all branches in a GitHub repository and executes this flow.
Notice that the git branch diagram for a PR is identical to a feature branch:
When a flaw is identified in production that requires immediate remediation:
release
tag is branched into hotfix
.hotfix
commit triggers Jenkins to execute all stages in the release candidate
pipeline phase resulting in a new docker image deployed to our pre-production environment.candidate
branch to separate the work streams.hotfix
triggering more executions of the release candidate
pipeline phase and deployment to the ppe environment.hotfix
code is of suitable quality, and ready to push to production, the hotfix
branch is merged into release
.release
stages are executed.The git branch transitions look like:
hotfix
branch is created from the current release
branch.hotfix
.hotfix
is merged into release
triggering a deploy to production.hotfix
is merged down into candidate
.hotfix
is merged down into master
.Products use their GitHub repo name as both Jenkins job name and docker image name because we prize operational over semantic clarity; it is more important for an operator to be able to locate source for a build job or deployed artifact than the name of that artifact making perfect semantic sense.
We use docker images as our fundamental deployment unit and store those under a single repository group. in a private registry requiring each image to use a fully qualified docker image name. For this effort, we will store images in Docker Hub under the stevetarver
group which yields image names like:
stevetarver/ms-ref-python-falcon
Pre-release builds append the branch name to the repo name and use a build timestamp as a tag to uniquely identify the image and indicate its provenance.
stevetarver/ms-ref-python-falcon-master:20170817191210
stevetarver/ms-ref-python-falcon-candidate:20170817191211
stevetarver/ms-ref-python-falcon-hotfix:20170817191212
When a candidate or hotfix is deemed ready for production, a new version is created to represent the feature group or fix being deployed. New features increment the minor version and fixes increment the micro version.
The latest branch image is pulled and retagged to omit the branch name and use the new version.
stevetarver/ms-ref-python-falcon:1.1.0
Now we know how developers will interact with GitHub and how GitHub will trigger Jenkins builds and deploys. Our next step will be to create a generic Jenkins pipeline that can implement this pipeline and be flexible to work with all of our client projects.