Concepts
Concepts
The following are general concepts related to continuous delivery.
-
Imperative vs. Declarative
Imperative Deployment vs. Declarative Desired State
-
Branch Plans
Alternate Branch Strategies
-
Release
Release Definitions and Tools
The following are general concepts related to continuous delivery.
Imperative Deployment vs. Declarative Desired State
Alternate Branch Strategies
Release Definitions and Tools
Imperative (Procedural Flow): Focuses on the manual execution of a specific sequence of commands, requiring the operator to manage state transitions directly, which increases complexity and the risk of configuration drift.
Declarative (Desired-State Flow): Uses an automated orchestrator (a reconciliation loop) that continuously compares the actual system state with a predefined state definition document. It handles provisioning and updates automatically to ensure consistent, self-healing environments.
Related Principles
Implementation Patterns with CDAF
In a very simple ecosystem, an imperative approach to deployments can be effective. For each target environment, the component promotion (the staging from Test to Production for example) can be timed by the DevOps engineer to ensure the combination of components that were tested are consistent with those that go live.
In this example, due to an oversight, the user interface was promoted to Production without the correct API version. The user interface fails in production even though it worked in test as expected, and a critical dependent API operation is now not available due to a drift.
Backend
|
Frontend
|
To add more complications, there is another version of the user interface in test now. What version of the API does it require?
This approach is not scalable, where an ecosystem has many components, and many environments, the complexity grows exponentially and the chances of manual errors leading to inconsistency increase.
See an alternative, Declarative Desired State.
Implementation Patterns with CDAF Imperative Deployment for pipeline constructs.
There are two distinct and important concepts to cover, Declarative & Desired State.
Declarative is a style of implementation where you describe what you want to achieve, rather than how to achieve it. In a declarative approach, you specify the desired outcome or state of the system, and the underlying implementation takes care of the details.
Desired State refers to the specific configuration or condition that you want a system to be in. It represents the target state that you want to achieve, and it is often used in the context of infrastructure management, configuration management, and orchestration.
Orchestrated Deployment is a process of managing and automating the deployment of applications and services in a way that ensures they are deployed in the desired state. It involves coordinating various components and resources to achieve the desired outcome, often using tools and frameworks that support declarative configurations.
In an orchestrated deployment, you would typically define the desired state of your application, such as the number of replicas, resource requirements, and network configurations. The orchestration tool would then take care of deploying the application according to the specified desired state, ensuring that it is running and functioning as intended.
In summary, a declarative approach focuses on describing the desired state of the system, while an orchestrated deployment is a method of achieving that desired state through automation and coordination of resources.
There are a variety of declarative notations:
This example is Puppet applying environment variables to a Tomcat server.
Declaration
|
Application of Desired State
|
Implementation Patterns with CDAF Declarative Release for implementation examples, which incorporate intermediary tools such Ansible Tower, Puppet Enterprise and Terraform Cloud
In a traditional infrastructure model, servers are treated like domesticated pets. They are given unique names, carefully nurtured, and kept alive at all costs.
The Cost of Failure: Because the server is unique, its downtime is an operational emergency. If it dies, rebuilding it exactly as it was can take hours or days, often leading to data loss or “configuration drift” (where no one is entirely sure how the server was originally configured).
In modern cloud-native architectures, infrastructure is treated like cattle or disposable machinery. Runtimes—such as Docker containers, micro-VMs, or serverless functions—are completely anonymous, identical, and easily replaced.
The Cost of Failure: Disposability transforms failure into a non-event. If a container exhibits an error, becomes unresponsive, or runs out of memory, the orchestrator (like Kubernetes) immediately terminates it and provisions a brand-new, healthy replica in seconds.
Different branch plans do not explicitly define deployment approaches, however, there are common associative methods for each plan, which are described in the subsequent pages. This page provides the baseline terminology that will be used in the remainder of this material.
Commonly referred to as Trunk Based Development. This is the simplest strategy and is commonly synonymous with Continuous Delivery (more on this to come). The only long running branch is main.
This branch strategy has been promoted by Microsoft, and is fundamental in their deploy process within Visual Studio. with two (or sometimes more) long-lived branches, e.g. main being used for test and release being used for production. Each additional environment requires another branch.
Originating from distributed source control systems, with prolonged disconnection. The majority of source control tools provided now are centralised server solutions, which obfuscate the underlying distributed architecture. GitFlow has continued, while being adjusted to use Pull Request/Merge Request to merge between branches. This typically has many long-lived branches, e.g. main, develop, release, hot-fix.
From Atlassian https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow
Continuous Delivery (CD) decouples the release activity from development activity. Unlike Continuous Deployment, Continuous Delivery has one or more approval gates. At time of writing, the majority of pipeline tools support approval gates, with the exception of the GitHub Free tier.
A fundamental aspect of Continuous Delivery is to build once and deploy many times. This means the output of the development process (Continuous Integration) is an artefact which can be re-used to deploy to multiple environments. The artefact represents the Release. Once this artefact is produced, the developer input is complete, and a non-development user, i.e. Test Managed or Product Owner can promote the release through various environments to production.
In this example, the first stage is Continuous Integration (CI) which produces the release. Each stage after that is automatically executed, with an integration test stage, and then deployment to the staging environment. After the deployment to staging, the pipeline stop, awaiting approval.
The release artefact in this example is #26, and this is re-used in each deployment phase.
The approval gate advises the approver of what release is currently in production (#23) and what release will be deployed.
Once approved, the same artefact that was tested, is now promoted to Production, completing the pipeline.
See the following reference from https://www.infoq.com/articles/Continuous-Delivery-Maturity-Model for the build once/deploy many construct.
Where the pipeline tool does not support approval gating, but a review and approval mechanism is desired, the “Pull Request”/“Merge Request” can be used. The resulting approval will rebuild the solution and deliver it to the defined environment.
There are a variety of methods used within a branch based deployment approach, the following is a common example.
A long-living branch is defined for each target environment, in this example dev, test and release. A source of truth branch exists (main) which does not trigger a deployment.
Unlike Continuous Delivery, a separate build is created for each environment, e.g. #64 in development, #65 in acceptance test, etc.
The long-lived branches provide a high level of concurrency and flexibility to skip stages, or deploy a selected feature or fix (known as “Cherry-picking”).
To “promote” changes from feature to development, then on to test and production, a pull request is raised for each stage. In this scenario it is typically a fellow developer that reviews and approves the change, and not a business user, i.e. Product Owner.
The merge triggers the corresponding build and deploy for the target branch/environment.
GitOps is another branch based deployment approach, however it typically does not have a Continuous Integration construct, and instead deploys directly from source control.
GitOps is commonly portrayed as Trunk Based
Each target environment maybe defined as a directory, but in many some cases, i.e. to provide a gating mechanism, like Branch Based Deployment, multiple long-lived branches are used.
A release can mean different things to different organisations. For product build teams who distribute to many customers, a release is the shippable product made available to consumers, and this is not the context of the following material.
The release process is the final stage of the software delivery pipeline, and is the point at which the software is made available to end-users or customers. The release process can involve several steps, including:
The release process is a critical part of the software delivery pipeline, and it is important to have a well-defined and tested release process to ensure that releases are successful and do not cause issues for end-users or customers.
The principle of “build once, deploy many” is a key concept in software delivery and release management. It refers to the practice of building a software application or component once and then deploying that same build to multiple environments, such as development, testing, staging, and production.
While interpretive languages, e.g. Javascript, Python, etc. can be deployed directly from source control, this approach is not a release. Compiled languages which are recompiled for each environment are also not a release.
The release artefact is the output of the build process, and should be immutable, meaning that it cannot be modified after it has been built. This allows for consistency across all environments, as the same build can be deployed to each environment without any changes. The following tools facilitate the deployment of one or more release packages, independent to the source code that created them.
Octopus DeployOctopus Deploy is a dedicated release orchestration and deployment automation tool. It treats the release as a first-class concept: a versioned, immutable snapshot of one or more packages that is promoted through environments rather than rebuilt. Key characteristics relevant to build-once, deploy-many:
Octopus separates the concern of building (handled by your CI tool) from releasing (handled by Octopus), which aligns directly with the principle that the artefact tested in staging is the artefact shipped to production. |
Azure DevOps ReleasesAzure DevOps Releases (part of Azure Pipelines) provides a pipeline-based release orchestration layer that sits downstream of the build pipeline. A release definition consumes build artefacts and controls their progression through a sequence of stages. Key characteristics relevant to build-once, deploy-many:
Azure DevOps Releases is particularly well-suited to organisations already invested in the Microsoft toolchain, providing a governed promotion path from a single build artefact across all environments. |