By Arielle Sullivan & Adarsh Shah
The craft of Software Engineering has been around for decades and we have learned and improved a lot along the way. Things like keeping code in source control are taken for granted these days, but we remember the days when the latest code existed on production servers or on an engineer’s machine.
As improvements to the craft of Software Engineering gained momentum, the way infrastructure was managed lagged, remaining a manual process for many teams. Overtime best practices for Software Engineering are being applied to infrastructure. The quintessential example is Infrastructure as Code.
Infrastructure as Code (IaC) is an approach that takes proven coding techniques used by software systems and extends it to infrastructure. It is one of the key DevOps practices that enable teams to deliver infrastructure, and the software running on it, rapidly and reliably, at scale.
You can find about those Infrastructure as Code practices here.
In this article, we will show how the history of Software Engineering has and will continue to shape the improvement of infrastructure practices. Then we will introduce newer concepts that will help further the craft of managing Infrastructure, beyond IaC.
Before Version Control became ubiquitous, determining the latest version of an application might be a matter of asking what code is on an engineer’s local machine who deployed it. We realized this is unreliable, error-prone, and makes collaboration difficult. Version control has allowed application code to scale & engineers to collaborate better.
Infrastructure continued to be managed manually long after application code left those days behind. Whether from a cloud provider’s UI or the command line from developers' machines, managing infrastructure manually makes it difficult to make changes or troubleshoot regressions. IaC made it possible for Infrastructure changes to be stored in Version Control and it has saved engineers the toil of tracking the current state of Infrastructure & made troubleshooting easier.
Initially, it was not uncommon for applications to be deployed with a bash script from a team member's laptop, with tests run ad hoc if at all. The frequent and reliable deployments of today were made possible through pipelines that have Continuous Integration & Deployment.
IaC has also benefited from the use of pipelines. With pipelines, infrastructure changes can be tested, deployment becomes repeatable & auditable, and managing dependencies and secrets is made easier. See more details here.
As applications grow more complex, deploying the entire application as a monolith can become a bottleneck to continuous delivery. A bug in one part of the codebase or infrastructure prevents the entire application from deploying. Domain logic and tech stack decisions are often tightly coupled throughout the codebase. Teams began breaking monolith applications into smaller, self-contained components, or microservices, that could be managed and released independently.
The same can be said for Infrastructure. Using a single IaC (like Terraform) to deploy all the infrastructure needed becomes complex and hard to maintain as you scale. To avoid that we started breaking the IaC into various pieces which helped with collaboration and reduced feature lead time for infrastructure. Breaking IaC into loosely coupled components makes it easier to understand and maintain it. Read more about it here.
IaC is a powerful and crucial tool. However, if you have ever managed many divergent environments with existing tools, you know there is still a long road ahead in improving developer effectiveness with infrastructure. You can read about challenges using & scaling Infrastructure as Code here.
There are other practices like Kubernetes, Package Managers & GitOps that have worked well in Software Engineering. Now we will look at those practices and how they can be applied to manage Infrastructure using Environment as Code (EaC).
Environment as Code (EaC) is an abstraction over Infrastructure as Code that provides a declarative way of defining an entire Environment. It has a Control Plane that manages the state of the environment, including relationships between various resources, Detects Drift as well enables Reconciliation. It also supports best practices like Loose Coupling, Idempotency, Immutability, etc. for the entire environment. EaC allows teams to deliver entire environments rapidly and reliably, at scale.
See below diagram that explains Environment as Code at a high level. You can read more about Environment as Code here.
The adoption of microservices made managing individual services easier but added complexity to application dependencies. Managing dependencies between various microservices can put even the most robust continuous delivery pipeline to the test. This makes deploying an entire application environment a pain point. Similarly, IaC code creates complex dependency graphs that can be difficult to navigate.
Several tools have gained popularity after microservices because they help simplify complex deployments. Containerization is one of those tools that helps organize dependencies so that an engineer doesn’t deploy a server, they deploy their application along with all the dependencies. (i.e. An environment). Helm charts and Kustomize take that concept even further, serving as package managers for a suite of many services.
Environment as Code (EaC) applies the same concept to an entire Environment by packaging all components within an Environment along with the dependencies between these components.
Environment as code, helm charts, and containers all:
With the increase in microservices adoption, it became clear that containerization makes it easy to manage those microservices. Then we had the Container Orchestration war that Kubernetes won and became the de facto orchestration tool.
Kubernetes made it easier to manage microservices applications especially due to its Controller pattern that watches the state of your cluster, then makes changes where needed to bring it to the desired state specified in code.
EaC applies the same logic to Infrastructure using the Kubernetes Controller pattern. EaC has a controller that tries to move the current state of the Environment to the desired state in the code. See below diagram that explains the Control Loop.
So far, we have talked about improvements made to close the gap between an engineer adding work to a code base and that code working and scaling in production. Turning to code submission itself as a tool for deployment is a natural next step. Kubernetes has already replaced a large portion of deployment pipeline logic with declarative configuration. GitOps builds on this concept and makes the entire deployment process configurable in code. It uses the Git Pull Request workflow and deployments are triggered by a code merge.
Tools like Flux and ArgoCD use the Kubernetes API to define a deployment that includes helm charts and other application dependencies, all in a single file. The platforms then deploy these custom deployment configurations continuously, by checking the contents of the file in source control.
GitOps = IaC + (Workflow + Control Loop)
This concept can be extended again to apply to entire environments using EaC. Read more about it here.
The software industry has made great strides in improving Software Engineering. We are hoping that this article will help demonstrate how we can continue to build on existing software development practices to achieve similar gains in productivity and performance with respect to managing infrastructure. Infrastructure as Code has already piqued the interest of many teams. Environment as Code is on the horizon.
Acknowledgments: Michael Wytock read the draft version of this article and provided feedback to improve it.