Posted by Steve on Wed 1 Jan 2014 at 19:34
This article is a brief introduction to docker, which is a utility for manipulating lightweight containers, allowing you to quickly spin up multiple isolated environments on a single host-machine.
To get started you need to ensure that you have a "modern kernel". Happily the kernel which is available in the Wheezy release of Debian GNU/Linux is recent enough to contain all the required features.
Because docker uses the LXC container system you need to install that first:
apt-get install lxc
Once you've got lxc installed you should then download the docker command-line tool, which is what you'll use to interact with the container system:
root@shelob:~# wget https://get.docker.io/builds/Linux/x86_64/docker-latest -O /usr/local/bin/docker root@shelob:~# chmod 755 /usr/local/bin/docker
Now that you have the docker tool installed you can start using it. What we're going to do, in brief is:
Because docker works with virtual images we need to find some, or create one. Finding them via the online registry is very simple, and will be a good starting point.
Before any other steps may be completed you need to be running the docker daemon, so in one terminal please run:
root@shelob:~# docker -d 2014/01/01 18:55:32 WARNING: You are running linux kernel version 3.2.0-4-amd64, which might be unstable running docker. Please upgrade your kernel to 3.8.0. [/var/lib/docker|fb570453] +job initapi() [/var/lib/docker|fb570453.initapi()] Creating server ...
Once the daemon is running we can pull down some Ubuntu images via the internet in another terminal:
root@shelob:~# docker pull ubuntu
NOTE: This will pull down approximately 80Mb of data, so it might take a while to complete.
Once you've done that you should find that the images are visible in the output of the "docker images" command:
root@shelob:~# docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE ubuntu 12.04 8dbd9e392a96 8 months ago 128 MB ubuntu latest 8dbd9e392a96 8 months ago 128 MB ubuntu precise 8dbd9e392a96 8 months ago 128 MB
Now we have some images we can launch one, interactively:
root@shelob:~# docker run -i -t ubuntu /bin/bash root@6959e0664889:/# id uid=0(root) gid=0(root) groups=0(root) root@6959e0664889:/# df df: cannot read table of mounted file systems: No such file or directory root@6959e0664889:/# uptime 18:57:10 up 2 days, 11 min, 0 users, load average: 0.23, 0.15, 0.26 root@6959e0664889:/# exit root@shelob:~#
Here we've launched a new guest instance, with the name "ubuntu", the two flags we used were :
Because we were running interactively, and then exited, any changes we made to the system would be lost. We can verify that by running the same thing twice - once making changes, and once to look for them:
root@shelob:~# root@shelob:~# # First run root@shelob:~# root@shelob:~# docker run -i -t ubuntu /bin/bash root@1b896de6091c:/# rm /etc/passwd root@1b896de6091c:/# exit root@shelob:~# root@shelob:~# # Second Run root@shelob:~# root@shelob:~# docker run -i -t ubuntu /bin/bash root@6c7b72d5dc21:/# ls /etc/passwd /etc/passwd root@6c7b72d5dc21:/# exit
You'll notice that in both case the prompt changed, this prompt is the ID of the temporary running system.
You can use this ID to create a snapshot of your system, after making any changes which you wish to preserve. For example you might find that the system is missing your favourite editor so you could install it via "apt-get install vim". By default if you exited after running that command the change would be lost.
So let us update our image to contain some additional utiltities:
root@shelob:~# docker run -i -t ubuntu /bin/bash root@96a3ca7208f1:/# apt-get install screen less Reading package lists... Done .. Unpacking less (from .../less_444-1ubuntu1_amd64.deb) ... Selecting previously unselected package screen. Unpacking screen (from .../screen_4.0.3-14ubuntu8_amd64.deb) ... Setting up less (444-1ubuntu1) ... root@96a3ca7208f1:/# apt-get install screen less
At this point we have a running instance which we've installed two extra packages to. We want to keep this as our base for future work, so we'll give this instance a name:
root@shelob:~# docker commit 96a3ca7208f1 myname/base 00a50d7bcac3e905fb5a0c159e13a4a05cdca41378fd5064f0b792716b5e43f5
NOTE: Providing you can still remember the numeric ID you can carry this operation out even when you've exited the session.
At this point we've created a snapshot of our Ubuntu system which contains the extra screen & less packages. This snapshot will be visible in the output of "docker images":
root@shelob:~# docker images | grep myname myname/base latest 00a50d7bcac3 47 seconds ago 141.2 MB
As you'd expect you can now launch that instance by name:
root@shelob:~# docker run -i -t myname/base /bin/bash root@8294c9a0ec40:/# which less /usr/bin/less root@8294c9a0ec40:/# exit
Because the new docker instances are running as containers, via lxc, none of your home directories, or other mount-points, will be available to it. You can fix this via a bind-mount:
root@shelob:~# docker run -v /home:/home -i -t myname/base /bin/bash root@49277c40b630:/# ls /home/ litecoin lost+found skx root@49277c40b630:/# exit
The "-v /home:/home" flag we passed to docker specifies that the /home directory on your host should be mounted at /home within the guest.
At this point we're almost done, having demonstrated how to create named snapshots, mount external directories, and generally have fun with docker.
The remaining example demonstrates how you can use port-forwarding to run services inside a docker-container. In our case we'll demonstrate running memcached instead a container.
As before we'll first launch a container, then configure it, and tag the container with a name. Then we'll enable port-forwarding so that the deamon inside the container can be accessed remotely.
To get started we'll launch another instance, and install the memcached server upon it:
root@shelob:~# docker run -i -t ubuntu /bin/bash root@1871e9964507:/# apt-get install memcached Reading package lists... .. root@1871e9964507:/# sed -i "/127.0.0.1/d" /etc/memcached.conf root@1871e9964507:/# exit
Now we've installed memcached, and removed the configuration-file item which causes it to listen upon 127.0.0.1. Because we've made changes to the system we now need to tag it, so we can launch it again in the future:
root@shelob:~# docker commit 1871e9964507 myname/cache 557f7d1a3a7385a9e031241ca03584a1b51e14ec1cf76e0219841ced087f36f9
Now that we've done that we have a named container with memcached installed upon it, but we need to launch it in order to use it - and to be useful we need to forward a port to the container so that we can actually access it.
Much like the bind-mount example we saw earlier we nominate an external port, and an internal port. In our case we want to forward port 3000 on the local system to port 11211 (which is the port that memcached listens upon) to the guest.
Before we proceed we should ensure that port-forwarding is generally available:
root@shelob:~# sysctl "net.ipv4.ip_forward=1" net.ipv4.ip_forward = 1
With that out of the way we launch docker again, with the port-forwarding option "-p":
root@shelob:~# docker run -d -p 3000:11211 myname/cache /usr/bin/memcached -u memcache be5d23555f200a54f63d9386604955448bb0ba5fb3e60cdb2e639eb72b666259
Now we've launched it we can contact localhost:3000 which will then be forwarded to port 11211 within the container:
root@shelob:~# echo -e "stats\nquit" | nc localhost 3000 | head -n 2 STAT pid 1 STAT uptime 32
This is the first time we've launched a container in the background, so if you do wish to stop it you should run the "docker stop" command, with the ID you were given when you launched it:
root@shelob:~# docker stop be5d23555f200a54f63d9386604955448bb0ba5fb3e60cdb2e639eb72b666259
If you forgot the ID which was printed when the guest was started you can look it up via "docker ps":
root@shelob:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 41a5f4a33720 myname/cache:latest /usr/bin/memcached - 3 seconds ago Up 2 seconds 0.0.0.0:3000->11211/tcp lonely_bardeen
And using that short ID you can stop it:
root@shelob:~# docker stop 41a5f4a3372
Hopefully that introduction was useful, I suspect the only part that needs explanation was the way that we launched memcached - because the container boots under the host system and doesn't run any init-system we had to manually launch the deamon ourselves.
To test your kernel you can run the lxc-checkconfig tool, which was installed when you ran "apt-get install lxc".root@shelob:~# lxc-checkconfig Kernel config /proc/config.gz not found, looking in other places... Found kernel config file /boot/config-3.2.0-4-amd64 --- Namespaces --- Namespaces: enabled Utsname namespace: enabled Ipc namespace: enabled Pid namespace: enabled User namespace: enabled Network namespace: enabled Multiple /dev/pts instances: enabled --- Control groups --- Cgroup: enabled Cgroup clone_children flag: enabled Cgroup device: enabled Cgroup sched: enabled Cgroup cpu account: enabled Cgroup memory controller: enabled Cgroup cpuset: enabled --- Misc --- Veth pair device: enabled Macvlan: enabled Vlan: enabled File capabilities: enabled Note : Before booting a new kernel, you can check its configuration usage : CONFIG=/path/to/config /usr/bin/lxc-checkconfig
If there are any errors these should be displayed as guidance, otherwise if all items show as enabled you're good to proceed.
If you don't trust the images which are available over the internet it is relatively straight-forward to create your own.
The docker tool has two commands for dealing with image and tar files:
- "docker import .." - To import a tar-file to an image.
- "docker export .." - To export an image to a tar-file.
For example here is the process of exporting a guest:root@shelob:~# docker export caafbe5d8f34 > mem.tar root@shelob:~# tar tf mem.tar | head -n 5 ./ ./etc/ ./etc/hosts ./etc/hostname ./etc/resolv.conf
Importing a guest from a debootstrap-created system is almost as easy as you would expect. First of all we create the new system to the temporary location wheezy/:root@shelob:~# debootstrap wheezy wheezy/ I: Retrieving Release I: Retrieving Release.gpg ... I: Configuring tasksel... I: Configuring tasksel-data... I: Base system installed successfully.
Once we have that local chroot we can build a tar-file from it, and then feed it to the import process, giving the new image the name debootstrap/wheezy:root@shelob:~# cd wheezy && tar cf ../wheezy.tar . root@shelob:~# cd ; docker import - debootstrap/wheezy < wheezy.tar
Once that is done you can see the image is present:root@shelob:~# docker images | grep debo debootstrap/wheezy latest fb59cf7301bb 6 seconds ago 218.5 MB
And this can now be used, as you would expect:root@shelob:~# docker run -i -t debootstrap/wheezy /bin/bash
One thing not explicitly mentioned is that you might receive an error about a missing cgroup mount when you try to launch a container. This error will look like this:root@shelob:~# docker run -i -t debootstrap/wheezy /bin/sh lxc-start: No cgroup mounted on the system .. [error] commands.go:2445 Error getting size: bad file descriptor
To solve this you need to ensure you have a cgroup filesystem mounted. You can do this by running:# echo "cgroup /sys/fs/cgroup cgroup defaults 0 0" >> /etc/fstab # mount /sys/fs/cgroup
This wasn't mentioned in the article originally because I already had this mount present upon my host.