In this blog I will describe a reusable approach to testing Docker that I have been working on.
By ‘testing Docker’ I mean the performing the following actions:
- Static code analysis of the Dockerfile i.e. is the file syntactically valid and written to our expected standards?
- Unit testing the Docker Image created by performing a build with our Dockerfile i.e. does our Dockerfile look like it created the Image we were expecting?
- Functional testing the Container created by running an instance of our container i.e. when running does it look and do as we expected?
I wanted a solution that was very easy to adopt and extend so I chose to:
- implement it in Docker so that it will work for anyone using Docker (see this diagram)
- use Docker Compose to make it as easy to trigger
- reuse Dockerlint
- use Ruby because it is fairly widespread as a required skill in infrastructure-as-code people (for now until Go takes over…), and because the docker-api Gem is very powerful, albeit expects you to learn more about the Docker API in order to use it.
- use RSpec an ServerSpec as testing framework because they have good documentation and the support BDD
So what is the solution?
Essentially it is a Docker image called test-docker. To use it, you must mount-in your ‘Dockerfile’ and your ‘tests’ directory, it then:
- Runs Dockerlint to perform the static code analysis on the Docker file
- Runs your tests which I encourage you write for both inspecting the image and testing a running container.
How to see it in action?
To run this you need Docker installed and functioning happily. Personally I’m using:
- a Windows laptop
- Docker Toolbox which gave me: docker-machine which manages a Linux virtual machine for me running on a local installation of Virtual Box
- docker-compose installed (I did it manually)
- git bash aka Git For Windows as my terminal
With the above or equivalent, you simply need to do:
$ git clone https://github.com/kramos/test-docker.git $ cd test-docker $ docker-compose -f docker-compose-test-docker.yml up
You should see an output like this:
Creating testdocker Creating testdocker_lintdocker_1 Attaching to testdocker, testdocker_lintdocker_1 testdocker | /usr/local/bin/ruby -I/usr/local/bundle/gems/rspec-support-3.4.0/lib:/usr/local/bundle/gems/rspec-core-3.4.0/lib /usr/local/bundle/gems/rspec-core-3.4.0/exe/rspec --pattern spec/\*_spec.rb lintdocker_1 | Check passed! testdocker_lintdocker_1 exited with code 0 testdocker | testdocker | Container testdocker | get running testdocker | check ruby testdocker | Command "ruby --version" testdocker | stdout testdocker | should match /ruby/ testdocker | stderr testdocker | should be empty testdocker | testdocker | Image testdocker | inpsect metadata testdocker | should not expose any ports testdocker | testdocker | Finished in 1.48 seconds (files took 1.45 seconds to load) testdocker | 3 examples, 0 failures testdocker | testdocker exited with code 0 Gracefully stopping... (press Ctrl+C again to force)
All good. But what happened? Well everything I’ve said we wanted to happen, against the test-docker tool. #Dogfood and all that.
You can also try out another example e.g.:
$ docker-compose -f examples/redis/docker-compose.yml up
So how to use this for your own work?
Hopefully you’ll agree this is very easy (at least to get started):
- Replace the Dockerfile in the root of the test-docker folder with your own Dockerfile (plus any other local resources your Dockerfile needs)
- Run the following (this time we allow docker-compose to use the default configuration file which you also pulled from Git:
$ docker-compose up
- You will find out what Dockerlint thinks of your code, followed by finding out whether by extreme luck any of the tests that were written for the test-docker image (as opposed to your image) pass
- Open the rb file (in tests/spec) and update it to test your application using anything that you can do to a stopped container using the docker-api
- Open the rb file (in tests/spec) and update it to test your application using anything that you can do to a running container using the docker-api and Serverspec.
- I suggest remove the .git folder and initialise a git repository to manage your Dockerfile and your tests.
Functional tests that run the container require two subtly different approaches according to whether your Docker image is expected to run as a daemon or just run, do something and stop. In the former case, you can use a lot of Serverspec functionality. In the latter, your choices are more limited to running the container multiple times and in each case grabbing the output and parsing it.
There were a surprising number of things I had to learn on the fly here to get this working, but I don’t want this blog to drag on. Let me know how you get on and I will happily share more, especially when any of magic things don’t work as expected – for example when writing tests.
I’ll leave you with my current list of things I want to improve:
- Make work with Ruby slim (the image is huge)
- Get working with Inspec instead of ServerSpc
- Provide better examples of tests
- I should really draw a diagram to help anyone new to this understand all this inception computing…