Avoiding The Needless Multiplication Of Forms
Automated Testing In Docker
Dr Andrew Moss2015-09-04
Part 3: Building containers
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:
- Run a custom linux distro inside VirtualBox as a headless machine.
- Run the docker daemon inside the virtual machine.
- Provides a mac client that connects seamlessly to the daemon over an internal (simulated) ethernet bridge.
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!
Comments
Sign in at the top of the page to leave a comment