Introduction
One of the things we have been asked many times is how to wrap a REDHAWK Component with some container-based solution like Docker. Such a capability would put the Component into a different runtime environment than the General Purpose Processor (GPP) that was executing it.
Now, let your mind wander a bit on what things you would want to deploy in a container. Imagine being able to leverage a library of signal processing chains with a framework that can distribute it to remotely-networked resources, like a Domain full of FrontEnd Interfaces -compatible hardware…
What we are presenting here, as stated in the demo video, is not necessarily The Way (or The Only Way) to do this. It is just one way: a demo capability to get a conversation started, a conversation we want to have with the community so that we can grow this together. So at the end of this, I would encourage you to fork our repositories and think about where we go from here.
For starters, ours skips over the fact that we’re checking the ability to actually start the container at the Execute stage. Ideally, we really should be checking at Load, but in the 2.0.x series, this would require synchronising (duplicating) a couple of properties so that we get both a load dependency and an execution instruction in the GPP. For now, we are providing an extension of the existing GPP that assumes you have provisioned your overall system so that all your Docker -bourne Components have their images installed alongside this modified GPP Device. Perhaps in REDHAWK 2.1, we can divise a cleaner way to provision systems with a GPP by leveraging Load as well.
Note: This blog post and related software are for REDHAWK version 2.0.4, which at the time this capability was developed, February 2017, was the latest version. The current version as of 7 September 2017 is 2.0.6 (2.1 is a beta).
Existing Ground Work
We’re not alone on this idea of using Docker in REDHAWK: see here. That’s the source code of the Application Factory implementation looking for an executable property (exec_param, now Pass on Command Line) with the ID: __DOCKER_IMAGE__
. In the chain of launching a Waveform then, this is the initial product of the Domain preparing a list of properties that will be passed to an Executable Device that responds affirmatively to the preceding load
request for a given Component. Reminder: load
is the chance to match an Loadable Device (parent to Executable) to match the Component’s dependencies, and load
precedes the execute
stage. If we go looking at the GPP source code however, the complimentary feature does not exist for either stage, hence this effort: we’ll have to develop the capability ourselves.
The Concept
At a high level, we need the GPP to recognize the __DOCKER_IMAGE__
property ID during the call to execute
by checking for the image, and then prefixing the original Component binary call with docker run ...
. And while we are at it, it would be really nice to inject additional arguments (for volumes, etc.) into that command. For that, we have added a similarly named ID: __DOCKER_ARGS__
. Graphically then, the end of the process looks like this:
Note: Don’t worry, in the demo video, we walk through the interactions/stages of the process.
We have a DockerComponent
with the two properties, and our custom, extended GPP
in a Device Manager colocated with where our Docker images are already loaded. When we go to launch an instance of Waveform A
, the GPP checks the __DOCKER_IMAGE__
property against the response of querying Docker. If the image exists, the GPP adjusts the command line arguments to prefix the executable with docker run ...
and adjusts the path for the container environment. The GPP dispatches a new thread with this new set of arguments just like it normally would and begins managing the process.
It’s a very simple extension of the base GPP with some pretty interesting possibilities.
The Implementation
In the following sections, we’ll cover the two main pieces of the design: the GPP and the example Docker image. Source code is provided along with a demo video showcasing the pass-through DockerComponent
, which is included with the Docker image builder and discussed separately.
The GPP
As part of this design, we modified the GPP to check against Docker for the image, and then call the Docker daemon. This means that your redhawk
user (or whatever user is running the GPP process) will need rights to call docker
. Provision your system accordingly.
You can find the source code for this GPP extension here. We’ve disconnected it as a fork so there are no accidental pulls of other versions of code, etc. but the commit history is intact if you would like to generate a patch. The only branch you will find at this time is docker-gpp-2.0.4
.
Important: Since this is an extension to the existing GPP, you will need to uninstall or move the original before you try to install this one.
Note: The link above now goes tocore-framework-docker-gpp
. The othercore-framework
repository is where we’ll be rolling forward to 2.0.5 soon.
Change into the GPP
subdirectory and run:
./build.sh
./build.sh install
The next time you run the default Device Manager referencing the standard GPP ID, you will get this newly extended one instead. And because it’s an extension to the original, it can still support running standard Components as well.
The Docker Image
Our testing of this design was performed on a standard CentOS 7 64-bit installation of REDHAWK 2.0.4 (i.e., we used the RPMs) and then we installed Docker CE using the standard installation instructions involving their own repository. We went this route for the demo to drive home the idea of having an otherwise vanilla installation of REDHAWK that will be launching a Component within an Ubuntu environment.
Our image builder repo is here. The process is detailed in the README.md
, since it’s a little out of the ordinary.
Skipping past the README.md
: clone the repository and run make
. The resulting redhawk/minimal
image is Ubuntu 16.04 with the REDHAWK SDR Core Framework’s base requirements baked into the image and the actual installation (libraries, headers, etc.) installed to a volume mountd to /opt/redhawk/sdr
. Additionally, the SDRROOT is also in its own separate volume.
Note: The separate volumes are to facilitate future provisioning since depending on your implementation, the base image may not need to change, rather just the contents of the volumes, which are much smaller.
The DockerComponent
The example pass-through DockerComponent
is not installed as part of the redhawk/minimal
image build. We do however provide a make
target to facilitate its installation into the SDRROOT volume, which can easily be reconfigured to install your own Component as well (see the README.md
for docker-redhawk-minimal
).
Also, keep in mind, the Component has two representations. One is actually compiled (if necessary) and installed relative to the Docker environment. The other is just a venier for deployment’s sake.
Docker Image’s SDRROOT
The basic installation of the example component is using our make
target:
make component-installer RH_COMPONENT="${PWD}/example/DockerComponent"
A run
script will execute the typical ./build.sh && ./build.sh install
method within the Docker environment, linked to that environment’s version-compatible REDHAWK installation.
The variable RH_COMPONENT
is the absolute path to the source directory. You can also specify BASE_IMAGE
in case you extend the redhawk/component-installer
to inject other dependencies.
GPP Host SDRROOT
We also have to provide the host REDHAWK Domain system with the ability to reference the Component. This involves a little bit of trickery since you do not actually want (or need) to run ./build.sh && ./build.sh install
on the Domain’s host system.
A simple route for this is to copy the Component directly into the SDRROOT/dom/components
path. Then for each implementation (cpp
, python
, or java
), create an executable file for the anticipated implementation name. For example, if you had a cpp
implementation:
touch $SDRROOT/dom/components/MyComponent/cpp/MyComponent
This is enough effort to let you reference the Component in a Waveform as well as for the Domain’s Application Factory to be convinced that the Component actually has been installed when it goes to load it on a GPP.
Configuration
Several other configuration issues should be addressed when configuring your system(s) to use this capability including how to serve the OmniORB configuration file for the container(s), since you may not want to use /etc/omniORB.cfg
from the GPP’s host (for security reasons). See the docker-redhawk-minimal
README.md
for details of the configuration elements.
Conclusion
In this post, we’ve touched on the high-level concept and build instructions. We have also provided some of the lower-level details of how this concept actually works and some paths forward for how to improve it, so please do fork, extend, and submit pull requests. We would love get a conversation going about how to use this capability and see more sophisticated implementations of it.
Demo Video
The following demo video below walks through the concept’s execution process as well as the installation needs. The concluding example is a trivial proof of concept using the DockerComponent
, which is a pass-through Python component. And in as much as it is trivial, remember that Component is actually executing in an Ubuntu -based installation of REDHAWK SDR!