What’s the real difference? Continuous Integration & Continuous Deployment (CI/CD)

What’s the real difference? Continuous Integration & Continuous Deployment (CI/CD)

This post may seem basic by many developer’s standards but for junior developers I believe CI/CD is a topic that’s commonly overlooked.

I was a little stumped the other week. The Principal Developer in our tribe, Brett, asked me “what’s the difference between CI and CD?”. We were talking about career goals at the time. In my head, I thought I knew the difference, although it was a little fuzzy in my head. I muttered out something along the lines of “Uhh, well it’s about integrating Git branches and deploying them independently from our machines”. He stopped me short. “CI/CD is a solution to a problem” he said. “It’s not just about Git”. I felt embarrassed that I couldn’t articulate why I use the tools that I use every day.

So I went away and made a note on my personal Trello board to investigate this topic in more depth. I don’t want to be in this position again and I certainly want to know why we use the tools that we use. Long story short, after doing some reading on CI/CD there was nothing new to me in what I came across. I already understood the concepts. It’s more that I couldn’t articulate them to someone else.

I’ve summarised my reading below into a single page document. It’s something I like doing when I want a quick refresher on a topic many months in the future it makes it easy to digest.

Continuous Integration diagram
Continuous Deployment diagram

There are a lot of materials online about CI and CD. It certainly helped as I was reading to think of how this relates to the way we do things in my team. We use Github and the feature branch workflow for our integrations and Buildkite for our deployments.

Continuous Integration

What problem does it solve? Avoiding “Integration Hell”: developers on the same team work on many features at once - trying to integrate changes together back to the ‘mainline’ (all the stuff that was previously developed) without conflicts can be tough. The principle is that you don’t integrate your changes unless they play nicely with what’s in mainline. How do we do that? The best way is through automated tests that are triggered once you try to merge changes to the mainline (master). We want all of those tests to run quickly and tell us whether our changes break anything else. Quickly is important here - we don’t want to wait for hours for test to run.

By doing this, we give ourselves more confidence that changes will integrate without breaking existing features. By running this on a third party system and making an independent arbiter run the code we avoid someone making the excuse “But it works on my machine”… Uhh, who cares it if works on your machine!?

How have I seen it used? We use a combination of Github and Buildkite. In my team, once a feature is completed we do a code review as a team and then I’ll make improvements based on any suggestions. Every time a change is made and those commits are pushed, Buildkite will run all automated tests as if it were in a pre-prod environment, if all tests pass, the build succeeds, otherwise it fails and we need to debug and do more work. However, from my experience I always run all tests on my machine before pushing it anyway. In my team we’ll usually run unit tests, integration tests and performance tests on Buildkite. If they all pass successfully we merge from the branch to master and that automatically triggers the app to be deployed to a pre-production environment for some manual testing (described below).

Refactoring tests for fast feedback

One thing as a team we applied in regards to having a quick feedback loop: removing Spring being brought up in all of our tests. One of the services we maintain is a Java/Spring Boot web application. In some of the unit tests, we noticed that JUnit was using the SpringRunner for a lot of tests - this meant that for every single test, Spring was being brought up which slowed down running all tests considerably. We refactored a lot of our tests to be independent of Spring and turned them more towards pure ‘unit’ tests, testing only business logic without bringing up a Spring container for each test. Being able to run all tests quickly after this refactoring is a godsend.

Continuous Delivery

What problem does it solve? Being able to deploy new features, bug fixes and other changes to production (to customers’ systems or devices) at any time. There’s no need to build up a backlog of features and wait for a quarterly/annual production release. A team might decide to deploy new changes feature-by-feature, as we do in my team, after they pass all tests in the integration stage. These features can be really small, such as a new field in the JSON payload being returned from an endpoint.

How have I seen it used? As described above, once our feature is completed and was successfully merged to the master branch, it will also have been deployed to the pre-production environment thanks to Buildkite. Time for some manual testing: We can now work alongside our Quality Analyst (QA) and open up Postman, create some requests with dummy data and check to see if the HTTP responses we get are as expected. Once we’re happy with that step, we can go back to Buildkite and deploy it from Pre-Production to Production with one click. Once that click happens, the app is containerised via Docker and automatically deployed in the background via a managed Kubernetes cluster on AWS. Everything is pre-configured, all new changes made to the app end up in the new Docker container. Continuous Deployment can be really hard - you need this ‘template’ of infrastructure ready to go. Once that’s all set up, this can make deployments really easy.

What about Continuous Deployment?

In my team we are a bit more on the ‘safe’ side - we only deploy to production after we’ve done some further testing and reasonableness checks with our QA in the pre-production environment. Some teams may decide that they care more about quick customer feedback; perhaps they also have greater confidence in their automated tests. Every single change deployed to pre-production may also be deployed to production at the same time. This way, features are out in the public arena very soon after the code was developed.