docker作为一种开源的应用容器引擎,基于Go 语言并遵从 Apache2.0 协议开源。之前网上存在一些使用docker去安装ros的相关教程。但是目前网上的教程都无法装载GAZEBO RVIZ等常用的一些软件。

在做纯编译开发时,我们不需要对CPP/Python代码进行编译,这时候能够一键安装环境的Docker就显得十分有必要,这样的方式不会考虑环境配置的差异,而且能够快速的搭建好环境。
在这里插入图片描述

创建python文件来运行程序

build

import argparse
import docker_utils as ut
import os

def main():
    # Parse arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-g', '--gazebo', dest="gazebo_version", default="9")
    parser.add_argument('-r1', '--ros1', dest="ros1_version", default="melodic")
    parser.add_argument('-r2', '--ros2', dest="ros2_version", default="")
    args = parser.parse_args()

    gz_version_tag = '9'
    # If ROS 2 is not defined, ROS 1 is compiled
    if args.ros2_version == "":
        # Compile ROS 1
        ros_version_name = 'ros1'
        ros_version_tag  = args.ros1_version
        gz_version_tag = args.gazebo_version
    else:
        # Compile ROS 2
        ros_version_name = 'ros2'
        ros_version_tag  = args.ros2_version
        if not (args.gazebo_version == "9"):
            print("Gazebo 9 is only supported. Using this version by default.")

    # Select image to compile
    image_name = '{}'.format(ros_version_name)

    # Build selected image
    command = 'cd {} && ROS_VERSION={} GZ_VERSION={} make {}'.format(
            ut.get_repo_root(), ros_version_tag, gz_version_tag, image_name)
    ut.run_command(command)

if __name__ == '__main__':
    main()

该文件是我们启动安装的入口,在输入./build后则执行到安装ros的指令中。
ros1/Dockfile

FROM ubuntu_18

ARG ros1
ENV ROS1_DISTRO ${ros1}
ARG gz
ENV GZ_VERSION ${gz}
ENV USER docker_ros

USER root

# Setup environment
RUN apt-get update && apt-get install -y locales
RUN locale-gen en_US.UTF-8
ENV \
  LANG=en_US.UTF-8 \
  DEBIAN_FRONTEND=noninteractive \
  TERM=xterm

RUN apt-get remove -y --purge xserver-xorg

RUN apt-get update && \
    apt-get install --no-install-recommends -y \
    gnupg2 \
    mesa-utils \
    sudo \
    tmux \
    wget \
    xserver-xorg

RUN dpkg-reconfigure xserver-xorg

# Add ROS keys
# https://discourse.ros.org/t/new-gpg-keys-deployed-for-packages-ros-org/9454
RUN echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list
RUN apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

# Add Gazebo keys
RUN echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable $(lsb_release -cs) main" > /etc/apt/sources.list.d/gazebo-stable.list
RUN wget http://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add -

# Install bootstrap tools
RUN apt-get update && apt-get install --no-install-recommends -y \
    python-rosdep \
    python-rosinstall

# Install ROS packages
RUN apt-get update && \
    apt-get install -y \
    ros-${ROS1_DISTRO}-desktop

RUN apt-get update && \
    apt-get install -y \
    gazebo${GZ_VERSION} \
    libgazebo${GZ_VERSION}-dev

# Install dependencies
# https://unix.stackexchange.com/a/391112
COPY packages.txt packages.txt
RUN apt-get update && \
    xargs -a packages.txt apt-get install -y

# ==================

RUN rosdep init
USER $USER
RUN echo ". /opt/ros/${ROS1_DISTRO}/setup.bash" >> /home/${USER}/.bashrc
RUN rosdep update
USER root

# Workspace
RUN mkdir -p /catkin_ws/src/ && \
    chown -R $USER /catkin_ws

WORKDIR /catkin_ws/

RUN usermod -a -G video $USER
RUN usermod -a -G dialout $USER

RUN apt-get update

USER $USER

该文件主要是Docker安装的指令合集,通过build文件传入基础的ros版本以及gazebo版本来方便动态安装管理。

ros1/packages.txt

libspdlog-dev
libjsoncpp-dev
libeigen3-dev
libzmq3-dev
libusb-1.0.0-dev
mesa-common-dev
build-essential
libgl1-mesa-dev
libpqxx-dev

