Stepan's technical notes

Notes about my journey to the Cloud

01 Dec 2019

Boost your productivity with Garden

The last couple of months I’m focusing on Developer Productivity, the technology stack is always almost the same: Docker, Kubernetes, Gitlab CI or similar (Circle CI). Yeah, pipelines are really cool but the time from the commit to the actual deployment is just too long sometimes. And there’s also the secondary problem: setting up a local development environment is mess sometimes.

Fortunately, there are already some tools which can shorten feedback loop for the development environment! There’s a variety of tools that can somehow mimic local development with Kubernetes: Draft (from Microsoft), Tilt, Telepresence, Skaffold (from Google) or Garden.

The last one is the tool I want to talk about now. Garden.io. With Garden.io, you can streamline development for containerized environment like it’s nothing.

Let’s just go through the simple scenario so you can make your own picture.

Development environment

As you might already know, I’m part of #KUBE100 focus group, hence I will be using this amazing service for the demonstration of Garden.io. Anyways, this setup will be working with any Kubernetes cluster. There are not any requirements for the specific Kubernetes implementation.

Golang application

In this demo, we’ll be using a simple Go application that leverages Gin framework.

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"hello": "world",
		})
	})
	r.Run()
}

Nothing fancy. This is the Dockerfile:

FROM golang:1.13.4-alpine3.10 AS builder
ENV GO111MODULE=on
WORKDIR /root
COPY go.mod go.sum ./
RUN go mod download
COPY main.go ./
RUN GOOS=linux go build -o app main.go

FROM alpine:latest
WORKDIR /root
COPY --from=builder /root/app ./
CMD ["./app"]

As you can see, we are using Go modules so we can have really small git repository and we can leverage Docker Layer Cache at the same time.

Project structure

Our project contains just one service, but in case of need, it can be easily extended for as many services as we need.

.
├── backend
│   ├── Dockerfile
│   ├── garden.yml
│   ├── go.mod
│   ├── go.sum
│   └── main.go
└── garden.yml

As you can see, Garden.io is using multiple garden.yml to specify the actual behaviour of the stack.

Let’s see the top-level configuration, this file contains configuration of the whole project.

kind: Project
name: sample-app
defaultEnvironment: remote
environments:
- name: remote
  providers:
  - name: kubernetes
    context: cl01
    defaultHostname: my-cluster.example.com
    deploymentRegistry: 
      hostname: docker.io
      namespace: vranystepan

you can note there couple of things:

  • we will be using remote Kubernetes cluster identified as cl01 context in the local kubeconfig file
  • services will be accessible via http://my-cluster.example.com
  • Docker images will be pushed to the Dockerhub

Now, let’s check the configuration of backend module:

kind: Module
name: backend
description: Go service container
type: container
image: backend
services:
  - name: go-service
    ports:
      - name: http
        containerPort: 8080
        servicePort: 80
    ingresses:
      - path: /api
        port: http

Let’s quickly go through important facts about backend module:

  • it will be built as backend image so the final docker image will be docker.io/vranystepan/backend
  • service will be exposed on the port 80 and traffic will be forwarded to container’s port 8080
  • servive will be mapped to path /api so the final URL will be http://my-cluster.example.com/api

Show time

So now we have everything configured so we can test if everything works as expected, right? This part is really simple. When the Garden is initialized, you just need to run garden deploy and everything happens automatically.

$ garden deploy
Deploy 

✔ providers                 → Getting status... → Done
✔ backend                   → Pushing image docker.io/vranystepan/backend:v-34c6adea2d to cluster... → Done (took 24.6 sec)
✔ go-service                → Deploying version v-34c6adea2d... → Done (took 11.8 sec)
   ℹ go-service                → Resources ready
   → Ingress: http://my-cluster.example.com/api

Done!

And that’s it! We have a running instance of our application in the Kubernetes cluster and it took only a few seconds.

