Implementing GitOps practices will take your software delivery pipelines to the next level. Declarative, immutable, and continuously reconciled infrastructure brings many benefits when managed through GitOps best practices. Over the years, I have helped many development teams build and improve their GitOps workflows. In this blog, I will share four approaches to managing code used in those pipelines.
The “Ops” half of “GitOps” refers to configuration code, or Infrastructure as Code (IaC). Software depends on the resources managed by this code to function. Managing this configuration in Git repositories offers many benefits. Often the structure of this code is an afterthought, which leads to significant refactoring in the future.
Below is a Node.js project with application code in the root, and YAML files in the kubernetes directory. Changes to development.yaml apply to the development environment, changes to production.yaml apply to the production environment.
- Infrastructure code and application code in the same repository keeps everything versioned together. There is no need to connect the dots between multiple repositories to reproduce the state of the application and configuration at a certain point in time.
- One repository means less context switching for developers. Developers don’t need to change repositories when making changes to infrastructure code.
- No privilege separation. Developers with access to the repository will be able to change both application and infrastructure code.
Some organizations require separation between application and infrastructure code. The examples below all manage application and infrastructure code in their own repositories. This improves privilege separation as each Git repository can set its own user privileges.
You may be familiar with Git branching workflows such as Gitflow. Gitflow has fallen out of favor recently as trunk-based development has gained popularity. There are good reasons to avoid more than one long-lived branch in your Git repository. Yet, multiple long-lived branches are still worth considering in certain cases.
Below is a Helm chart repository with two long-lived branches, development, and production. Changes always originate in the development branch. Promotion to production requires merging development into production. The development environment uses the development-values.yaml values file in the development branch. The production environment uses the production-values.yaml values file in the production branch.
- Low risk of configuration drift when promoting changes between environments. Merging branches ensures that no changes will be missed.
- Improved privilege separation between development and production changes. For example, GitHub supports branch protection rules. The owner of the repository can control which users can commit to a branch.
- This is a “one lane road” for your infrastructure code. Changes in the development branch can block production changes (without cherry-picking desired changes).
Now, let’s consider a repository where a single long-lived branch exists (main). Each environment has its own directory.
Below is a Terraform repository with separate development and production directories. Changes to development use the development.tfvars tfvars file in the development directory. Changes to production use the production.tfvars tfvars file in the production directory.
- Changes made in the development directory do not affect the production directory.
- Increased risk of configuration drift between environments. There is a high burden on the developer to understand differences between directories.
- No privilege separation. Users can make changes to both development and production environments.
Let’s consider an approach where each environment has its own dedicated repository. Each repository has a single long-lived branch (main).
Here is an example Terraform project, where development and production are separate repositories. Changes to development use the development.tfvars tfvars file in the development repository. Changes to production use the production.tfvars tfvars file in the production repository.
- Highest level of privilege separation. Any feature that your Git host provides around user/group access at the repository level will be available to you. Only users that need to make changes to production will be able to commit changes to the production repository.
- Easier to bring up new environments, or migrate existing environments. When bringing up a new environment, create a new repository. There is no need to integrate with an existing repository to bring up a new environment.
- Higher risk for configuration drift between environments. There is a high burden on the developer to understand differences between repositories.
In my experience, one repository per environment is the most future-proof method for managing your GitOps code. The privilege and environment separation benefits outweigh the potential drawbacks. If you decide the separation is not required in the future, you can collapse multiple repositories into one. The good news is that whatever method you choose, Harness’ suite of products supports them all.
Whether you are building, testing, and publishing artifacts with Harness CI, deploying with Harness CD, or taking your pipelines to the next level with Harness GitOps (currently in beta), we’ve got you covered! Also, every Harness pipeline can take advantage of advanced features around governance, chaos engineering, and more.
Come see how Harness can help accelerate your GitOps journey. Sign up for a 14-day free trial and follow our Kubernetes CD Quickstart guide to deploy an application to your cluster. If you are looking for a guided tour, book a demo.
The post Managing the ‘Git’ in ‘GitOps’: 4 Ways to Structure Code in Your GitOps Repos appeared first on Harness.