Garden

I’ve been playing with a lot of development work flow tools over the last few weeks. I was an early adopter of Docker and have always been passionate about reducing friction in the development process, especially locally. Quick performance wins for individual developers add up to substantial productivity improvements across a whole team. I’ve been interested in how these tools are evolving, as we expand beyond the basic tools Docker provided with containers. As an outcome of this exploration I’m going to write a few posts on these tools.

note
These posts are explorations of each solution, not comparisons or a pros and cons analysis. I think everyone’s development platform and experience is sufficiently different that they should explore solutions and identify which works for them. Additionally, in many cases, these solutions might not fit at all and something like Docker Compose might address your needs.

The posts:

Garden 🔗︎

Garden …

orchestrates your development work flows to make developing microservices and functions both faster and easier.

Garden manages your app from source code, through development, testing, and then make deployment repeatable and consistent. With Garden you can model your services locally, test them, and then deploy them to production (or the next stop on their journey - development, testing, QA, etc). This not only allows you to model what’s happening locally, with Garden you develop with a “hot reload” capability, but it also allows you to test services without needing to locally mock or stub them out. Instead of discovering a bug in CI because of an integration issue, find it locally first and fix it before your CI run.

Garden architecture 🔗︎

Garden is graph-based, each component of your application is defined independently and can be explictly or implictly dependent on other components. This allows you to locally model applications as they appear in production. This also means that when you change something the graph ensures only what needs to be changed will be, reducing the time needed to build or re-deploy applications.

The root of the graph is a project. Projects contain providers and modules. Providers are the deployment environments for Garden, for example a local Kubernetes cluster. They provide the “how” of implementing your project. Modules contain your services and two other primitives: tasks and tests. Each modules has a type, like a container or a Helm chart. Each type of module can contain zero or more services, the applications you want to write. You can build dependencies between these services that will be reflected in your graph.

There are two other primitives: tasks and tests. Tasks are activities you can run and wait for them to finish. Tests are either unit tests for your specific services or integration tests that you can make aware of Garden’s graph to allow you to do inter-service integration testing. Both tasks and tests can also be dependent on services or other primitives.

Let’s install Garden locally and run it.

Installing Garden 🔗︎

We’re going to install on macOS but the documentation has installation instructions for Linux and Windows too. We will need to install Docker and a local Kubernetes environment (you could also use an external Kubernetes cluster). One of the easiest ways to do this locally is to use Docker for Mac, which installs both. You could also install something like Minikube.

Install one or the other and initiate a local Kubernetes cluster, either enabling it in Docker for Mac, or initiating and building a Minikube cluster.

Then we can install Garden itself is via Homebrew. Garden has its own tap you can add and a recipe called garden-cli.

$ brew tap garden-io/garden
$ brew install garden-cli

You can later upgrade Garden with brew update; brew upgrade garden-cli.

We can now confirm Garden is working.

$ garden version
0.10.3

Building a Garden project 🔗︎

To test Garden, let’s create our first project. We’re going to use a Garden example we’ve pre-built to take you through the process. You can find it on Github.

Let’s checkout our project.

$ git clone git@github.com:jamtur01/garden-example.git
$ cd garden-example
$ ls -la
README.md  garden.yml modules

Here we’ve checked out the project, changed into the resulting directory, and listed its contents. We have a README for the project, a garden.yml file, and a directory called modules. You’ll see several garden.yml files in our project, each defining some element of the project. The top-level garden.yml file is the heart of our project. It contains the definition of it. Let’s look at it now.

project:
  name: garden-example
  environments:
    - name: local
      providers:
        - name: local-kubernetes

We can see our project is named garden-example and we’ve specified an environment. Environments can be things like development, testing, etc. Ours is called local. It contains a single provider: local-kubernetes. Which indicates to Garden that we’ll run any services in the environment in a local Kubernetes cluster. Other providers include external Kubernetes clusters and clusters running on Cloud platforms like Azure, AWS, or GCP.

We’ve also created a directory called modules to hold our modules. Let’s look inside.

$ ls modules
yo mate

We can see two modules, one called yo and one called mate. Let’s look at the yo module.

$ ls yo
Dockerfile garden.yml yoservice

We can see a Dockerfile, another garden.yml file, and a directory called yoservice, that contains the source code for a service in this module. The Dockerfile is a standard Docker Dockerfile and tells Garden how to build our service.

FROM golang:1.12.5-alpine
MAINTAINER James Turnbull <james@lovedthanlost>
ENV service_path /go/src/github.com/jamtur01/yoservice/
ENV PATH $PATH:$service_path
WORKDIR $service_path
COPY yoservice/ .
RUN go build .
ENTRYPOINT ./yoservice
EXPOSE 8080

The real magic is inside our second garden.yml file, which defines the configuration for our yo module.

module:
  name: yo
  description: Yo Service - the Go edition
  type: container
  services:
    - name: yo-service
      ports:
        - name: http
          containerPort: 8080
          servicePort: 80
      healthCheck:
        httpGet:
          path: /yo
          port: http
      ingresses:
        - path: /yo
          port: http
      dependencies:
        - mate-service

This garden.yml file contains our yo module configuration. We give the module a name, yo, and a description. We also give it a type, this is going to tell Garden how to handle and deploy the module. In our case, our type is container, a Docker container. You can also create a helm chart or an exec module, which allows you to run commands locally.

We then define any services inside the module. Services are the individual components that Garden deploys. We’ve defined one service: yo-service. The service definition will look pretty familiar, it’s very similar to a Kubernetes service definition. We expose port 8080 as port 80, define a HTTP GET health check on the path /yo on our HTTP port. We also define an ingress to allow access to that path and port.

