• Jun 18, 2021
In the previous article in this series, I detailed how Tilt provides a developer-centric experience for coding cloud-native applications destined to be run on Kubernetes clusters. This nascent approach to cloud-native application development is gaining some momentum, and in this article, I'll be taking a look at another tool in this space, Garden. In case you've previously heard of Garden in the context of containers, let's make it clear that we're not referring to the container runtime that is part of the Cloud Foundry project.
Garden is both, an open-source project for automating Kubernetes development and testing, and a corporation that provides a commercial variant called Garden Enterprise. The company is based in Berlin.
Moving from application monoliths to distributed, containerized microservices can be a daunting experience even for the most seasoned developer! Monoliths are derived from a tightly coupled set of modules in a single codebase, which makes reasoning about the codebase relatively straightforward. The same can't be said for microservices. When applications are split into loosely coupled, independent services managed by different teams, reasoning about an entire application gets hard.
“Instead of building products, we’re building plumbing.”
Garden was born out of a developer's frustration; where are the services that make up the application, how do they work together, how can I code and test against this labyrinth of services? A blog post written by one of Garden's founders likens the developer's experience to plumbing, where more time is spent trying to work out all the components and interactions between them, rather than on what's important — coding, building, and testing.
Garden seeks to remove this complexity from the developer's shoulders, to enable them to focus on what's important, and that's developing software. So, how does Garden achieve this?
At the heart of Garden's mode of operation is a representation of the application and its environment, called a Stack Graph. It's a directed, acyclic graph that is built from configuration data that resides alongside the different components of the application and its environment. Garden builds the Stack Graph from the distributed configuration data to create a representation of the application stack so that it knows which parts of the puzzle are affected by different component changes. This ensures that actions are only performed on those components that are directly affected by a new change.
Garden's function revolves around a
project which is confined within a directory structure on a host where Garden runs. Generally, the root directory of the structure contains a YAML config file that describes some project-wide characteristics and a bunch of sub-directories for each of the application's components. For the application represented by the stack graph above, sub-directories would exist for
api, and so on.
The project-level config file also defines environments and providers, which is where the action takes place. A provider is a type of target, and can be
local-kubernetes or just
kubernetes to signify a remote cluster. It can also be one of a number of different types besides; openfaas, terraform, container, exec, and so on. Garden is principally aimed at Kubernetes, but there is flexibility in target choice. In turn, environments are linked to providers and can be namespaced for granularity purposes.
Now this tells us a lot about the structure and configuration of Garden projects, but how do application components get built, deployed, and tested? To establish or update an environment or part of an environment, Garden has a comprehensive CLI for the purpose. Some of the more obvious commands are
garden build ,
garden test, and
garden dev. The latter command fires up the Garden Dashboard which gives us the view of the stack graph (as shown above), an overview of the results from the actions it's carried out (e.g. execution of unit tests), and access to logging information. Having a depiction of your application, and a summary of the output of different actions that have taken place, is extremely useful to a developer.
Modules are Garden entities that provide the necessary scaffolding to hang service definitions and associated tests from. But, they're also the atomic build unit for any services that need building (e.g. with Dockerfiles). Modules get their own config file that is contained within a sub-directory in the project root, along with any artefacts necessary for building, deploying or testing. To cater to the different abstractions and tools that might make up an application, modules are defined with a type which are providers as described above. For example, a module might have a
helm type, a
container type, a
kubernetes type, and so on.
│ ├── Dockerfile
│ ├── garden.yml
│ └── main.go
│ ├── app.js
│ ├── Dockerfile
│ ├── garden.yml
│ ├── main.js
│ ├── package.json
│ └── test
│ └── integ.js
A simple Garden project directory structure might look like the one above, with two modules called
frontend, respectively. Each module contains its own config definition in a file called garden.yml, and in this case, both are of the same provider type;
container. Even though the module definitions contain no reference to Kubernetes (i.e. no Kubernetes API resource definitions), Garden is smart enough to generate these definitions for the services described in the modules. Issuing the
garden deploy command results in container image builds for each of the services, followed by a deployment to the environment defined in the project's config (project.garden.yml).
Defining everything as declarative configuration not only makes the workflow portable but also reproducible. It can be triggered by hand by a developer, but can also be embedded in a continuous integration pipeline.
For developers, once all of the configuration has been defined for the project, iterating around the inner loop becomes an efficient, consistent, and productive experience. When changes are made to any component in the project, Garden only rebuilds, redeploys or retests the components where there is an explicit or implied dependency. This removes a huge burden from the developer, who no longer needs to be intimate with the infrastructure that underpins the application environment. Garden even provides a hot reload feature to help avoid container image rebuilds when making changes to the codebase.
Garden is definitely up there as a contender amongst the slew of new tools that aim to simplify and improve the cloud-native developer's experience. It has a whole raft of features I've not had the space to touch on, including a templating capability for module definitions (was that a groan I heard coming through the ether?), two-way syncing, and in-cluster container image building.
Perhaps, the first impression you might have when you first encounter Garden is complexity; the exact same thing that it seeks to remove from the developer's experience. At first glance, it can be hard to see how modules, services, providers, and workflows all fit together. But if you persevere, these concepts slot into place, and once the config has been defined for a project and its modules, the hard work is done. It's then up to you to capitalize on the set-up and be as productive as you wish with your application development.