I’ve been looking through the latest Technology Radar issue and here’s what I found in its new Techniques section: “TDD’ing containers”. Wow. Mentally, I’m not yet ready to connect TDD to containers, but I took a look at the tools used for that, and those are quite interesting.
The first one is serverspec, which allows running BDD-like tests against local or remote servers or containers. It looks pretty solid, supports multiple OSs and its only downside (for me) is that serverspec is written in Ruby and therefore doesn’t really fit in the stack I normally work with.
The other one – goss
– leaves an impression of a multitool, which usually worries me, but here… I’m kind of curious, so let’s have a look.
What is goss for
goss
is a tool that checks if current server satisfies certain configuration. It’s that simple. “Certain configuration” here can mean different things, starting from whether specified process is installed and running, necessary ports are open and user accounts are set, and ending with local or remote URLs responding with expected content.
Quick example
I’ve prepared a trivial Vagrantfile to quickly spin up a VM with goss
installed, so I can play with it in safe disposable environment:
1 2 3 4 5 6 7 8 9 |
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| config.vm.box = "ubuntu/xenial64" config.vm.provision "shell", inline: <<-SHELL curl -fsSL https://goss.rocks/install | sh SHELL end |
Creating a server specification file
Like many tools nowadays, goss
keeps its configurations (server or container specs) in YAML files. I could create one from scratch, but what’s cool I actually can do that semi-automatically by asking goss
to scan particular feature of current machine and add it to config file.
For instance, my current host has user ubuntu
, sshd
service and ports 80
and 443
being closed. I think that’s a perfect configuration and all of my other servers should follow it. To check if that’s currently the case, I can ask goss
to add these features’ states to config file, then bring it to all other servers (can’t test them remotely) and call goss validate
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
goss add service sshd #Adding Service to './goss.yaml': # #sshd: # enabled: true # running: true goss add user ubuntu # Adding User to './goss.yaml': # #ubuntu: # exists: true # groups: #... goss add port 80 443 # ... #tcp:80: # listening: false # ip: [] #... |
I had to clean up resulting file a little bit, as I don’t care what groups ubuntu
user belongs to, or which network interfaces’ ports we’re going to test, so resulting goss.yaml
started to look like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
port: tcp:80: listening: false tcp:443: listening: false service: sshd: enabled: true running: true user: ubuntu: exists: true |
Testing if server matches specification
That couldn’t have been any simpler:
1 2 3 4 5 |
goss validate #..... # #Total Duration: 0.008s #Count: 5, Failed: 0, Skipped: 0 |
Testing if docker container matches specification
goss
comes with evil-twin brother called dgoss
, which knows how to test Docker containers. It’s easy to remember how to use it:
docker run ubuntu sleep 1
will launch Docker container using latest ubuntu image, which will sleep for 1 second before shutting down, whereasdgoss run ubuntu sleep 1
will test such container.
In my case dgoss
shows that native ubuntu image fails the test:
1 2 3 4 |
sudo dgoss run ubuntu sleep 1 # ... # Count: 5, Failed: 3, Skipped: 0 # ... |
Because both dgoss
and goss
‘s exit codes reflect whether the test was successful or not, it’s quite easy to connect them to CI and validate, for instance, if Dockerfiles still produce what they supposed to.
Interesting side-effects
There’s nothing particularly fancy in testing server configurations, so here’re few very interesting side-effects instead.
Firstly, goss validate
command can wait:
1 |
goss validate --sleep 5s --retry-timeout 1m |
This command will try to validate server every 5 seconds until it succeeds or 1m timeout. What it means for us is we could postpone, for example, Docker’s entry point commands until container dependences become ready:
1 2 3 |
FROM... # ... CMD goss validate --sleep 5s --retry-timeout 1m && start-my-service.sh |
Secondly, we could use goss
for Docker health checks or Kubernetes liveness or readiness probes. I used to test with curl
if container’s web service is still responding:
1 2 3 4 5 6 |
#... healthcheck: test: curl -sS http://127.0.0.1 || exit 1 interval: 5s timeout: 10s #... |
Instead, I could add more validation steps and use goss
:
1 2 3 4 5 6 |
#... healthcheck: test: goss -g /goss.yaml validate interval: 5s timeout: 10s #... |
In fact, it doesn’t have to be executing goss validate
all the time. goss serve
can start a small web service with health check endpoint – http://127.0.0.1:8080/healthz. This URL would respond with test results and 200 OK
http code when everything’s fine, and 503 Service Unavailable
when it’s not. What’s cool both Docker and Kubernetes know how to interpret these status codes, so they’d be able to work with this endpoint right away.
1 2 3 4 5 |
curl -v localhost:8080/healthz #> GET /healthz HTTP/1.1 # ... #< HTTP/1.1 200 OK # ... |
Conclusion
Even though goss
seems like a nice tool for validating servers and containers configuration, I’m more enthusiastic about using it as a Kubernetes or Docker health check provider. Seriously, I can see so many things to put into goss.yaml
file, so e.g. Kubernetes knows when it’s OK to route incoming traffic to the pod, or it’s time to restart it. Of cause, all of that could be achieved by relatively simple bash script. But that’s one more kind of wheel we don’t have to reinvent anymore.