ros-*-amcl
ros-*-base-local-planner
ros-*-costmap-2d
ros-*-depth-image-proc
ros-*-depthimage-to-laserscan
ros-*-dynamixel-workbench-toolbox
ros-*-four-wheel-steering-msgs
ros-*-gazebo-plugins
ros-*-gazebo-ros-control
ros-*-gazebo-ros-pkgs
ros-*-geographic-msgs
ros-*-gmapping
ros-*-hector-gazebo-plugins
ros-*-image-proc
ros-*-joint-state-publisher-gui
ros-*-joy
ros-*-map-server
ros-*-mavros-msgs
ros-*-move-base
ros-*-moveit
ros-*-moveit-commander
ros-*-moveit-python
ros-*-moveit-visual-tools
ros-*-navfn
ros-*-open-manipulator-msgs
ros-*-pcl-ros
ros-*-pluginlib
ros-*-rgbd-launch
ros-*-robot-calibration
ros-*-robot-controllers
ros-*-robot-localization
ros-*-ros-control
ros-*-ros-controllers
ros-*-rosparam-shortcuts
ros-*-rosserial-python
ros-*-rotate-recovery
ros-*-serial
ros-*-slam-karto
ros-*-teb-local-planner
ros-*-teleop-twist-keyboard
ros-*-tf2-geometry-msgs
ros-*-twist-mux
ros-*-urdf-geometry-parser
ros-*-voxel-grid

这个package包中包含了待安装的函数库包。

ros2/Dockerfile

FROM ubuntu_18

ARG ros2
ENV ROS2_DISTRO ${ros2}
ARG gz
ENV GZ_VERSION ${gz}
ENV USER docker_ros

USER root

# Tmux
RUN apt-get update && \
    apt-get install --no-install-recommends -y \
    curl \
    gnupg2 \
    lsb-release \
    sudo \
    tmux

# setup keys
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
RUN curl http://repo.ros2.org/repos.key | apt-key add -

# setup sources.list
RUN echo "deb http://packages.ros.org/ros/ubuntu `lsb_release -sc` main" > /etc/apt/sources.list.d/ros-latest.list
RUN echo "deb [arch=amd64,arm64] http://packages.ros.org/ros2/ubuntu `lsb_release -cs` main" > /etc/apt/sources.list.d/ros2-latest.list

# install ros2 packages
RUN apt-get update && \
    apt-get install -y \
    ros-$ROS2_DISTRO-desktop

# install bootstrap tools
RUN apt-get update && apt-get install --no-install-recommends -y \
    ros-$ROS2_DISTRO-gazebo-*

# install ros2 packages
RUN apt-get update && apt-get install -y \
    ros-$ROS2_DISTRO-ros1-bridge \
    python3-argcomplete \
    python3-colcon-common-extensions

# Source ros2 automatically
RUN echo ". /opt/ros/$ROS2_DISTRO/setup.bash" >> /home/${USER}/.bashrc

# ==================

# Workspace
RUN mkdir -p /colcon_ws/src/ && \
    chown -R $USER /colcon_ws
RUN rosdep init
USER $USER
WORKDIR /colcon_ws/
RUN rosdep update
USER root

RUN apt-get update

USER $USER

这部分是ROS2的Docker安装指令。

docker_utils.py

import os
import subprocess
from subprocess import PIPE

def run_command(command):
    subprocess.call(command, shell=True)

def get_repo_root():
    return subprocess.check_output('git rev-parse --show-toplevel'.split()).strip()

def get_uid():
    return os.getuid()

def get_user():
    return os.getlogin()

def create_directory(directory):
    run_command("mkdir -p {}".format(directory))
    run_command("sudo chown {0}:{0} {1}".format(get_user(), directory))

可选择的执行策略

[-r1|—ros1]: 默认选择ROS1的 版本(melodic)。

[-r2|—ros2]: 选择ROS2的版本
[-g|—gazebo]: 选择ROS中Gazebo版本。(目前ROS2只支持Gazebo 9 的版本)

ROS 1 Melodic + Gazebo 11

./build --gazebo 11

在这里插入图片描述

ROS 2 Eloquent + Gazebo 9

./build --ros2 eloquent

在这里插入图片描述

