A developer working in a microservice stack can quickly burn out a CPU trying to bring up a whole stack for local development. But getting code to the cluster means a pipeline that will slow down iteration, right? In this post I show how to use cloud native Buildpacks, Helm and Skaffold to hotload code (pushing changes as soon as they are saved) into the cluster and iterate quickly. A video demo is included.
When working with microservices, it can be difficult to iterate on a single service without having the context of the other upstream and downstream services that you need for integration, end-to-end testing and sanity checking.
Running all upstream and downstream services on your laptop, however, will quickly cause your machine to suffer. So, we’re shifting our local development to our Kubernetes clusters. This allows you to focus on coding and unit/contract testing on your machine, and leaves running apps to the cluster.
To get rapid iteration of local work into the cluster, we use three tools:
We’ll walk through a quick explanation of each and then show how they tie together.
Buildpacks take the place of Dockerfiles, if you are familiar with Docker. They build an immutable image of your application code and the underlying OS environment. You can then run the code locally, or in Kubernetes. Buildpacks do this in a standardized way. They remove the need to write a Dockerfile for every app, and make delivering security updates much easier; updates get integrated into the buildpack, rather than into every app’s Dockerfile.
Heroku and Pivotal provide sets of open source builders and buildpacks. We’re going to use Heroku’s here, which can be found in their public Github repo.
Examples are given below for Go, but Heroku’s stack supports JS with Yarn or npm, Ruby, Java, Scala and Python, and is extensible; you can write your own buildpacks for their stack if necessary.
To use buildpacks, you need to install the pack tool, using the official installation instructions.
Build an Image
To build an image for a Go app using Heroku’s buildpacks, there must be a main.go file at the top level of the directory you are building in. The command is
$ pack build <name of app> \ --builder heroku/buildpacks:18 \ --buildpack heroku/go \ --buildpack heroku/procfile \ --path . \ -e “GO_GIT_CRED__HTTPS__GITHUB__COM=<a github token>“
where “name of app” is the name of the docker repo and image name you want – like thedevelopnik/my-go-app:latest – and “a github token” is a service token of a Github user with read permissions for any private git repo the application depends on in its go.mod dependencies.
Pack can also be used to build production deployment images, so you can use it in Continuous Integration as well.
Helm is a templating tool for Kubernetes files. You build a group of files together into a “chart,” and can then use that chart for reliable, repeatable deployments. To use Helm, you need to install it locally using the official installation instructions.
To get started with Helm for an app you’re just developing, you can run
$ helm create my-go-app and Helm will generate a starter set of templates for you. We’ll use the path to that local chart for the rest of this tutorial.
To use your local helm chart to deploy an application, you would run a command like:
$ helm install ./my-go-app --namespace default --name my-go-app -f path/to/localdevvalues.yaml
Most charts take a values.yaml file with overrides for the options they provide. For local development, you’ll need a values file that probably contains secrets like passwords for dev databases, or tokens.
As good practice, every app that uses helm for local dev should have a “localdevvalues.yaml” that is listed in .gitignore. That way, your local dev secrets don’t end up in your code repository.
Where Helm truly shines in this workflow is a team environment. By using Pack and building common Helm charts (i.e. yourorg/go-web or yourorg/rails) rather than generating a new one for every application, you eliminate the need to write most of the resources for build and deployment for each application, allowing developers to stop writing yaml and get back to writing app code.
Skaffold ties Pack and Helm together to make local development against the cluster easy. Using the command
$ skaffold dev causes Skaffold to watch your code; and on changes, it will rebuild and re-deploy your app to the cluster.
Get started with Skaffold by installing:
Create a config file
$ skaffold init only supports docker and kubectl for auto-generation, so we need to make our own config file.
Now that you’ve seen the CLI commands for Pack and Helm, you should recognize the Skaffold config file as just mapping those commands to a yaml file that tells Skaffold how to use those tools to hotload your code into the cluster.
Get details about pack configuration here
Get details about helm configuration here
apiVersion: skaffold/v2beta4 kind: Config metadata: name: <appname> build: artifacts: - image: thedevelopnik/my-go-app buildpack: builder: "heroku/buildpacks:18" buildpacks: "heroku/go" env: - GO_GIT_CRED__HTTPS__GITHUB__COM=<a token> deploy: helm: releases: - name: my-go-app chartPath: ./my-go-app namespace: <mynamespace> valuesFiles: - ./localdevvalues.yaml
Then, just run
$ skaffold dev and you’re off to the races!
There are more powerful features in Skaffold – like container validation testing – to explore further.
Find a full version of the code, with a working example here.
See Dave Sudia’s bio, below, and his interview with DETW (Developers Eating the World)