ROS2 Docker (workshop 2)
Video of Workshop 2
The video of the first presentation of Workshop 2 is here. The second presentation of Workshop 2 is here. It is mostly solving people's problems getting graphics to run in docker, but at the end we go through how to configure the DISPLAY variable.
Content Outline
Docker concepts: virtual env, images, containers, layers. shell & docker_exec. Containers as short-term resources. Process model. Mounted volumes. Networking model. Serial port access
How docker, RPi, and PC fit into the robot environment
PC & RPi pre-configuration: .bashrc
Install docker on PC and RPi
git clone workshop docker repo and build the ros2_x86_linux and ros2_rpi containers
Scripts, aliases and basic docker commands
Launch container & run a ROS2 command on RPi
Customize container (add nano, apt-get and Dockerfile). Look at the contents of Dockerfile
Configure vs-code for remote editing
Delete container., perform system prune, review other docker commands
CLI Cheat Sheet
Docker commands run from the RPi or Ubuntu shell (i.e. not inside a container)
docker image list # list the images that have been downloaded to this machine or built on this machine
docker container list # list the containers on this machine. -a option shows all, not just running.
docker system prune # clean up stopped containers, dangling file segments
docker image rm <image_name> # remove an image
docker build -t <image_name> . # build an image from a Dockerfile in the current directory
docker run # run a docker image in a container. Lots of options are used in the run_it.sh script
docker exec # launch another process (typically a shell) in a running container
Ctrl-D # exit a docker shell or an RPi shell
<newline>~. # exit an ssh session. (Used when the target dies)
Terminator
The copy/shift & new tab/window keys are the same on a normal Ubuntu terminal.
Ctrl+Shift+C copy selected text to clipboard
Ctrl+Shift+V paste from clipboard
Ctrl + Shift + T gives a new tab
Ctrl+Shift+N gives a new terminal window
Right-click -> preferences lets you save layouts
Docker Concepts
A docker container is a virtual environment without the isolated kernel of a VM: the kernel is shared but the kernel provides isolation between containers and native apps. It's virtual in the sense that the apps run in an environment that looks like they have their own machine running its own OS, but they don't. Containers are a half-way house between apps running in a VM and apps running on the native OS. The container contains all libraries and executables above the kernel interface, so it is independent of the environment of the host running the container.
Images, containers: An image is like an executable file - a container gets instantiated (much as a process gets instantiated) and loaded with the image. The container runs the virtual environment. Different than a process, a container exists on disk and could have changes made to its files and they would persist for the life of the container. (We don't do that.) A container is the runnable form of an image. It provides a virtual environment which can run apps. In our way of using docker, we don't let containers persist (even though they could) - we delete them when they shut down. We use them as a short-term resource for running apps. We choose to persist changes in the image, and rebuild the image when we need changes to the container. This keeps our configuration defined by the Dockerfile.
I provide a way to persist a container and relaunch it, which can be convenient for testing changes, but that's not the normal use case.
Images are built up in layers. In the Dockerfile we instruct the docker image builder to install apps and make other changes that will end up in the container. I own the content of the containers we'll use. I provide a template so that you can build on top of my containers and create your personally enhanced container. The active_docker function is useful to select which docker image you want to build, run or exec.
We configure the image to start a shell when a container is launched from the image. The terminal which launches the container gets a shell connection. Other terminals connect to a running container and get their own shell using docker exec. From their shells, each terminal can run different apps in the virtual environment (e.g. robot-app in one, ROS GUI in another, ROS bag recorder in another, and so on).
Hands-on
Install docker
In this segment we install the docker engine and utilities onto a system running an OS that isn't native Ubuntu 22.04
RPi running Raspberry Pi OS
Install docker on an RPi system from these instructions: https://docs.docker.com/engine/install/debian . Use the "Install using the apt repository" section of the instructions. Be sure to verify the installation by running the docker hello-world image per instructions.
Configure docker to run for a non-root user using these instructions: https://docs.docker.com/engine/install/linux-postinstall/
REBOOT.
Verify you can run the docker "hello-world" image without sudo
Ubuntu PC or VM
Install docker on an Ubuntu system from these instructions: https://docs.docker.com/engine/install/ubuntu/ . Install docker on an RPi system from these instructions: https://docs.docker.com/engine/install/debian . Use the "Install using the apt repository" section of the instructions. Be sure to verify the installation by running the docker hello-world image per instructions
Configure .bashrc for running docker (RPi or PC)
Edit ~/.bashrc and add the following to the end. Note: ~/ means in the home directory.
# Aliases
#--------
# Docker functions
#
# if ~/.active_docker doesn't exist, create it & set active_docker to ros2_rpi
if [ ! -f ~/.active_docker ]
then
echo "ros2_rpi" > .active_docker
fi
export ACTIVE_DOCKER=`cat ~/.active_docker`
active_docker () {
if [ $# -eq 1 ]; then
echo ${1} > ~/.active_docker
fi
export ACTIVE_DOCKER=`cat ~/.active_docker`
echo $ACTIVE_DOCKER
}
# Docker aliases
export PATH_TO_ROS_DOCKERS=${HOME}'/ROS_dockers'
# CHANGE DISPLAY VARIABLE ON THE NEXT LINE AS NEEDED FOR YOUR SYSTEM
alias docker_build='cd ${PATH_TO_ROS_DOCKERS}/${ACTIVE_DOCKER} && docker build --build-arg CONFIGURED_USER=$USER --build-arg DISPLAY=:1 -t ${ACTIVE_DOCKER} .'
# docker run
alias dr='cd ${PATH_TO_ROS_DOCKERS}/${ACTIVE_DOCKER} && ./run_it.sh $@'
# docker exec
alias de='docker exec -it `docker ps -lq` bash'
# docker start last active container
alias dsl='docker start -ai `docker ps -lq`'
NOTE: After adding the script above to .bashrc, change the setting of DISPLAY in the docker_build alias above as needed for your system - it may be :0 (if you have a locally-connected monitor) or :1 if not. Identify the correct setting by launching a terminal in a vnc window and running echo $DISPLAY The output is what you should set DISPLAY to in the docker_build alias above.
Run source .bashrc
On RPi, run active_docker ros2_rpi
On PC, run active_docker ros2_x86_linux
Configure github to allow your RPi to clone repos
In this segment we clone the ROS_dockers repo and build the ros2 docker images. Note: you must have a github account. <your_github_username> is your email address.
ssh into RPi
Generate a public and private key pair that allows the RPi to access github. cd .ssh; ssh-keygen -t ed25519 -C <your_github_username>
Run ls and verify the files id_ed25519 and id_ed25519.pub exist.
Use a browser to open github.com. Click on your name/avatar (top-right) and select settings. Click on "SSH and GPG keys" in the left menu. Press the "New SSH key" button. See images below for how to navigate github to add your key to allow the rpi to clone repos.
Give the new key a name, like "RPi ssh key"
On the RPi, run cat id_ed25519.pub and use the mouse to select/copy the file contents.
Paste the contents of id_ed25519.pub into the key box on the web page. Ensure there is no leading or trailing whitespace. Save the key. VERY IMPORTANT: copy-paste has proven problematic. Selecting the key in the text window and pasting it into the web page has often failed. Double-check for no hidden whitepace if you get permission denied (publickey) when you try to clone the repo in the next step.
Select "Settings" under your account name
In the Settings menu, click on "SSH and GPG keys"
Click the "New SSH key" button to add your RPi ssh key.
Clone and build the ROS_dockers repo on RPi
Back on the RPi, in the home directory, clone Paul's ROS_Dockers repo: "git clone git@github.com:PaulBouchier/ROS_dockers.git". (In the future you may choose to fork my repo in github, but please don't do that yet.)
Build the image. This can take 5 - 30 minutes depending on the computer. Run docker_build
Build the ROS dockers on PC
Precondition: You have previously accessed github.com from your PC and have your keys set up to download. If you haven't done that, you need to do a procedure similar to what's described above for RPi.
clone Paul's ROS_Dockers repo: "git clone git@github.com:PaulBouchier/ROS_dockers.git". (In the future you may choose to fork my repo in github, but please don't do that yet.)
Build the image. This can take 5 - 30 minutes depending on the computer. Run docker_build
Launch ROS2 docker on RPi
In this segment we launch the ROS2 containers and verify correct operation of GUI and CLI and logins.
From a terminal on the PC, ssh into RPi. "ssh rpi4"
Run tigervncserver. It will print a command-line to invoke the vnc client with if you're running xtigervncviewer on Linux.
Run the tigervnc client on the PC as before and enter the vnc password. You will get a desktop.
Open an terminal on the desktop - this is an RPi terminal.
In the rpi4 terminal, run "dr" to launch the docker container. Note the prompt is ros2_rpi, indicating the shell is running in the container
Make a new PC terminal, ssh to RPi, and run "de" and observe you have a new docker shell, as indicated by the prompt
Use Ctrl-D to exit from both docker shells back to the RPi. Note the prompt change. Note that exiting the "dr" window first automatically exits the "de" window (because the container shuts down).
Launch ROS2 docker on PC
Launch the Ubuntu VM or Ubuntu on PC and start a terminal window
Run 'dr" to launch the docker container. Note the prompt is ros2_x86_linux, indicating the shell is running in the container
Make a new terminal. Run "de" and observe you have a new docker shell, as indicated by the prompt.
Use Ctrl-D to exit from both docker shells back to the PC. Note the prompt change. Note that exiting the "dr" window first automatically exits the "de" window (because the container shuts down).
Test ROS2 docker on RPi and PC
In the desktop (vnc or real desktop), launch a terminal. Run dr in it to launch a docker shell in a container. In the docker shell, run terminator & . Observe a terminator terminal window is launched on the desktop. Observe the prompt which shows terminator is running a docker shell. Split the resulting window horizontally into three terminals by right-click in the pane and select "Split Horizontally".
Verify that the user-id and group-id of the docker user is the same as that of the host OS. You can easily do this as follows:
Create a file in a directory shared between docker and the host OS, e.g. touch bag_files/foo
List the file in long form from the docker shell and check its owner and group: ls -als bag_files/foo
List the file in long form from the host OS shell and check its owner and group are the same
In one subwindow, run ros2 run turtlesim turtlesim_node The turtlebot simulator will appear.
In a second subwindow, run ros2 run turtlesim turtle_teleop_key. Press arrow keys to drive the turtle around.
In the third subwindow run rqt and observe the rqt window opens. Use the rqt menu to navigate "Plugins" -> "Topics" -> "Topic Monitor". Observe the active topics are listed. Click the check box against /turtle1/pose and use the arrow to expand the fields. Expand the window as needed to see the values. Observe the topic publish frequency is shown, and the x and y and theta values change as you drive the robot.
Type Ctrl-C to exit the programs in each of the three windows
Shut down the terminator window
Do Ctrl-D to exit the docker shell in the desktop terminal.
Inspect the scripts
In this segment, we review how the system hangs together
inspect what was added to .bashrc in RPi or the PC. Note the active_docker function, and the docker_build and dr/de/dsl aliases and the -keep option to dr. Look at how they cd to a platform-appropriate directory based on active_docker, and run ./run_it.sh there
inspect run-it.sh. Note the options:
The run command launches a container from an image. (start would start a pre-existing container)
--net=host puts the container on the host's network
-i -t provide an interactive terminal for login
${RM} sets whether to remove the container when it stops
--privileged gives access to /dev
-v mounts various host directories or files onto the container
--user sets the user for the shell that's launched
-w sets the working directory
${ACTIVE_DOCKER} selects the docker image (based on the .bashrc function).
Inspect the Dockerfile in ros2_rpi
FROM selects the base image that will be layered with our additions
RUN runs a program on the host as part of the image build. The current working directory is where docker build was launched. E.g. RUN apt-get install results in installing a package when the image is built.
COPY copies a file from the build directory to the image
ENTRYPOINT sets a script to run when the container is launched
CMD sets a program to run by default when the container is launched - it can be overridden inthe "docker run" command.
Review docker command cheat sheet and demo commands
Add a package to an image
In this exercise, we test adding a package to the ros2_rpi image, and when we're happy, we configure it into the my_rpi_mods Dockerfile, and build and test my_rpi_mods
On RPi, with active_docker still set to ros2_rpi, run "dr" to launch container. Find a package you want to add (apt-cache search), and install and test it.
Exit container
Run active_docker my_rpi_mods
Edit dockerfile to add your package
Run docker_build to build the image
Run dr to launch the container and test that your added package works.
Using a persistent container on RPi or PC
This technique for using docker launches a container from the ros:humble image, and persistes it, like a VM image persists. You can do manual modifications and they will persist, but BEWARE of the system prune command, which will blow away stopped containers. While this technique is comfortable for many, similar to a VM as it is, like installing in a VM it does not necessarily result in an easily reproducible image (because you make manual changes). Also, you run as root, which may have dire consequences if you typo. Also, it's a pretty minimal environment (e.g. ping is not installed, no GUI tools). OTOH it's as simple as it gets as a starting point.
Run docker run -it --net=host -v /dev:/dev --privileged ros:humble
Notice that you are root in a ros-humble docker container (shown by # prompt and ls /opt/ros)
Run apt install ros-humble-demo-nodes-py
Run source /opt/ros/humble/setup.bash
Run ros2 run demo_nodes_py talker
Notice that the ros2 talker node is running.
Type Ctrl-D to exit the container. The changes we made will persist in the container.
Run docker container list -a Observe your container is listed and has a two-word identifier. You can identify your container among others by looking at its timestamp.
Restart the container by running docker start -ai <your_container_name>
Run apt list --installed | grep demo Observe that the demo package you installed is still there - it persisted in the container.
In a new window on RPi, run docker exec -it <your_container_name> bash Observe that you have a second bash shell in the container (works like the de alias)
Do Ctrl-D in the window you ran docker start in. Observe the container stops and both docker shells exit back to the RPi shell.
You can re-launch the container as much as you want and it retains the changes made in it.