如何使用 Docker 跑 ROS2 應用?

本文是 AI 神經網路自走車 第五篇。官方文件提供的安裝法為直接裝入系統之中。為了統一環境和隔離應用,引入 docker container 是一個不錯的選擇,這邊分享如何自行 build ROS2 的 docker image,以及如何使用 container 化的 ROS2。

準備 ROS2 Docker file

我們參考 ros2_trt_post repository 為 nvidia jetson 系列平台寫的 docker file,改成一般通用型的 docker file 來實作。

簡單解釋一下裏面在做的事情:

  1. image base 原本是使用 nvcr.io/nvidia/l4t-base:r32.4.3 這個 nvidia jetson 平台專用的 image,我們改成 ubuntu:18.04 ,這樣其他平台如 raspberry pi 也能使用(之後文章我們會使用 nvidia jetson nano,如果你也用一樣的平台的話,請修改回 nvcr.io/nvidia/l4t-base:r32.4.3)
  2. 安裝的 ROS 版本為 eloquent,並裝在 /opt/ros/eloquent 下
  3. 設定 locale 和 lang 為 en_US.UTF-8
  4. 裝入一些基本套件如 git 、cmake、build-essential 等等
  5. 將 ROS deb repo 加入 apt 來源,並且開始安裝相關套件
  6. 設定 entrypoint,把已經寫好的 ros_entrypoint.sh 複製到 image 裏面
ARG BASE_IMAGE=ubuntu:18.04
FROM ${BASE_IMAGE}

ARG ROS_PKG=ros_base
ENV ROS_DISTRO=eloquent
ENV ROS_ROOT=/opt/ros/${ROS_DISTRO}

ENV DEBIAN_FRONTEND=noninteractive

WORKDIR /workspace

# change the locale from POSIX to UTF-8
RUN locale-gen en_US en_US.UTF-8 && update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
ENV LANG=en_US.UTF-8

