eCAL in Docker#

Here we will show how to deploy eCAL into a docker container, and how to use its image from other containers.

Important

This will work with eCAL 5.10 and up. Older eCAL versions will lack Shared Memory communication when being run in a docker container.

Prerequisite#

Getting Started#

In this tutorial we are going to create:

  • A general purpose eCAL Docker container

  • A publisher container with a custom Hello World Publisher

  • A subscriber container receiving the Hello World data.

The file hierarchy that we are going to follow:

 ecal_in_docker
├─  docker-compose.yaml
|
├─  ecal_runtime_container
|  └─  Dockerfile
|
├─  pub_container
|  ├─  Dockerfile
|  ├─  CMakeLists.txt
|  └─  main.cpp
|
└─  sub_container
   ├─  Dockerfile
   ├─  CMakeLists.txt
   └─  main.cpp

eCAL runtime container#

  1. Create the file ecal_runtime_container/Dockerfile and paste the following installation commands:

     1# Base image:
     2FROM ubuntu:focal
     3
     4# Install eCAL from PPA:
     5RUN apt-get update && \
     6	apt-get install -y software-properties-common && \
     7	rm -rf /var/lib/apt/lists/*
     8RUN add-apt-repository ppa:ecal/ecal-latest
     9RUN apt-get install -y ecal
    10
    11# Install dependencies for compiling the hello world examples.
    12# You can omit this, if you don't want to build applications in the container.
    13RUN apt-get install -y cmake g++ libprotobuf-dev protobuf-compiler
    14
    15# Set network_enabled = true in ecal.ini.
    16# You can omit this, if you only need local communication.
    17RUN awk -F"=" '/^network_enabled/{$2="= true"}1' /etc/ecal/ecal.ini > /etc/ecal/ecal.tmp && \
    18	rm /etc/ecal/ecal.ini && \
    19	mv /etc/ecal/ecal.tmp /etc/ecal/ecal.ini 
    20
    21# Print the eCAL config
    22RUN ecal_config
    
  2. Build the image:

    cd ecal_in_docker
    sudo docker build . --rm -t ecal-runtime
    
  3. Test the image

    sudo docker run --rm -it --ipc=host --pid=host --network=host ecal-runtime
    

    At this point you are in the docker container. You can exit it with exit. If you run ecal_sample_person_snd in the docker container and have an eCAL installation on your host, you can subscribe to the data via the eCAL Monitor or ecal_sample_person_rec.

    Note

    • --ipc=host will enable Shared Memory communication with your host system and other docker containers that are started with the same parameter. This is important for local communication.

    • --network=host will share the host’s network. This is important for network communcation with other machines. It is also important for local shared memory communication, as it affects the hostname of the container. The hostname is used to determine whether an eCAL topic is avaialble via shared memory.

    • --pid=host will share the Process-ID range with the host. Otherwise processes from different containers may get the same Process ID, which will prevent communication between those two processes.

Publisher container#

The publisher container will be built on top of the ecal-runtime container. It will contain the Hello World Sample from the Getting Started Section.

  1. Create a file pub_container/Dockerfile and paste the following content:

    1#ecal base image:
    2FROM ecal-runtime
    3
    4WORKDIR /src/pub
    5
    6COPY CMakeLists.txt main.cpp ./
    7RUN cmake . && make
    8CMD ./hello_world_snd
    
  2. Create publisher source code: pub_container/main.cpp

     1#include <ecal/ecal.h>
     2#include <ecal/msg/string/publisher.h>
     3
     4#include <iostream>
     5#include <thread>
     6
     7int main(int argc, char** argv)
     8{
     9  // Initialize eCAL. The name of our Process will be "Hello World Publisher"
    10  eCAL::Initialize(argc, argv, "Hello World Publisher");
    11
    12  // Create a String Publisher that publishes on the topic "hello_world_topic"
    13  eCAL::string::CPublisher<std::string> publisher("hello_world_topic");
    14
    15  // Create a counter, so something changes in our message
    16  int counter = 0;
    17
    18  // Infinite loop (using eCAL::Ok() will enable us to gracefully shutdown the
    19  // Process from another application)
    20  while (eCAL::Ok())
    21  {
    22    // Create a message with a counter an publish it to the topic
    23    std::string message = "Hello World " + std::to_string(++counter);
    24    std::cout << "Sending message: " << message << std::endl;
    25    publisher.Send(message);
    26
    27    // Sleep 500 ms
    28    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    29  }
    30
    31  // finalize eCAL API
    32  eCAL::Finalize();
    33}
    
  3. Create file pub_container/CMakeLists.txt

     1cmake_minimum_required(VERSION 3.0)
     2set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
     3
     4project(hello_world_snd)
     5
     6set(CMAKE_CXX_STANDARD 14)
     7set(CMAKE_CXX_STANDARD_REQUIRED ON)
     8
     9find_package(eCAL REQUIRED)
    10
    11set(source_files
    12  main.cpp
    13)
    14
    15add_executable(${PROJECT_NAME} ${source_files})
    16
    17target_link_libraries(${PROJECT_NAME}
    18  eCAL::core
    19)
    
  4. Build the image:

    cd pub_container
    sudo docker build . --rm -t ecal-publisher:1.0.0
    

Subscriber container#

The subscriber container will also be based on the ecal-runtime container and contain the Hello World Sample from the Getting Started Section.

  1. Create a file: sub_container/Dockerfile

    1#ecal base image:
    2FROM ecal-runtime
    3
    4WORKDIR /src/sub
    5
    6COPY CMakeLists.txt main.cpp ./
    7RUN cmake . && make
    8CMD ./hello_world_rec
    
  2. Create subscriber source code: sub_container/main.cpp

     1#include <ecal/ecal.h>
     2#include <ecal/msg/string/subscriber.h>
     3
     4#include <iostream>
     5#include <thread>
     6
     7// Callback for receiving messages
     8void HelloWorldCallback(const std::string& message)
     9{
    10  std::cout << "Received Message: " << message << std::endl;
    11}
    12
    13int main(int argc, char** argv)
    14{
    15  // Initialize eCAL
    16  eCAL::Initialize(argc, argv, "Hello World Subscriber");
    17
    18  // Create a subscriber that listenes on the "hello_world_topic"
    19  eCAL::string::CSubscriber<std::string> subscriber("hello_world_topic");
    20
    21  // Set the Callback
    22  subscriber.AddReceiveCallback(std::bind(&HelloWorldCallback, std::placeholders::_2));
    23
    24  // Just don't exit
    25  while (eCAL::Ok())
    26  {
    27    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    28  }
    29
    30  // finalize eCAL API
    31  eCAL::Finalize();
    32}
    
  3. Create file sub_container/CMakeLists.txt

     1cmake_minimum_required(VERSION 3.0)
     2set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
     3
     4project(hello_world_rec)
     5
     6set(CMAKE_CXX_STANDARD 14)
     7set(CMAKE_CXX_STANDARD_REQUIRED ON)
     8
     9find_package(eCAL REQUIRED)
    10
    11set(source_files
    12  main.cpp
    13)
    14
    15add_executable(${PROJECT_NAME} ${source_files})
    16
    17target_link_libraries(${PROJECT_NAME}
    18  eCAL::core
    19)
    
  4. Build the image:

    cd sub_container
    sudo docker build . --rm -t ecal-subscriber:1.0.0
    

Run the docker containers#

  • You can run the publisher and subscriber images manually with docker run.

    sudo docker run --rm -it --ipc=host --network=host --pid=host ecal-subscriber:1.0.0
    sudo docker run --rm -it --ipc=host --network=host --pid=host ecal-publisher:1.0.0
    
  • You can also use the docker-compose file to manage multiple containers.

    1. In the parent folder create file docker-compose.yaml and paste the following content:

       1version: "3"
       2
       3services:
       4    subscriber:
       5        build: ./sub_container
       6        image: ecal-subscriber:1.0.0
       7        container_name: ecal-subscriber
       8        network_mode: host
       9        ipc: host
      10        pid: host
      11    publisher:
      12        build: ./pub_container
      13        image: ecal-publisher:1.0.0
      14        container_name: ecal-publisher
      15        network_mode: host
      16        ipc: host
      17        pid: host
      
    2. You can now use that docker-compose to build/run the publisher and subscriber containers:

      sudo docker-compose build
      sudo docker-compose up
      

Seamless IPC-Communication across host borders#

Important

This will work with eCAL 5.12 and higher. Older versions lack the ability to utilize the host_group_name in the ecal.ini file, thus it won’t work.

In eCAL, you are able to set host belonging over network borders by utilizing the ecal.ini configuration file with the same host_group_name - in the following steps, you will learn how to set this up.

Note

If we don’t set the same host_group_name on our Host and our Containers, an IPC-Communication across host borders is not available with different host names.

  1. To encapsulate your container network from your host network, you need to create a new docker network with the following command:

    sudo docker network create --driver=bridge --subnet=10.0.10.0/24 my_network
    
  2. Edit your ecal.ini and run your Container within the newly created docker network

    • You will use our previously discussed ecal-runtime-image for the next step.

    • First, open /etc/ecal/ecal.ini from your preferred editor.

    • Search for the line network_enabled and set it to true.

    • Search for the line host_group_name and write your preferred name.

    • Save and close the ecal.ini file.

    • Now your ecal.ini file is prepared. We want to use it not only for our Host-System but also for our Container, so we don’t need to edit the ecal.ini in our Container again. To achieve that, run following command to start your container:

    sudo docker run --rm -it --ipc=host --pid=host --network=my_network --name=container1 -h=container1 --ip=10.0.10.10 -v /etc/ecal/ecal.ini:/etc/ecal/ecal.ini ecal-runtime
    
    • You should now be inside the root shell of your Container. Check if your ecal.ini file is correct.

    • Now your Container is prepared and configured correctly, so we are ready to start an eCAL example.

    ecal_sample_person_snd
    
  3. Configure the Host network

    • eCAL is sending UDP messages to a multicast IP group 239.0.0.0/24, further information in Getting Started Section. The idea is now, to successfully receive those messages from your previously started container on your host. For that, you need to add a route to your routing table. By typing ifconfig in your shell, you can identify the right docker network. In our case, the prefix of the docker network is always br followed by random numbers. After identifying the right network, run following command.

    sudo ip route add 239.0.0.0/24 dev <br-xxx> metric 1
    
    • Review your network configuration. Your eCAL-Monitor should resemble this example:

    ../_images/doku_ecal_docker_mon.png
  4. (optional) After adding the route, you register the Container with IP address and name in /etc/hosts for DNS resolution, enabling easy access to it by hostname within the network.

    sudo nano /etc/hosts
    
    ../_images/vscode_etc_hosts.png

When you are done, all eCAL nodes can communicate seamlessly from docker to the host and vice versa.