此时,我们的基于docker的ROS环境已经安装完毕,在同一路径下我们创建一个run文件来启动docker容器

运行环境

run

#!/usr/bin/env python2.7

import argparse
import subprocess
import docker_utils as ut
import os

IMAGE_NAME = "gazebo_ros_docker"

def run_dev_environment(command, ws_mount, ros1="melodic", ros2=""):
    user = ut.get_user()
    docker_args = []
    dockerfile  = '{}'.format('ros1' if ros2 == '' else 'ros2')

    temp_volume = "/home/{}/.{}".format(user, IMAGE_NAME)

    # Workspace name
    ws_name = "{}_ws".format("catkin" if ros2 == "" else "colcon")

    docker_args.append("-it")
    docker_args.append("--rm")
    docker_args.append("--env=\"DISPLAY\"")
    docker_args.append("--volume=\"/tmp/.X11-unix:/tmp/.X11-unix:rw\"")
    docker_args.append("--volume=\"$HOME/.Xauthority:/root/.Xauthority:rw\"")
    docker_args.append("--name=\"{}\"".format(IMAGE_NAME))
    docker_args.append("--privileged")
    docker_args.append("--network=\"host\"")
    docker_args.append("--user {0}:{0}".format(ut.get_uid()))
    # Keep user settings
    docker_args.append("--volume {}/user/:/home/{}/".format(temp_volume, user))
    # Mount workspace
    docker_args.append("--volume {}:/{}".format(ws_mount, ws_name))
    docker_args.append("--volume {}/ws/build/:/{}/build/".format(temp_volume, ws_name))
    docker_args.append("--volume {}/ws/devel/:/{}/devel/".format(temp_volume, ws_name))

    docker_args.append("-e ROS_HOSTNAME=localhost")
    docker_args.append("-e ROS_MASTER_URI=http://localhost:11311")
    docker_args.append("--workdir /{}/".format(ws_name))

    if ut.is_nvidia():
        docker_args.append("--runtime=\"nvidia\"")
        dockerfile += "_nvidia"

    # Join arguments together separated by a space
    docker_args = ' '.join(docker_args)
    docker_command = "docker run {} {} {}".format(docker_args, dockerfile, command)

    ut.create_directory("{}/user/".format(temp_volume))
    ut.create_directory("{}/ws/build/".format(temp_volume))
    ut.create_directory("{}/ws/devel/".format(temp_volume))

    ut.run_command("xhost +local:root")
    ut.run_command(docker_command)
    ut.run_command("xhost -local:root")

def attach_dev_environment(command):
    command = 'docker exec -it --user {0}:{0} {1} {2}'.format(ut.get_uid(), IMAGE_NAME, command)
    ut.run_command(command)

def is_running():
    command = 'docker ps | grep {} > /dev/null'.format(IMAGE_NAME)
    try:
        subprocess.check_call(command, shell=True)
    except Exception:
        return False

    return True

def main():
    # Parse arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-c', '--cmd', dest='command', default='bash')
    parser.add_argument('-r1', '--ros1', dest="ros1_version", default="melodic")
    parser.add_argument('-r2', '--ros2', dest="ros2_version", default="")
    parser.add_argument('-ws', '--workspace', dest='workspace', default="/home/{}/catkin_ws".format(ut.get_user()))
    args = parser.parse_args()

    if not is_running():
        run_dev_environment(args.command, ws_mount=args.workspace, ros1=args.ros1_version, ros2=args.ros2_version)
    else:
        attach_dev_environment(args.command)

if __name__ == '__main__':
    main()

下面我们对上面文档进行解释,除了上面已有的r1和r2以外,还多出了-c和-ws

[-c|—cmd]: 运行命令(默认为bash)。Tmux终端也可以使用。

[-ws|—workspace]: 选择要挂载的工作区域,避免重复使用cd来修改参数

ROS 1 Melodic + Gazebo 11

./run --cmd tmux

在这里插入图片描述
ROS 2 Eloquent + Gazebo 9

./run --ros2 eloquent -ws "/home/my_user/my_colcon_ws"

在这里插入图片描述
从下图中我们可以看到docker环境已经启动,此时,我们仅需要正常的去使用该容器即可,避免了大量的时间的浪费。
在这里插入图片描述