Getting started with CI/CD
By: Albert Starreveld
Tags: DevOps, Agile Transformation, Cicd, Pipeline, Continuous Delivery

Installing software automatically is a piece of cake. Everybody can build a Docker container. Creating a deployment-pipeline is easier than ever before. But every system is different, and not every system can be updated without causing downtime.

Mistakes are easily made, and IT-systems are fragile. Some systems are too big to be deployed automatically. Some teams depend on other teams and as a result, systems depend on each other. Implementing CI/CD is easier said than done. It’s not just a matter of building a deployment pipeline. It impacts (almost) every team and system in the organization. It’s a new way of working.

Implementing CI/CD means every team will deploy their changes straight to production when the product is good enough, without causing downtime. It means they’re not responsible for a small part of the system anymore, they’re responsible for complete features. It changes the organization, and it changes the software.

1.) Power to the Product Owner!

Release frequency matters with CI/CD. The CI/CD process works great when you’re constantly shipping tiny increments to production. The bigger the increment, the bigger the risk of downtime, and the harder it is to revert the release when it failed. If the team can’t decide when to go to production, changes will pile up, and you’ll end up having a “big-bang” release before you know it.

To be able to ship to production often, a team cannot have a dependency on an external person or department who’ll decide whether they can ship their product to production or not. The Product Owner represents the stakeholders’ interests. She should be able to make that call.

2.) End-to-end responsibility

Technical services and generic APIs are the enemy of Continuous Integration. Especially when they’re maintained by different teams. Multiple teams will have to change multiple systems in order to produce a feature an end-user can use. Probably in a specific order, too. Not only their software has dependencies on one another, but the teams also do. A web-team, for example, would have to wait for the DBA-team to complete their work on the database before they can store data in it, from the website. Not to mention the number of dependencies that are created, this way. How many other teams (and systems) will store data in that same data-structure? How many things will break if the database is updated? DRY isn’t always a good thing…

Make teams responsible for features, not for technique. Focus on technique causes systems and teams to depend on each other. The technique doesn’t matter, it’s just means to an end.

Making a team responsible for a feature, rather than technical details only, enables them to produce value, independently. Don’t create a DBA-team. Put a DBA in every team. Don’t create an OPS team. Have an OPSer in every team. Teams can operate autonomously, that way, and that will lessen the amount of time that occurs between starting on a feature until it is deployed.

3.) Applications are cut into smaller pieces

Systems in an IT-environment often depend on one another. Producing a product-increment that’s valuable to an end-user might cause several parts of the system to be modified by several teams, making it impossible to deploy independently.

Software is like cake. It has layers. From the layer-philosophy, it makes sense to partition systems by their technical purpose. A presentation-layer, a Business Logic Layer, and a Data-Access-Layer, infrastructure layer, and so forth. This approach introduces coupling. One layer depends on another, and other layers depend on it. It’s only possible to deploy all of them at the same time. Instead, partition an application landscape functionally. Consider the following example. What’s does your team deliver? A database layer? Or an accounting application?

Functional partitioning vs technical partitioning

To discover the functional slices in an application, use a technique called Event Storming. It exposes the “slices”, which are better known as “Bounded Contexts”. Aligning teams around products and features make teams and their software more autonomous.

The point of aligning teams around products and features is to enable them to frequently deliver product-increments that directly have value to the end-user. To be able to do so, a team needs to deliver its own (parts of) a user interface, APIs, databases, and so forth. They need to have their own repositories and their own deployment pipelines.

4.) Deployments are automated

CI/CD and DevOps are a mindset:

“Three strikes, you automate!” “The first time you do something, you just do it manually. The second time you do something similar, you wince at the repetition, but you do it anyway. The third time you do something similar, you automate.”

CI/CD is an endless loop. Things are built, tested, deployed, and released, continuously, at a high pace. Ultimately, every pull request goes to production.

CI/CD visualized — Release new software daily

That means potentially, the software needs to be tested and deployed every minute. Having a manual deployment-process would be too expensive and too slow. Automate it with a deployment pipeline! Use Azure Pipelines or GitLab, for example, to automate the installation of the software. It pulls code from a repository, compiles it, and it installs it onto environments, automatically. It’s meant to automate the organizational process of deploying software. Like the testing, for example, or the stakeholder approval, or the configuration of the software.

A deployment pipeline automates the DTAP process. First, it’s installed onto a development environment, automatically. If it works well enough there, it’ll be shipped to the next environment, automatically, and so forth.

5.) Testing shifts left

Testing the same thing over and over again is boring and expensive. Releasing once a year and testing extensively for a month isn’t a problem. But having the team test the software extensively, monthly, daily, or even hourly is impossible.

Unfortunately, every change to a codebase introduces a potential bug. The longer it takes to discover it, the more effort it takes to fix it. To mitigate that risk, test-coverage is key. To get good coverage, always write an automated acceptance-test first, and implement the code changes later. As a result, all acceptance-criteria can be validated automatically.

Work starts with defining testable acceptance-criteria during a refinement meeting. They are automated later. Obviously, these tests will fail. Nobody has implemented the new functionality yet. The team keeps adding code until these automated tests pass, and when they do, the code goes straight to production.

Contract- and integration testing

Making systems that don’t have any dependencies is probably not possible. But the deployment of one system should not break another. And whatever is an environment, won’t be there for long. Updates will be installed in every environment all the time.

Will the new version of your system be compatible with the systems it depends on? And will the new version of your system be compatible with the systems that depend on it?

A system that has a dependency on another system will send information to it. It will receive a response containing the queried data. The two systems communicate in a predefined format. The format is also known as the data-contract. Validating these contracts is part of the process of installing software onto an environment and by that a part of the deployment pipeline. Automate it with a click-recording, end-2-end test, or by a bit more modern approach, by implementing contract tests in the deployment pipeline.

6.) The team builds it, so they run it!

How will the Ops department operate a system that changes every day? The Dev team and the Ops team will have to cooperate much more intensively.

To make CI/CD work, a development team needs to have end-to-end responsibility. When a deployment fails or causes trouble in an environment, nobody but the team will fix it. There’s no testing-coordinator in CI/CD. There’s no hardening phase in CI/CD. The team builds it, they ship it, they’re responsible to make sure it runs.

Continuously delivering value increments to production requires a variety of Ops tasks to be done more frequently. As previously stated, if you do something often, you need to automate it and include these tasks in the deployment pipeline.

Ops work is part of Development work. Every development team needs Ops to be part of the team.

Summary

CI/CD is much more than creating a deployment pipeline. CI/CD is a culture change. It changes software and people. Instead of being an invisible part of a software factory, in an isolated silo, development-team members will be implementing changes that are directly visible to the end-user.

Monoliths and CI/CD are a bad combination. To be able to deploy quickly, the application needs to be small enough to deploy. The individual parts of the systems cannot have too many dependencies. Instead of approaching IT technically, it needs to be approached functionally. Sometimes, that requires realigning the teams and the software.

Continuous Delivery = continuous testing. CI/CD and test-automation go hand in hand. Smart testing is the key to good CI/CD. A smart test provides the development team the information they need, proactively, as quickly as possible. Be smart about what tests to automate and how to automate it. Automate arbitrary checks in the easiest way possible, and have the developer run them, continuously.

CI/CD and DevOps go hand in hand. Deploying frequently changes the way a system needs to be operated. The only way to not grind the software delivery process to a halt is to have both Ops- and Devs in one team.