In a previous blog entry I described some of the controls that are needed if you want to use a container as a VM. Essentially, if you want to use it as a VM then you must treat it as a VM.
This means that all your containers should have the same baseline as your VM OS, the same configuration, the same security policies.
Fortunately we can take a VM and convert it into a container. Mostly.
In my lab setup (“lab” be a grandious word; it’s a single machine running
too many KVM virtual machines :-)) I’ve created a process for building
a new VM via kickstart. I can run
virt_build test2 and it will
create the LVM volumes, start an install of CentOS 7, patch it, apply
my local configs (create my account, install SSH keys, etc etc). A few
minutes later I can then
ssh test2 and, just like that, I have a new VM.
Many enterprises will have a similar build process; whether it uses PXE to do the initial boot and [Cobbler](https://en.wikipedia.org/wiki/Cobbler_(software%29) to dynamically build the kickstart file, or does it the simplistic way I do doesn’t really matter. The result is an automated consistent build.
Side note: Some artifacts of the automated build process might need to be split into a “build” and “first boot” section; for example a server may register itself to Active Directory for authentication, or to Tivoli for monitoring… these are now “first boot” type activities and can’t be done at build time. But let’s ignore this complexity :-)
The important parts of my kickstart file are:
network --onboot yes --device eth0 --bootproto dhcp --ipv6 auto rootpw --iscrypted yeahyeahyeah authconfig --enableshadow --passalgo=sha512 firewall --disabled selinux --disabled zerombr clearpart --all --initlabel part /boot --fstype=ext4 --asprimary --size=500 part swap --asprimary --size=512 part / --fstype=ext4 --asprimary --grow --size=1
%post section (which creates my account)
and so on. This should be perfectly familiar to anyone used to kickstart.
Now the result of my build process is an LVM disk image consisting of three partitions
This, in my case, is
So now we need to take this image and convert it to a docker container.
So we need to make the image accessible to the host OS. Then we can mount it. Then send it to the docker server, where it can be converted to an image.
$ sudo kpartx -av /dev/Raid10/vm.test2 add map Raid10-vm.test2p1 (253:21): 0 1024000 linear /dev/Raid10/vm.test2 2048 add map Raid10-vm.test2p2 (253:22): 0 1048576 linear /dev/Raid10/vm.test2 1026048 add map Raid10-vm.test2p3 (253:23): 0 6313984 linear /dev/Raid10/vm.test2 2074624 $ sudo mkdir /tmpmount $ sudo mount -r /dev/mapper/Raid10-vm.test2p3 /tmpmount $ cd /tmpmount $ sudo tar cf - . | ssh dockerserver docker import - c7test2 $ cd $ sudo umount /tmpmount/ $ sudo kpartx -d /dev/Raid10/vm.test2
Now this may through up a couple of errors about sockets (in my case it
postfix sockets), but we can ignore them.
tar: ./var/lib/gssproxy/default.sock: socket ignored tar: ./var/spool/postfix/private/virtual: socket ignored tar: ./var/spool/postfix/private/bounce: socket ignored .... tar: ./tmp/ssh-IB3AMGEQUc/agent.1873: socket ignored sha256:374dc7777cfc8c6c2676021dfdd535aac1047a4794a1333814ddcf4936d32b33
If we now look on the docker server:
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE c7test2 latest 374dc7777cfc 3 minutes ago 1.185 GB
Notice how large this is, compared to the official docker centos image
docker.io/centos latest 970633036444 3 weeks ago 196.7 MB
But we now have an OS container that we can run!
$ docker run --name container1 c7test2 /sbin/init & $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0e640e27a91b c7test2 "/sbin/init" 26 seconds ago Up 22 seconds container1
And we can see the container is running a full OS tree
$ docker top container1 | head -4 UID PID PPID C STIME TTY TIME CMD root 30732 9504 0 19:49 ? 00:00:00 /sbin/init root 30755 30732 0 19:49 ? 00:00:00 /usr/lib/systemd/systemd-journald root 30793 30732 0 19:49 ? 00:00:00 /usr/sbin/rsyslogd -n
I haven’t done anything clever with the networking here, but
$ docker inspect container1 | grep IPAddress
tells me this container is 172.17.0.3
So we can now
ssh into it!
$ ssh 172.17.0.3 The authenticity of host '172.17.0.3 (172.17.0.3)' can't be established. ECDSA key fingerprint is 32:31:44:a0:9b:61:1b:ff:2f:db:76:ae:a9:a5:36:2b. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '172.17.0.3' (ECDSA) to the list of known hosts. 0e640e27a91b$
My OS image is now a (mostly) working container with a funky hostname :-)
What doesn’t work? Well, I don’t seem to have a
running on the console. And commands like
dmesg don’t work.
And I haven’t joined it to my main network (using the default
network). But for all intents and purposes, this is a working OS container.
(For clean-ness we should create a derived container based on this
image that automtically runs
init and lets the
return, but that’s a refinement).
Now I’ve walked through how I could convert my build process to creating a docker OS container. Naturally you’ll need to modify your own processes to work in your environment, and the more complex the environment the more tweaks necessary. But this is the concept you can follow; take a known good build from your standard build process and convert it to a container.
I’m not really a fan of the “container as a VM” model. I can understand that some people might want to do it and, as previously written, I think that the lines between a container and a VM will blur (at which time the build process will also become easier).
But if you’re going to do it, building a process that maintains consistency between your VM and container builds is a massive step forwards towards controlling the estate and, with automation, can simplify your compliance stance; you only have the one build process to worry about :-)