Docker Container Support in CDT
There is a lot of buzz these days around Docker Containers. Docker Containers are akin to lightweight Virtual Machines whereby one can run an application in isolation from the host OS. They differ from traditional VMs in that they share the machine's system kernel and use filesystem layering to share common files. Since they share existing resources, startup is quick and they use less storage.
Docker Containers are created using Docker Images which are templates that define the initial filesystem contents of the Container including the OS, installed packages, libraries, configuration files, and general user files. The Docker Container is an instance of an image running a particular command or application. Running the same command or application in the same image should produce similar results (timing issues notwithstanding).
Images are prepared in advance and are usually based on other existing images. Modifications made to the base image result in layers being stored and when the image is used to create a container, the layers are assembled using the layered filesystem. This can be done extremely quickly and minimizes the storage required by a new image to just the filesystem delta. Base images are supplied for most of the various Linux variants and releases to build more complex images. For example, your application might require gdbserver installed so you can create an image that takes a base OS and adds the gdbserver package. How to do this will be demonstrated later in this article. That image can, in turn, be used to form other images that need more than just gdbserver installed.
Using Docker requires a Docker daemon be up and running either locally or remotely. An end-user specifies the location either as a locally running Unix socket (Linux) or as a TCP address. Actions are performed using the "docker" command which performs a number of various tasks via the daemon (e.g. start a container, pull an image, build an image).
Images are stored in a registry. Docker provides a default registry that you can use to access existing images or to store images you have created for others to share. To access an existing image you need to first pull it from a registry. Any images you create based on this image are only known to your daemon and are persistent, but if you wish to make such images accessible to others, you will need to push to the registry. The Docker default registry is free to use after you have created an id. Images placed there are by default public, but you can designate an image as private. While the Docker registry is default, a local private registry can be created to be used by the daemon (for example, to provide pre-release versions of software that are confidential).
Docker Tooling in Eclipse IDE
With the Eclipse Mars release, the Linux Tools project contributed the Docker Tooling plug-ins. This set of plug-ins provides access to docker command tasks with an Eclipse UI, making it easier to use. The Docker Tooling perspective includes an Explorer View which shows a tree view of Docker daemon connections, an Images View which shows a table of images that have been pulled by a connection, and a Containers View which shows containers that have been created by the active connection.
Launching C/C++ Executables
In the CDT, an optional feature was added to allow launching and debugging of C/C++ projects in a Docker Container. At present, building is not yet supported but bug:513589 has been opened and includes prototype changes to the master branch. This means that the executable must be compatible for the target OS of the Container (i.e. Elf format and dynamic library functionality compatible with Container's level of base OS).
To install the feature, click on Help → Install New Software... and choose the Neon release repository (http://download.eclipse.org/releases/neon). Under the Programming Languages category, choose the C/C++ Docker Container Launch Support feature.
Launching uses the Linux Tools Docker Tooling plug-ins and so they will also be installed if not already present in your Eclipse IDE.
The launch uses volume mounting to make the C/C++ executable accessible to the Container. Otherwise, one would have to make a Docker Image with the C/C++ executable in the filesystem. This would take a lot more time and require a new Docker Image whenever the executable gets built. Mounting on the other hand allows the same image to be used with each container run getting the updated executable. It should be noted that when running the Docker daemon locally, we are actually mounting the data, but when the daemon runs remotely, this is not possible so empty volumes are created and data is copied from the host to the volumes. The result is the same and this is done automatically by the plug-ins.
Running a C/C++ executable can be done two ways:
- Right-click on the binary and select "Run as → C/C++ Container Application".
- From the Run Configurations dialog, create a new C/C++ Container Application launch configuration.
Option 1 will default the Docker Connection to the first known connection and will either use the default image set in Window → Preferences → C/C++ → Docker Container Launch or else use an arbitrary image which is first in the list of images. After starting, a launch configuration is created which you can then modify via the Run Configurations dialog.
Option 2 allows you to specify all settings before performing the run via the launch configuration Container tab.
From the tab, you can add additional host directories that would be needed to run the executable. The directory containing the executable is already mounted by default. Other options include: keeping the container after launch (by default it is removed), allowing stdin support (if console input is required), and running the executable in privileged mode (needed for certain operations such as using strace).
Each launch creates a new Docker Container. If you choose to keep the container after launch (useful if you want to simply run it again or view the console output at a later date), you can find all these containers in the Containers View using a label filter. By default, all C/C++ launches will add a label to the Docker Container configuration that can be filtered in the Containers View. To filter, use the menu pull-down and select Configure Labels Filter. From this dialog, add a CDTLaunch entry (no value is required). You can optionally add a CDTProject label if you want to only look for launches for a particular project.
Launching an executable will result in a console being created. Using Window → Preferences → Docker you can specify if a timestamp should be prefixed to all output or not. A timestamp is useful if you want to run multiple times and want to compare results.
Debugging a C/C++ Executable in a Container
Debugging an executable is similar to running one except that the selected image must have gdbserver pre-installed. Base images do not typically have gdbserver installed so the following method can be used to create such an image:
- From the Docker Images View select a desired base image (e.g. fedora:21).
- Right-click on the selected image and choose "Run".
- In the Run Image Wizard enter the /bin/sh command to run - be sure to select using a pseudo-TTY and stdin support from the additional options then hit "Finish".
- In the Terminal View, manually install the appropriate gdbserver package for the OS.
- Find the container you just created in the Docker Containers View.
- Select the container, right-click and select "Commit".
- Save the Container with gdbserver installed as whatever image name you like.
Now you have an image that is able to run gdbserver to debug the executable. To debug a C/C++ executable in a container you should create a C/C++ Container Application launch configuration from the Debug Configurations dialog as opposed to using Debug as → C/C++ Container Application. This is needed the first time because you will need to specify the privileged option. As of Docker 1.10 or higher, gdbserver needs to run privileged to avoid an error due to security. This problem will be fixed in Eclipse Oxygen whereby the gdbserver launch will internally set security options to bypass requiring user intervention.
Debugging works the same as debugging a remote executable, only the remote setup is done for you automatically and the source is already locally accessible.