Lastly, we define a dependency. Garden service dependencies specify other services or tasks that this service depends on, i.e. things it’ll start or run first. You can also create dependencies between modules, allowing you to create whole sets of services that are dependent on one another.

When we deploy this module with Garden, it’ll first build the mate-service and then the yo service inside this module. To build the service, it will create a Docker image, using our Dockerfile, for the yo service and then deploy it to our local Kubernetes cluster.

We’ll skip over looking at the actual source code of the yo service, you can find it here on Github, it’s a very simple Golang web service. Instead let’s quickly look at the mate module and its service. If we change into the mate directory we’ll find another garden.yml file and a matching Dockerfile.

module:
  name: mate
  description: Mate Service - the Ruby edition
  type: container
  services:
    - name: mate-service
      ports:
        - name: http
          containerPort: 8080
          servicePort: 80
      healthCheck:
        httpGet:
          path: /mate
          port: http
      ingresses:
        - path: /mate
          port: http

This configuration is near identical to our yo module, barring it’s not dependent on anything else. The underlying service is written in Ruby (also available on Github) and is exposed via HTTP on the path /mate.

Now we have our project, modules, and services defined, let’s see about deploying it.

Running Garden 🔗︎

We’ve assumed you’ve installed and got running some local Kubernetes cluster, via Docker or Minikube, etc. We then tell Garden to initialize our environment to ready it for deployment. We do this using the garden init command.

$ garden init
Initializing local environment ⚙️

✔ providers                 → Preparing environment... → Ready
   ✔ local-kubernetes          → Configuring... → Ready
      ✔ tiller                    → Deploying Tiller... → Done (took 9 sec)
         ℹ tiller                    → Service deployed
         ℹ tiller                    → Service deployed
      ✔ tiller                    → Deploying Tiller... → Done (took 11.5 sec)
         ℹ tiller                    → Service deployed
         ℹ tiller                    → Service deployed
      ✔ kubernetes-dashboard      → Building version v-0ad8435b80... → Done (took 6.6 sec)
      ✔ default-backend           → Getting build status for v-eddc568920... → Done (took 0.1 sec)
      ✔ ingress-controller        → Building version v-e4c961fcdf... → Done (took 7 sec)
      ✔ default-backend           → Deploying version v-eddc568920... → Done (took 3.7 sec)
         ℹ default-backend           → Service deployed
      ✔ kubernetes-dashboard      → Deploying version v-0ad8435b80... → Done (took 12.7 sec)
         ℹ kubernetes-dashboard      → Service deployed
      ✔ ingress-controller        → Deploying version v-e4c961fcdf... → Done (took 30.7 sec)
         ℹ ingress-controller        → Service deployed

Done! ✔️

During this process, Garden creates the scaffolding neded to run our project on top of our local Kubernetes cluster. Garden will be deploying our modules and services into this scaffold.

Now, again from the root of our project, we can then deploy our modules and services using the garden deploy command. There’s two stages to a Garden deploy, the first builds everything in our modules and the second deploys what we’ve built. Let’s build and deploy now.

$ garden deploy
Deploy 🚀

✔ providers                 → Getting status... → Ready
✔ mate                      → Getting build status for v-78b5201081... → Done (took 0.6 sec)
✔ yo                        → Getting build status for v-4097e2dac1... → Done (took 0.6 sec)
✔ mate-service              → Deploying version v-78b5201081... → Done (took 6.3 sec)
   ℹ mate-service              → Service deployed
   → Ingress: http://garden-example.local.app.garden/mate
✔ yo-service                → Deploying version v-4097e2dac1... → Done (took 6.2 sec)
   ℹ yo-service                → Service deployed
   → Ingress: http://garden-example.local.app.garden/yo

Done! ✔️

tip
You can run the build phase on its own with the garden build command.

We can see that we’ve built and deployed our mate and yo modules and the services within them. Each has returned a local URL that they are deployed on. Let’s visit the yo’s services URL: http://garden-example.local.app.garden/yo.

Yo, yo service

Cool. We can do the same with the mate service, let’s do that via curl.

$ curl http://garden-example.local.app.garden/mate
Mate!

Developing with Garden 🔗︎

Now we have Garden running our services, we can develop them further, interactively in much the same way you can build and test a static site with Hugo or Jekyll. To do this we start the Garden development server.

$ garden dev
...

🕑 Waiting for code changes...

🌻  Garden dashboard and API server running on http://localhost:54470

This launches a developer console and API server, and then that waits for changes to our code.

We can also see the console in our browser.

The Garden console

The console shows us our running services and the Garden Stack graph.

The Garden stack graph

tip
We can also see logs for our services and a link to the local Kubernetes cluster’s dashboard.

If we make a change to our project, let’s say add another endpoint to one of our services, we’ll see Garden rebuilding and redeploying our service. Let’s add a /folks endpoint to our mate service. We’ll add code to our service,

. . .

get '/folks' do
  "Folks!"
end

And then a new ingress in the garden.yml file for the module.

. . . 
    ingresses:
        - path: /mate
          port: http
        - path: /folks
          port: http

Once we’ve saved this code take a look at the Garden dev console we’ve started on the command line and you’ll see that the service has been rebuilt and redeployed.

mate-service              → Deploying version v-11cd36537d... → Done (took 6.7 sec)
   ℹ mate-service              → Service deployed

If we now query, we’ll see our new endpoint.

$ curl http://garden-example.local.app.garden/folks
Folks!

Woot! Local services development, updated real time.

Summary 🔗︎

I hope this was useful as a walk through. This just scratches the surface of Garden’s capabilities, it’s also possible to use Garden with shared, external dev/test environments, making use of private namespaces, and integrate it with your whole CI life cycle. You can explore this, and other uses cases in the Garden documentation.

comments powered by Disqus