Distributing Go Projects With Debian And Chef

A couple of months ago I started to do some programming in Go and since then I’ve learned a few things on how to distribute the finished code. I have no idea if this is similar to the Debian way of doing it but this is pretty simple, straightforward and gets the job done for me.

Where I work we mainly use Debian so we wrap all our projects into .deb at some point. Then we use Chef recipes to deploy these packages and projects in our infrastructure.

The Debian Package

First, let me show you what the structure of the finished .deb looks like.

.
├── debian
│   ├── changelog
│   ├── compat
│   ├── control
│   └── rules
├── etc
│   ├── balancer.toml
│   └── sv
│       └── balancer
│           └── run
├── balancer.go
├── Makefile
└── src
    └── github.com
        └── BurntSushi
            └── toml

Here we have a standard debian folder, etc to hold the projects config/startup scripts and src for package imports. Then there is a Makefile and the main balancer.go source file.

Inside debian/rules

We do not need any special debhelper commands so the default sequence of commands works for us, as it will use our provided Makefile when invoking dpkg-buildpackage later.

#!/usr/bin/make -f

#export DH_VERBOSE = 1

%:
	dh $@

The other files are standard Debian package files, so consider consulting the Debian Policy Manual if they look unfamiliar to you.

The Makefile

In here we have three main targets that will be used by dpkg-buildpackage. First out is the clean target invoked by dh_auto_clean, then build called from dh_auto_build and finally install which is from dh_auto_install.

all: build

build:
	export GOPATH=$(PWD); go build -o balancer balancer.go

install:
	install -D -o 0 -g 0 -m 0755 balancer $(DESTDIR)/usr/sbin/balancer
	install -D -o 0 -g 0 -m 0755 etc/sv/balancer/run $(DESTDIR)/etc/sv/balancer/run
	install -D -o 0 -g 0 -m 0644 etc/balancer.toml $(DESTDIR)/etc/balancer.toml

clean:
	rm -rf debian/tmp
	rm -rf debian/files
	rm -rf balancer
	rm -rf debian/balancer
	rm -rf debian/*.substvars
	rm -rf debian/*.debhelper.log

I export GOPATH with the working directory so that the src directory with my package imports can be found when building the Go binary.

Runit

One thing you might notice and may be wondering about could be the etc/sv/blancer/run line in the install target. We currently use runit to handle starting/stopping/reloading/respawning of our Go applications.

No daemon? No fork? Use a spoon.

Did you notice that I didn’t write daemon, but applications? Yep, at first I figured I would just use the Debian start-stop-daemon but that is really not optimal as I really want my Go programs to respawn directly if they crash. Also, implementing a daemon() call in Go has also proven to be an not so easy task nor recommended which you can read about here and here.

Anyways this is why we use runit and trust it to handle the Go applications we write. Here is a nice post about runit if you want to know more.

Subtree merge external dependencies

If you have any external dependencies for your project we subtree merge them into the src folder.

git remote add toml https://github.com/BurntSushi/toml

git merge -s ours --no-commit toml/master

git read-tree --prefix=src/github.com/BurntSushi/toml -u toml/master

git commit -m "Merge github.com/BurntSushi/toml into src"

Now you should be all set to proceed with dpkg-buildpackage and push your new package to your local mirror.

Chef

In Chef we create a new recipe that will install our new program.

package "balancer"  do
    action :install
end

To enable runit and let runsv monitor our new application we need a link in /etc/service/balancer to point to the /etc/sv/balancer directory we had in our Makefile earlier. We also make a node attribute so we can enable or disable the application from our Chef config.

if node['balancer']['enable'] && node['balancer']['enable']!=false
    link '/etc/service/balancer' do
        to '/etc/sv/balancer'
    end
else
    link '/etc/service/balancer' do
        action :delete
    end
end

To let our application pick up config changes from Chef we need a service resource to be able to manage it. Here I have bind SIGUSER2 to handle reloads and also defined the usual start/stop/restart commands.

service 'balancer' do
    supports [:start, :restart, :stop, :reload]
    service_name 'balancer'
    start_command 'sv start balancer'
    restart_command 'sv restart balancer'
    stop_command 'sv stop balancer'
    reload_command 'sv 2 balancer'
    action :nothing
end

We can then put it in to use from a template whenever that resource changes.

template "/etc/balancer.toml" do
    source "balancer.toml.erb"
    owner "root"
    group "root"
    mode 0644
    notifies :reload, "service[balancer]", :delayed
end

Happy days!

comments powered by Disqus