Avoiding The Needless Multiplication Of Forms

Automated Testing In Docker

Dr Andrew Moss

2015-09-04

Today the plan is to use the moon_base1 container as a staging point to build the testing environment, then clone it into a specialised container with the code to test. This is surprisingly easy, and suggests that Docker has been designed very well. But first a quick aside about the Mac. Docker sits on-top of the namespaces support in the Linux kernel, and provides instances of a linux installations packaged into a container. When it is running on a linux host this is straight-forward. But what about running a Docker container on a mac (or even windows)? Well, this is where things get really nice.
A short aside on running linux containers on OS-X Docker Toolbox packages together everything needed to:
This is actually breath-taking, in the "oh my god that is so cool I cannot actually breath" sense. One interface for a application container - that can be instantiated directly on a linux host using namespaces for performance, or (optionally) deployed inside a virtual machine for maximum isolation. There are known hypervisor (or were, I have not checked if they've been fixed), but an exploit to break the container and then the hypervisor layer is such a specific attack surface that I find I can sleep really quite easily at night. (again: CivEng security students who want a challenging project, feel free to come and discuss this).
There is only one glitch here from my perspective: for ease of use the standard toolbox sets up a writeable share into the /Users directory that can be mounted from inside the container. Obviously this is useful in most target applications for docker, but in this application we want to disable it. The toolbox names the virtual machine "default" ... err, by default.
$ VBoxManage list vms .... "default" {long hex id number} $ VBoxManage showvminfo default ... (snip) Shared folders: Name: 'Users', Host path: '/Users' (machine mapping), writable ... (more snip) $ docker-machine stop default $ VBoxManage sharedfolder remove default --name Users $ docker-machine start default Starting VM... Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command $ eval "$(docker-machine env default)" $ VBoxManage showvminfo default | grep Shared Shared folders: <none>
That produces a nice warm feeling of satisfaction. Now of course we will need to load the files that we need into the container in a different way...
Back to building the specialised testing containers If we remember that a Docker image is a union fs and a Docker container is a set of processes running over that file system then everything is nice and straightforward. We will jump into the moon_base1 container and directly install what we need as a dev environment. Then we can export the file system from this container into a new image that we can spawn individual containers from. Lovely jubbley.
$ docker restart moon_base1 $ docker attach moon_base1 root@b8e173f8481f:/# apt-get update root@b8e173f8481f:/# apt-get -y install gcc make flex bison root@b8e173f8481f:/# ^d $ docker commit moon_base1 ubuntu_localdev f899773591dbf38ddbd68aa73ffba3c26a151a84f385bb0892ab78677560d48d $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE ubuntu_localdev latest f899773591db 7 minutes ago 281.9 MB ubuntu latest 91e54dfb1179 2 weeks ago 188.4 MB $ docker run -it --name test1 ubuntu_localdev root@dd851d126938:/# which gcc /usr/bin/gcc root@dd851d126938:/# which make /usr/bin/make root@dd851d126938:/# which bison /usr/bin/bison root@dd851d126938:/# which flex /usr/bin/flex root@dd851d126938:/# exit
Cool, so the important thing to understand here is that if we modify the filesystem in the test1 container it will not affect the moon_base1 container. The read-write layer in the union filesystem of the moon_base1 container becomes a readonly layer in the test1 container filesystem. The commit (export) and then new start have the effect of copying the moon_base1 filesystem into the new container, not aliasing / sharing it. The idea is that we will run our tests in this clean container and then destroy it afterwards without affecting the moon_base1.
So far we've used mainly interactive tools to explore docker and poke around images and containers to get a feel for the thing. But now, it's on. Docker's main interface is a simple scripting language called Dockerfiles. This gives us a simple syntax to automate building containers and images. First we need a build context - this really wants to be an empty directory as it will be transmitted to the docker daemon for execution of the script.
$ mkdir build_context cd build_context cp ../student.tgz . vi Dockerfile
Obvious you don't have to vi if you feel it is too oldskool. Atom is lovely. Or, if you feel that vi is one of them there new-fangled devices then feel free to fire up ed, or just use a here-document to fill the file. We are editor-neutral in this place. But regardless, our first Dockerfile will contain:
FROM ubuntu_localdev ADD student.tgz /testroot/
The FROM directive tells Docker which image is to be used as the starting point for the build. The ADD command is a slightly-magic version of copy that does things like unpack tarballs into the target file-system. We can take the result for a quick spin:
$ docker build -t=current_test . Sending build context to Docker daemon 321.5 kB Step 0 : FROM ubuntu_localdev ---> f899773591db Step 1 : ADD student.tgz /testroot/ ---> 7efb17601604 Removing intermediate container c27c7f81935f Successfully built 7efb17601604 $ docker run -it --name inside_test current_test /bin/bash root@10007f8dee6f:/# ls / bin dev home lib64 mnt proc run srv testroot usr boot etc lib media opt root sbin sys tmp var root@10007f8dee6f:/# cd testroot root@10007f8dee6f:/testroot# ls ass1-int root@10007f8dee6f:/testroot# ls ass1-int/ Makefile case2 case4 int out.dot parser.tab.c parser.y case1 case3 case5 lex.yy.c out.png parser.tab.h scanner.flex root@10007f8dee6f:/testroot#
Which looks a lot like a student submission for the assignment being graded. Progress! Onwards!