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.
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.
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
$ 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 firstname.lastname@example.org: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
$ ls yo Dockerfile garden.yml yoservice
We can see a
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
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
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
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
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! ✔️
You can run the build phase on its own with the
garden build command.
We can see that we’ve built and deployed our
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:
Cool. We can do the same with the
mate service, let’s do that via
$ 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 console shows us our running services and the Garden Stack graph.
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.
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.