For one of our projects we found ourselves in need of a PXE server. In order to make proper use of a PXE server you will quickly find you need to change settings in your DHCP server. So we figured we'd probably needed one of those as well. We tried to roll our own but soon enough we came across a package that does all this combined. It’s called Cobbler.
Also being somewhat biased, we would prefer to run Cobbler in a container. That comes with some challenges in itself. If you search for Cobbler on the Docker Hub, there are 10 hits. 4 of them are not automated builds, which means you can’t really tell what’s in them. None of the others seem to be using systemd, which makes it kind of hard for Cobbler to manage the services, which in turn makes it rather impossible to change the config of a running Cobbler container.
So the obvious thing to do is to roll our own. Which we did.
Building the Docker image
Docker is primarily meant to run a single process, but Cobbler consists of mulitple services that you cannot easily separate. There’s cobblerd itself, httpd for the webfrontend, tftpd for serving the PXE images, dhcpd serving ip addresses, and possibly dns for resolving hostnames. Cobbler wants control over all of these services, i.e. after you make changes to the configuration it needs to restart the services so they can pick up the changes. This is why it makes sense to put all these things in a single container, although it makes for a bit of a bloated one. Restarting services requires an init system such as systemd, and luckily the official Centos image is able to run with systemd. Details on how to use it can be found on the CentOS Docker Hub page. So the beginning of the Dockerfile will look something like this:
FROM centos:7.2.1511 MAINTAINER firstname.lastname@example.org RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \ rm -f /lib/systemd/system/multi-user.target.wants/*;\ rm -f /etc/systemd/system/*.wants/*;\ rm -f /lib/systemd/system/local-fs.target.wants/*; \ rm -f /lib/systemd/system/sockets.target.wants/*udev*; \ rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \ rm -f /lib/systemd/system/basic.target.wants/*;\ rm -f /lib/systemd/system/anaconda.target.wants/*; VOLUME [ “/sys/fs/cgroup” ]
Next we install the EPEL repositories that contain the Cobbler packages we need. Once we add EPEL we can install Cobbler and the rest of the necessary packages.
RUN yum -y install epel-release RUN yum -y install cobbler cobbler-web dhcp bind syslinux pykickstart
We need to enable the services in systemd.
RUN systemctl enable cobblerd RUN systemctl enable httpd RUN systemctl enable dhcpd
Then we change the tftp xinetd config file to enable the tftp service.
# enable tftp RUN sed -i -e 's/\(^.*disable.*=\) yes/\1 no/' /etc/xinetd.d/tftp
And we make sure the rsync file exists, or else Cobbler will fail to start.
# create rsync file RUN touch /etc/xinetd.d/rsync
We expose the ports we plan to use:
EXPOSE 69 EXPOSE 80 EXPOSE 443 EXPOSE 25151
And we end by invoking init
Once we have declared the Dockerfile, we build it with
docker build -t cobbler . and we should be good to go. But not quite.
Running the Container
We can't just run it, we need to specify
--privileged because we're using systemd which needs elevated capabilities, like CAP_SET_FILE.
We also want to use the network stack of the host, so Cobbler will listen on and offer ip addresses on a subnet that the host is connected to. If it would use it's own network stack Cobbler would only be able to issue addresses in the private docker subnet, which would not really make any sense because every container already gets an address from the docker engine.
Another thing we want is to save the Cobbler configuration and the imported OS distributions in between restarts of the container. For this we need to mount a bunch of volumes. Lastly we also need to mount an iso inside the container to be able to import it's contents into Cobbler. So the resulting
docker run command is gonna look like this:
docker run \ -d \ --privileged \ --net host \ -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ -v etc/cobbler/settings:/etc/cobbler/settings \ -v etc/cobbler/dhcp.template:/etc/cobbler/dhcp.template \ -v var/www/cobbler/images:/var/www/cobbler/images \ -v var/www/cobbler/ks_mirror:/var/www/cobbler/ks_mirror \ -v var/www/cobbler/links:/var/www/cobbler/links \ -v var/lib/cobbler/config:/var/lib/cobbler/config \ -v var/lib/tftpboot:/var/lib/tftpboot \ -v dist/centos:/mnt:ro \ -p 69:69 \ -p 80:80 \ -p 443:443 \ -p 25151:25151 \ --name cobbler cobbler
Before we can run though, we need to configure some files. Get these files by starting the Cobbler container without the volumes mounted, and use
docker cp to copy over at least the
hcp_template files from
/etc/cobbler inside the container to the host. Put them in a directory
etc/cobbler under the current dir, which would be where the Dockerfile is.
Next, edit the
settings file, change the value for
1 and enter the ip address where your host will be listening on in both the
dhcp_template file, configure the subnet details. Be sure to use a subnet that your host can actually connect to, or Cobbler will fail to start later. Debugging this is kind of hard and cost me a lot of time. There are no logs written anywhere and you can only find out what's wrong by checking
systemctl status <servicename> or
journalctl -xe. Once you have the addresses sorted though you should be able to use the run command listed above.
Importing a distribution
You see we used a CentOS iso for importing into Cobbler, we mounted it on
dist/centos on our host and attached that as a volume to
/mnt inside the container. I found the
cobbler import command fails if you use it with any other path than
/mnt. The complete mount command is:
sudo mount -t iso9660 -o loop,ro -v dist/centos.iso dist/centos
and for importing the distribution:
docker exec -it cobbler cobbler import --name=centos7 --arch=x86_64 --path=/mnt
Checking the web interface
After you have started the container, you should be able to go to the web interface under https://localhost/cobbler_web.
cobbler as the username and
cobbler as the password. If the web interface doesn't respond, exec into the container and check the
systemctl status for
httpd and also
Seeing Cobbler in action
For my test setup i used a Virtualbox VM to connect to the Cobbler server. I created a host-only network and attached an empty client to that. I configured the ip address of the
vboxnet interface in the
Last, not unimportant step is to create a system in Cobbler that actually uses the distribution you imported. Configuration is straightforward, but be sure to match the MAC-address assigned to the VM by VirtualBox.
Once the vm boots, press F12 and select the LAN boot device. You should see the VM booting into the CentOS installer.
All the code from this post can be found on our Github project page. I also tried to get Docker Hub to do an automated build but due to an issue with Docker Hub (see here and here), installing httpd in a CentOS machine doesn't work on the Docker Hub. It appears to be related to using aufs as the storage backend. For now, just build it on your local machine.