In one of our projects, GEYSER, we were looking into a way of packaging customized code for a pilot test. Generally, we developed µservices which communicate with key Openstack components and we have specifically modified Openstack Horizon code by adding a new dashboard. These µservices as well as Openstack Horizon are quite decoupled from the core Openstack system meaning that communication is done mostly through external API calls rather than a message bus. A number of packaging options were considered: basic packaging python code is relatively straightforward but does not offer the flexibility we require, specifically around rollback. Other solutions include virtual environments or virtual machines, but ultimately, we decided to use Docker containers they are all the rage these days. This blog post describes step-by-step how to containerize Horizon in docker, noting any particular issues we observed in the process.

Putting Openstack Horizon in a container is pretty straightforward; the Openstack official documentation lists package dependencies that needs to be installed within the container. Considering that the Horizon API calls to keystone default to localhost, it is necessary to change the ‘OPENSTACK_HOST’ parameter in /etc/openstack-dashboard/ to your Openstack controller host; other configuration parameters may be changed according to your needs.

However, this is insufficient as the Docker model is one in which the container terminates if its primary process ends. A common solution to this is to use supervisord for process supervision: it handles the lifecycle of other processes running within a container and is very useful when it is necessary to run more than one long-term process within a single container. Although it is compliant with Docker best practices supervisord is not bulletproof – if for some reason the process under supervisord control fails, supervisord may not notice this and ultimately there may be no reaction to an important service failure. For our purposes, in a test and pilot environment, we consider apache to be stable enough such that the likelihood of failure under supervisord should be very small; for production systems, however this may need to be considered further. (More details on this topic are provided in one of our previous blog posts).

The Dockerfile can be used to create a new image which contains an Openstack Horizon dashboard. Using this, it is possible to have multiple simultaneous dashboards either for test, for upgrade or perhaps to deliver different functionalities/experience to users. Note that a sample of as well as supervisord.conf with your custom configuration must be in the same directory as the Dockerfile. An example supervisord.conf file is provided here.


FROM ubuntu:14.04 RUN apt-get update &&\ apt-get install ubuntu-cloud-keyring &&\ echo "deb"\ "trusty-updates/kilo main" > /etc/apt/sources.list.d/cloudarchive-kilo.list &&\ apt-get update && apt-get install python-pip python-dev supervisor &&\ openstack-dashboard apache2 libapache2-mod-wsgi -y &&\ apt-get remove openstack-dashboard-ubuntu-theme -y ADD /etc/openstack-dashboard/ ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf EXPOSE 80 ENTRYPOINT /usr/bin/supervisord


[supervisord] nodaemon=true [program:apache2] command=/bin/bash -c "source /etc/apache2/envvars && exec /usr/sbin/apache2 -DFOREGROUND"

Different base OS image can be used instead of ubuntu:14.04 (the package management commands may need to be adapted if an alternative distro is used). The RUN command comprises of multiple bash commands according to Dockerfile best practices to reduce the amount of layers in the build image. EXPOSE allows the container to listen to a port and if mapped accordingly to enable the services within the container to be accessible via the host machine and neighbouring containers. For more info on Dockerfile formats, check the official documentation.

The image can be created using “docker build -t custom/horizon -f Dockerfile .” When running the container, it is necessary to map port 80 in the container – where Horizon listens by default – to a port on the host machine: this is done using the -p or -P flags when starting a new docker container, e.g “docker run –name {NAME} -Pd custom/horizon” or “docker run –name {NAME} -d -p CUSTOM_PORT:80 custom/horizon”. The former lets Docker decides in which port the container is accessible in the host machine while in the latter you can specify a custom port mapped to the container. So, you just need to point your browser at the necessary port and you’ll have a dockerized Horizon dashboard.

We also wanted to add extra security to limit which hosts the container can access; we wanted to restrict access from the container to the specific host network interfaces (the controllers) and the external network by defining a range of IPs the container could access. This was non-trivial in Docker: the Docker daemon by default changes the iptables configuration, allowing a container to access to all network interfaces in the host machine and that was not ideal; fortunately Docker has a way of constraining access from a container by disabling changes to iptables when docker daemon starts using the –iptables=false. Specific restrictions for a given container can be realized through manual iptables modifications. This is less than ideal and quite inflexible, but it is one solution which satisfies our needs.
After all this, we have a container which contains our Horizon dashboard; we can easily launch multiple instances of this, it is robust enough for our needs and there is reasonable security around it.