Continuous delivery outlines methodologies for the automation of software (Private) release processes.
- Self service approach allows developers to own the creation of release artefacts and their deployment to an environment.
- Package once, deploy everywhere: reduce the risk associated with deployments by decoupling release preparation from deployment and reusing the same artefacts across environments.
- Increase release cadence and reduce batch size, as incremental updates are much less likely to cause deployment issues.
- Integration is the act of blending changes from developers in a single repository with automated build and testing.
- Building is the packaging up of software builds ready to go to production.
- Deployment is the act of getting the build artifacts to the target environment, e.g. by copying the files, or replacing the Kubernetes pods.
- Releasing is the act of enabling the new code path, e.g. by toggling a feature flag. The business risk should be associated with this step, not the deployment.
- Approvals are manual sign-offs given by humans, e.g. pull request reviews.
- Gates (also referred to as quality gates) are automated approvals, e.g. by a CI system.
The most appropriate approach to continuous delivery is contextual. Factors include:
- Release cadence, the frequency of deployments. Generally, increasing the frequency of deployments reduces the relative risk of any individual deployment as changes are more evenly spread out across a wider timeframe.
- Application architecture:
- Monolithic designs, where the application is made up of a single component, are harder to gradually effect change upon than modular or service-oriented (microservices) designs.
- Feature flags allow developers to release partially implemented features in a disabled state. They can be taken further, allowing selective release of functionality to specific audiences.
- Environment architecture:
- Load balancers enable dividing up and distributing transactions across a set of weighted backend pools, enabling rollouts to be staged.
- Multiple instances for geo-redundancy or isolation of environments provide another boundary.
In-place upgrades simply replace the running instance with the updated version. There's no separation of deployments.
- Simple, only one environment to maintain.
- Cheaper, because there's less infrastructure, though this is less relevant with IaaS.
- Requires downtime for changes.
Progressively deploys changes to rings of audiences, separated by different levels of risk tolerance. The release progresses from the innermost rings to the outermost rings based on predefined timescales, which may be contractually agreed with clients. Users might consciously select their release rings, or they might be determined behind the scenes.
- Precise control over when features reach different audiences.
- Very complex.
- Forces a measured timeline for changes, extending overall time to live for changes.
Canary deployments are similar to progressive exposure, but instead of discriminating by audience a percentage of all traffic is exposed to the new version. We're able to identify problems with the release from the signals available to us, and we'd expect to complete the deployment over a much smaller time frame.
This approach is named after canaries used in coal mines to determine whether miners were being exposed to carbon monoxide.
- Less upfront organisation required, as we don't need to identify audiences.
- Smaller time to live.
- Weighting of requests between different backend pools may require session persistence.
Blue/green deployment involves wholesale migration between two environments during a deployment. The process can be summarised as:
- Prepare new environment from the new release.
- At predefined intervals, migrate a percentage of transactions from one environment to the other, e.g.:
- (Optional) When you're confident the new release is good and there's zero chance of rollback, tear down the previous environment. This is useful for reducing cost in IaaS environments.
Shadow deployments deploy more than one instance of an application concurrently. Both versions receive traffic, but the results from all but the production instance aren't included in the result sent to the end user. This is useful for verifying that an in-development implementation provides results that are consistent with an existing implementation, and for highlighting cases where they diverge.