# add the ROS deb repo to the apt sources list
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
          git \
		cmake \
		build-essential \
		curl \
		wget \ 
		gnupg2 \
		lsb-release \
    && rm -rf /var/lib/apt/lists/*
    
RUN wget --no-check-certificate https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc && apt-key add ros.asc
RUN sh -c 'echo "deb [arch=$(dpkg --print-architecture)] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" > /etc/apt/sources.list.d/ros2-latest.list'

# install ROS packages
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
		ros-eloquent-ros-base \
		ros-eloquent-launch-xml \
		ros-eloquent-launch-yaml \
		ros-eloquent-vision-msgs \
                ros-eloquent-image-tools \
		libpython3-dev \
		python3-colcon-common-extensions \
		python3-rosdep \
    && rm -rf /var/lib/apt/lists/*
  
# init/update rosdep
RUN apt-get update && \
    cd ${ROS_ROOT} && \
    rosdep init && \
    rosdep update && \
    rm -rf /var/lib/apt/lists/*
    
# compile yaml-cpp-0.6, which some ROS packages may use (but is not in the 18.04 apt repo)
RUN git clone --branch yaml-cpp-0.6.0 https://github.com/jbeder/yaml-cpp yaml-cpp-0.6 && \
    cd yaml-cpp-0.6 && \
    mkdir build && \
    cd build && \
    cmake -DBUILD_SHARED_LIBS=ON .. && \
    make -j$(nproc) && \
    cp libyaml-cpp.so.0.6.0 /usr/lib/aarch64-linux-gnu/ && \
    ln -s /usr/lib/aarch64-linux-gnu/libyaml-cpp.so.0.6.0 /usr/lib/aarch64-linux-gnu/libyaml-cpp.so.0.6

# setup entrypoint
COPY ./ros_entrypoint.sh /ros_entrypoint.sh
RUN echo 'source ${ROS_ROOT}/setup.bash' >> /root/.bashrc 
RUN chmod +x /ros_entrypoint.sh
ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["bash"]
WORKDIR /

可以發現最後一步設定 entrypoint 的時候需要 ros_entrypoint.sh 這個檔案,接下來我們開始準備

準備 ros_entrypoint.sh

這份檔案很簡單,讓 container 剛建立完成時,就去 source ROS2 的 setup.bash,初始化 ROS2 的環境變數。如果沒有這一行,建立起來的 container 是無法使用 ROS2 的。此步驟很重要,之後建立自己的 ROS package 時也需要做一樣的初始化,ROS2 才能正確地找到 package,這部分會在文章後半提及。

我們將這份檔案存為 ros_entrypoint.sh,與 Dockerfile 放在同一個目錄底下,準備開始 build ROS2 docker image。

#!/bin/bash
set -e

# setup ros2 environment
source "/opt/ros/\$ROS_DISTRO/setup.bash"
exec "\$@"

Build RO2 Docker image

在 Dockerfile 同樣的目錄中使用以下指令開始 build ROS2 docker image

docker build -f Dockerfile -t ros2:latest .

依照每個平台的效能不同,可能需要一段不算短的時間,可以先喝杯咖啡休息一下。

進入 ROS2 docker container

build 完畢,我們使用以下指令建立一個一次性的 ROS2 container,並直接進入他的 shell

docker run -it --rm ros2:latest bash

進入 container 後,我們可以試著印出目前已有的 topic

root@e50b6063314a:/src$ ros2 topic list

如果有正確印出,表示 ROS2 有正確安裝到 docker image 裡,且 entrypoint 也有設定正確,container 裡面有正確的環境變數給 ROS 使用。

在 ROS2 container 裡 build package

假定你已經寫了幾個 package,我們可以在 container 裡面 build code 看看。尚未了解 ROS 專案結構的朋友們可以先到 如何開發第一個 ROS2 應用一文閱讀。

舉例來說,你在自己的家目錄下建立的 src 資料夾,裡面放入了兩個 package

.
└── src
    ├── a_package
    │   ├── setup.py
    │   ├── setup.cfg
    │   ├── package.xml
    │   └── a_package
    │       ├── xxxx.py
    │       └── yyyy.py
    └── b_package
        ├── CMakeLists.txt
        ├── package.xml
        └── b_package
            ├── file1.cpp
            └── file2.cpp

我們可以先在 src 同級目錄下建立一個 build.sh,方便以後 build code。

這邊 colcon build 後面帶 –symlink-install 的原因是讓 python code 的 package 只安裝 symlink 到 ROS2 裡面,如此開發中修改 python source code 後,都不需要再跑一次 build,重新 ros run 即可生效,大大減少時間浪費!

#!/bin/bash

rosdep install -i --from-path . -y  # 安裝 package 依賴的套件
colcon build --symlink-install  # build package

建立完畢後記得

chmod +x build.sh

以後執行只需要在 container 裡面輸入以下指令即可

root@e50b6063314a:/src$ ./build.sh

當然,在啟動 container 的時候記得要把自己的 source code mount 進去 container 哦

docker run -it --rm -v $HOME/src:/src ros2:latest bash

在 ROS2 container 裡 run package

還記得前面 build image 的時候引入的 ros_entrypoint.sh 的目的嗎?讓 ROS 相關的環境變數可以正確設置。同理如果今天要使用自己開發的 package,也需要做類似的事情。

一樣,記得啟動 container 的時候要 mount source code

docker run -it --rm -v $HOME/src:/src ros2:latest bash

回顧前面的 src 資料夾樹狀圖,當 build 完後會發現 package 下多一個資料夾為 install,裡面有很多 setup.*sh 的檔案

.
└── src
    ├── a_package
    │   ├── setup.py
    │   ├── setup.cfg
    │   ├── package.xml
    │   ├── a_package
    │   │  ├── xxxx.py
    │   │  └── yyyy.py
    │   └── install
    │       ├── setup.sh
    │       ├── setup.bash
    │       ├── setup.zsh

在 container 裡,我們用 setup.bash 設置環境變數

root@e50b6063314a:/src$ source a_package/install/setup.bash

之後就可以執行自己 package 的指令了

root@e50b6063314a:/src$ ros2 run a_package some_cmd

對於 ROS 初學者最後兩節不清楚沒關係,趕快閱讀 如何開發第一個 ROS2 應用 快速了解吧!如果覺得我文章內容對你有幫助的話,請在文章後面幫我按 5 個讚!讓我知道大家都喜歡什麼內容哦!

AI 神經網路自走車
上一篇:十分鐘快速認識 ROS (Robot Operating System)
下一篇:如何開發第一個 ROS2 應用

Written by J
雖然大學唸的是生物,但持著興趣與熱情自學,畢業後轉戰硬體工程師,與宅宅工程師們一起過著沒日沒夜的生活,做著台灣最薄的 intel 筆電,要與 macbook air 比拼。 離開後,憑著一股傻勁與朋友創業,再度轉戰軟體工程師,一手扛起前後端、雙平台 app 開發,過程中雖跌跌撞撞,卻也累計不少經驗。 可惜不是那 1% 的成功人士,於是加入其他成功人士的新創公司,專職開發後端。沒想到卻在採前人坑的過程中,拓寬了眼界,得到了深層的領悟。