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

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

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    cmake_minimum_required(VERSION 3.0)
    set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
    
    project(hello_world_snd)
    
    set(CMAKE_CXX_STANDARD 14)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    find_package(eCAL REQUIRED)
    
    set(source_files
      main.cpp
    )
    
    add_executable(${PROJECT_NAME} ${source_files})
    
    target_link_libraries(${PROJECT_NAME}
      eCAL::core
    )
    
  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
    2
    3
    4
    5
    6
    7
    8
    #ecal base image:
    FROM ecal-runtime
    
    WORKDIR /src/sub
    
    COPY CMakeLists.txt main.cpp ./
    RUN cmake . && make
    CMD ./hello_world_rec
    
  2. Create subscriber source code: sub_container/main.cpp

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

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

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      version: "3"
      
      services:
          subscriber:
              build: ./sub_container
              image: ecal-subscriber:1.0.0
              container_name: ecal-subscriber
              network_mode: host
              ipc: host
              pid: host
          publisher:
              build: ./pub_container
              image: ecal-publisher:1.0.0
              container_name: ecal-publisher
              network_mode: host
              ipc: host
              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