CI/CD is a modern approach how to bring software that was developed by teams is brought to the customer in modern days. CI/CD is usually used for cloud-native software, software that runs on a server. We could consider CI/CD as an answer to questions like: How can we achieve good software quality? How can we make the whole process reproducable? How can we always know, which version of which software was running at when?
Traditional Software Development and Deployment
Before we start, let us have a view how development and deployment of software was done on a server previously. Usually there was a database instance hosting multiple databases and a server that hosted web projects. The admin had to look that all prerequisites of the software are installed as mentioned in an install manual. Then he adapted the config files and started the service.
This was not a bad approach, but had it's cons. E.g. it was not very easy to run multiple different versions of PHP on the same webserver. Same for Python, Ruby, etc. Virtualization helped a lot, since it was at least possible to have on VM for each different software version.
Upgrading the VM was also a challenge. Sometimes it was easier to set up a new VM and migrate each webservice from on VM to the other. This always included adapting the routing, and for site-internal stuff also DNS.
Also the administrator always needed to know how the application works internally. This is not unnecessary knowledge, but the admin needed to know about many different applications.
Containers
To make it short, containerization is a competing approach. Think of very lightweight platform, that is even more lightweight than Virtual machine. Containers are a bit like a process that is limited to a directory so much that the directory seem to be the whole system from the process perspective. In fact the kernel and hardware resources is shared with the host. There is no virtualization and also no emulation.
Modern container technologies provide the container with network, storage, logs and outside communication. The container itself runs the process and contains all the prerequsites necessary for the process to run. They are only installed inside the container. To acchive that, containers are build from images, that already contain the container's filesystem including all prerequisites. Those images are build in advance. They can always be reproduced by folling the instructions how to build them, just like without containers.
But, wait, what is the benefit?! The power comes not only by the machine readable instructions.
In fact all the instruction are written down in a file called Dockerfile. Using this and a
build context, we can reproduce the image. The image then can be used to reproduce the container
multiple times on different platforms. One just has to run
docker build <build context> -f <Dockerfile>
# or
docker build <build context> # for use Dockerfile as default value
When we then have this container this could be started easily using
docker run <image>
It does not matter which software we have, building the image and running it always works the
same. An administrator does not have to know what it is inside the container that much, only
how much resources it needs to run. docker build/run are also highly automizable, you could
use a simple bash script to help with that.
Of course containerization has a lot more advatages but this would bloat this post here. Let's concentrate on the CI/CD part. Now that building images and running containers is so easy, we could think of integrating that to our development process and just automize it.
The development process
Modern development processes highly integrate some kind of version control system (VCS). Most of
the time we talk about git. Git allows teams to collaborate on one single project. One common
paatern is that a devloper create a new branch for a new feature he wants to implements. On this
he commits his code. When he then finished he asks for merging into a special branch. Here another
developer comes in reviews the code and merges it into the requested branch.
Usually each developer should also provide automated tests for the code he commits. Those tests should always pass, if not the developer has to fix the problems. With those tests, merging a new feature is not that hot since previous code also has tests that already pass before merging and should also pass after that step.
Continous Integration
When the merge is done, code will be pushed to the specific branch and now git or an
environment around it, will detect this and could now start building a new container image. Since
also some integration tests are already automized, we can also invoke those. If everthing passed
we can then push the image to a central registry.
This automated process is always run after a git merge ...; git push <specific branch> command.
This happens all the time, so integration is done continous not just once a year. Therefore this
is called Continous Integration, or in short CI.
Without changing our development process we already included the build of a new container image. Automating this is highly recommended, since this can be logged and is therefore reproducable. Also, building an image is alwayas the same, with some small differences because there are also some arguments you can pass to the build processe, that could be needed. All big platforms with this feature implement features to provide that flexibility.
Deployment
We now already have that image in our registry, but how can we get the container up and running. But also here we could just run a bash script on the server that checks, if we had a new version of that image and just run that. But then we cannot track, which version was started then and we also do not know which configuration we had.
What, if we define a desired state of our application in a text file? We could put that in our version control system (e.g. git) and now always know which version and config was desired at which time. Then we have a software that continously checks this and compares it with the real application's state. Whenever there's a difference, it stops/start containers with the desired image.
The big benefit is, that the desired state must be written in a machine readable config. The machine will always read it the same way, so there will never be differences between the work of different people.
Since we also already have our integration which end with the integration tests, we could also
trigger the change of the desired state with the success of the integration. This is what we call
Continous Integration.
Summary
CI/CD is a method to automize the process to bring applications from the developer team to users. It does not replace the development process but attaches exactly at it's end. Containerization is not a must-have but makes deployment much easier. Since it standardizes processes and guarantees platform uniformity inside a container it allows to automize a lot.
This post is just a rough overview about the concept of CI/CD in general. The core technologies will be covered in other posts. It would be to much for such an article.