This is Part 1 of a 3-part series that demonstrates how to construct a complete end-to-end GitOps working using Terraform plans, GitHub, GitHub Actions, and Azure. This article takes a look at GitOps — its origins, what it actually is, and how it relates to the term IaC and the pipeline concepts of CI and CD.
Introduction to GitOps
In this article series, we’ll explain some GitOps theory and then dive into hands-on practice using Terraform, Azure, and GitHub. We will start by taking a look at GitOps — its origins, what it actually is, and how it relates to the term infrastructure as code (IaC) and the pipeline concepts of continuous integration (CI) and continuous deployment (CD).
In this article, we will look at the history of software engineering practices and how they have led to the evolution of GitOps.
Once, There was Waterfall
For a long time, software was developed using the waterfall method — an approach where the entire software development lifecycle was completed as a single, large piece of work. In this model, the first time the software was used by a client was on release day. With such long cycles, it was a long time before development teams got feedback and had a chance to release a new version.
This worked just fine for the methods of software distribution available at the time — shorter cycles would have been impractical. This way of working provided Operations teams with plenty of time to provision the infrastructure for launch day.
Then Came Agile
Arguably, the main benefit of adopting Agile methodologies in software development was the idea of making smaller changes and releasing much more frequently — something that was facilitated by the new methods of distribution becoming widely available — moving away from solid media.
This methodology was definitely capable of getting code out faster, but operations teams were working with tools and practices that could not always support the increased frequency of changes.
DevOps Became a Thing
This is one reason DevOps evolved — to drive a culture where developer and operations teams work much more closely together. And working together, they were able to automate builds, tests, and deployments, to support the increased release cadence from agile.
The size and complexity of applications have grown, though, with systems built from huge numbers of microservices — and the demands on those systems are growing too. As such, infrastructure solutions need the ability to scale and reduce infrastructure resources dynamically in response to loads. This is what the cloud does very well.
Developers benefited greatly from working closely with operations, but all the resulting cloud services are a lot to manage. And, with old methods, it is easy to lose track of what infrastructure we have, what infrastructure we need, and whether the two match up.
Then, There was GitOps
In hindsight, it seems obvious. For operations activities to keep up with developer cadence, they should adopt the same DevOps best practices used for application development and apply them to automate infrastructure provisioning.
At the core of this approach is Git source control, used as the single source of truth for what infrastructure is currently deployed. Layered on that, we have three core components: IaC (infrastructure as code), pull requests, and CI/CD.
Infrastructure as Code
The standout change with GitOps is the use of Git. Git is a version control system that tracks changes to code over time. To use Git, we now need to represent our infrastructure in code form. This is one of the key components of GitOps, called infrastructure as code (IaC), where we use code to describe the desired state of our infrastructure.
Pull Requests as a Change Gate
Operations teams use Git workflows to work collaboratively on a shared source of truth. A pull request is a key part of Git workflows. It is a way for someone working on a clone of a repository to request that changes they have made in their clone are pulled into the main repository.
When a pull request is opened, it is normal to require a code review. This is an opportunity for other team members to inspect the new code, write comments suggesting changes, and grant their approval (a repository will be set to block any incoming code without approval, like a ‘gate’ for all changes to our infrastructure).
Since Git will keep a history of all changes, it also acts as a clear audit log.
Continuous Integration and Deployment
Continuous integration (CI) is another best practice borrowed from DevOps. It refers to automating how we integrate code changes with a repository. It helps multiple contributors to merge changes into a shared, central repository to reduce the risks of adverse effects through changes that break things. This is achieved with tools that run automatically to execute tests when new code is added, typically in a pull request. In the flow shown in the diagram above, we can see that our infrastructure updates are only going to be merged in from the pull request after two conditions are met:
- Code review approved by the team
- Automated checks (or tests) all pass
Continuous deployment (CD) refers to a setup where all new changes pulled into a repository are automatically deployed to production (if they pass the pull request checks). This is an ideal to aim for, which requires all your processes, particularly your tests, to be excellent. Some teams choose to gate production environments with manual approvals too. Whatever level of maturity your CD is at, a common theme is the ‘pipelines’ (or tools) for automating all of the infrastructure provisioning steps.
CD can also refer to continuous delivery, where the repository is always kept in a releasable state — even if you are not deploying every change. While your release rhythm is a matter of preference in application development, a shorter release cycle and smaller changes reduce risk. A purest might argue that, for GitOps, this is non-negotiable as the repository should reflect the current state of production. But we can argue that if the audit trail from the deployment pipeline to the repository is in place, it is always clear whether our repository is reflecting the current state or a future desired state at any point in time.
A Quick Note on Push Versus Pull Deployments
GitOps deployments can either be push- or pull-based. They differ in the way they keep the environments in the desired state. Pull-based is preferred as it is more secure and offers better support for continuous delivery, but push-based is simpler to get started with, and Terrform is designed for push-based deployment.
Push-based will be more familiar to those coming from application development. When the application code is updated in the repository, a build pipeline gets triggered that ultimately pushes the changes to the production environment(s). It wouldn’t automatically notice any changes made directly to environments.
Pull-based deployments are not triggered by code changes. We need an ‘operator’ service that continuously compares desired state in the repository code with the actual current state in the deployed infrastructure. If it finds changes, it pulls in the changes, updating the infrastructure to match the repository.
GitOps provides a single, unified tool for an organization to manage both their infrastructure and application development, building on improvements to collaboration made with DevOps. There are many benefits, including:
- Disaster recovery — This allows you to rapidly redeploy any or all of your infrastructure. Or, you can roll back and forward to any point in time in the Git log to deploy a particular desired state of your infrastructure.
- Security — Using Git means that all changes are logged in the history. These change details can be recorded in pull requests and Git commit messages, answering those audit questions of the who, what, and why for infrastructure changes. GitOps also reduces the risk of sharing login credentials as developers no longer need to have access to servers.
- Self-documenting deployments — All changes made to your environments go through your Git repository. You only have to look at the latest commit on your main branch to know exactly what is in production — no need to remote into servers to audit state.
In the next article, we will put theory into practice and write some IaC using Terraform, push it up to a Git repository on GitHub, and have a go at a simple workflow using a code review process with a pull request to merge infrastructure changes into our code.
To learn more about GitOps and how you can use GitOps to manage the configurations of your applications deployed to Kubernetes, check out the resource How to use GitOps with Microsoft Azure.
Ben is the Principal Developer at a gov.uk and .NET Foundation foundation member. He previously worked for over 9 years as a school teacher, teaching programming and Computer Science. He enjoys making complex topics accessible and practical for busy developers.