Context, what actually happened there

Now let’s go through some Kubernetes resources and see what actually happens when we run garden deploy.

First of all, let’s see all the pods:

$ kgp --all-namespaces
NAMESPACE     NAME                                       READY   STATUS    RESTARTS   AGE
kube-system   metrics-server-6d684c7b5-z45zl             1/1     Running   0          114m
kube-system   local-path-provisioner-58fb86bdfd-qhbkv    1/1     Running   0          114m
kube-system   coredns-d798c9dd-746pb                     1/1     Running   0          114m
sample-app    tiller-deploy-7559467cf6-4mp62             1/1     Running   0          101m
sample-app    go-service-v-34c6adea2d-59484cf74b-kwkqw   1/1     Running   0          4m5s

Obviously, everything is happening in the namespace which is the same as the Garden project name.

Let’s see if Garden is using Kubernetes Deploymens or not:

$ k get deploy -n sample-app
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
tiller-deploy             1/1     1            1           104m
go-service-v-34c6adea2d   1/1     1            1           7m4s

Also, let’s see the service configuration:

$ k get svc -n sample-app                             
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)     AGE
tiller-deploy   ClusterIP   192.168.151.90   <none>        44134/TCP   106m
go-service      ClusterIP   192.168.140.38   <none>        80/TCP      95m

And the last interesting resource is Ingress:

$ k get ing -n sample-app                          
NAME           HOSTS                    ADDRESS   PORTS   AGE
go-service-0   my-cluster.example.com             80      96m
$ k get ing go-service-0 -n sample-app -o yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    garden.io/generated: "true"
    garden.io/last-applied-configuration: '{"not": "so important"}'
    garden.io/version: v-34c6adea2d
    ingress.kubernetes.io/force-ssl-redirect: "false"
  creationTimestamp: "2019-12-01T16:29:11Z"
  generation: 2
  labels:
    module: backend
    service: go-service
  name: go-service-0
  namespace: sample-app
  resourceVersion: "5362"
  selfLink: /apis/extensions/v1beta1/namespaces/sample-app/ingresses/go-service-0
  uid: b386a506-7869-41d9-a270-1038cbf3d4db
spec:
  rules:
  - host: my-cluster.example.com
    http:
      paths:
      - backend:
          serviceName: go-service
          servicePort: 80
        path: /api
status:
  loadBalancer: {}

So what did we find? Garden translates simple garden.yml into proper Kubernetes resources and changes them as you need.

Development mode

Now we are getting to the most interesting part, how can we leverage Garden.io in the development process? Developers typically don’t want to run a set of commands after each change. They prefer automatic reload of the application so they can focus on important things.

I have good news! You can achieve such functionality with garden dev. This function is watching your codebase and if you do some changes, it automatically rebuilds the image and updates the Kubernetes resources.

garden dev

Now we can just update the application code and see what happens next.

garden dev

Brilliant, it’s updated to the newer version of our app, it’s using a containerized environment and it took approximately 30 seconds only!

Going further

With Garden, you can run even tests directly in the Kubernetes cluster! And it’s actually pretty easy, you just need to extend module like this:

kind: Module
name: backend
description: Go service container
type: container
image: backend
tests:
  - name: unit
    args: ["echo", "works"]
services:
  - name: go-service
    ports:
      - name: http
        containerPort: 8080
        servicePort: 80
    ingresses:
      - path: /api
        port: http

As a result, Garden will run specified tests every time you change the code.

unit test

And we can even view test results from the web console:

web console

Wrap

Don’t be fooled. This is not the complete list of Garden.io features. I was playing with this amazing tool for couple of hours and still, I was not able to go through all the features. Anyways I can definitely tell you that this is the right direction if you want to streamline the development process a bit.

Check the official documentation for the complete list of features. It’s really huge. I’m really excited after some time spent with this tool. Give it a shot. You’ll be amazed.

comments powered by Disqus