It’s the season for updates. The last few weeks have ushered in ROS 1 Noetic and ROS 2 Foxy, both of which target the recently released Ubuntu 20.04 Focal Fossa. As always, new releases come with trepidation: how can I install new software and test compatibility, yet keep my own environment stable until I know I’m ready to upgrade? This is one of the many good reasons to dive into containers
In this blog post we’ll create a base LXD profile with the ROS software repositories and full graphical capabilities enabled. Launch containers to meet your robotics needs: everything from software development and system testing through robot operations can be covered within containers.
All you need to get started is a Linux workstation with LXD installed. The installation of LXD is not covered here as there are a number of great instructions online. See the getting started guide at linuxcontainers.org for more information.
We’ll cover hereafter the three basic steps to getting set up:
- Creating a LXD container profile
- Launching and connecting to the container
- Installing ROS
Create a profile
All LXD containers have a defined profile. A default profile was created when LXD was first set installed, but we will create a ROS specific profile. This will support running ROS (either version 1 or 2) on an Ubuntu image.
The profile contains four specific configuration features:
- ROS software repositories
- Run X apps within the container
- A disk storage device
In order to set up the profile properly, we must first collect your workstation’s network adapter, and the user/group ID for your account. Find your network adapter using the
ip addr show command:
~:$ ip addr show 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: wlp2s0: mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether a8:00:0b:c0:88:e7 brd ff:ff:ff:ff:ff:ff 3: enx001a98a552d4: mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 00:1a:98:a5:52:d4 brd ff:ff:ff:ff:ff:ff inet 192.168.1.100/24 brd 192.168.1.255 scope global noprefixroute enx001a98a552d4 valid_lft forever preferred_lft forever
The above example shows three network adapters: loopback, wireless and ethernet (respectively). Select the adapter to use for the container; for this case we will use the wired adapter
To find the id of your non-root account, use the the
uid=1001(sid) gid=1001(sid) groups=1001(sid),4(adm),24(cdrom),27(sudo), ...
In the example above my user id “sid” has a uid and gid of 1001.
Create the ROS Profile
Armed with these two key facts, we can create and edit the LXD profile for our ROS containers:
lxc profile create ros
lxc profile edit ros
This brings up a default profile template for editing within vi. Update the file as follows, then save and exit vi:
config: environment.DISPLAY: :0 raw.idmap: both [your group id] 1000 user.user-data: | #cloud-config runcmd: - "apt-key adv --fetch-keys 'https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc'" - "apt-add-repository 'http://packages.ros.org/ros/ubuntu'" - "apt-add-repository 'http://packages.ros.org/ros2/ubuntu'" description: ROS devices: X0: path: /tmp/.X11-unix/X0 source: /tmp/.X11-unix/X0 type: disk eth0: name: eth0 nictype: macvlan parent: [your network adapter] type: nic root: path: / pool: default type: disk name: ros
Let’s break down the configuration file and look at each part individually.
Configure the environment
The following line sets the DISPLAY environment variable required by X. The display within the container is always mapped to ”’:0”’.
Our linux containers will run under the security context of the current user, but all work within the Ubuntu containers will be done under the default
ubuntu user account. The
raw.imap setting maps our workstation’s user and group ID (1001 in this example) into the container’s user and group ID (always 1000 for the default user):
raw.idmap: both 1001 1000
Configure ROS repositories
The ROS software repositories can be added to the container every time a new container is launched. The simplest way to achieve this is to use cloud-init, and add a
runcmd to the user-data section of the profile. Each of these commands will be executed whenever a new container is initialized using this profile.
The apt-key command pulls the ROS distribution signing key from github, while the two add-repository commands add the ROS 1 and ROS 2 software repositories.
user.user-data: | #cloud-config runcmd: - "apt-key adv --fetch-keys 'https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc'" - "apt-add-repository 'http://packages.ros.org/ros/ubuntu'" - "apt-add-repository 'http://packages.ros.org/ros2/ubuntu'"
Adding the X0 device to the profile allows X data to flow between the container (“path”) and the host (“source”). If you have multiple graphics cards check the contents of
/tmp/.X11-unix to make sure you’re mapping to the correct source; the source should mirror the
$DISPLAY environment variable from your host.
devices: X0: path: /tmp/.X11-unix/X0 source: /tmp/.X11-unix/X0 type: disk
If you have a separate graphics card with a discrete GPU, you may also find it necessary to add the GPU as a device:
To learn more about running graphical apps in LXD containers, including some caveats when working with an NVidia GPU, take a look at this blog post by Simos Xenitellis.
Multiple options exist for networking. Although a container can use LXD’s built in address translation or a bridge device, our profile will use the macvlan network driver. Macvlan creates a simple bridge between the parent network adapter and the container so the container receives its own IP address on the host network. With this setup no additional configuration (e.g., port forwarding) is required for either ROS 1 or ROS 2.
The network interface (nic) device configured here uses macvlan to connect the parent network adapter
enx001a98a552d4 to the container’s eth0 interface:
Take a look at this post if you need more information about LXD networking with macvlan.
A disk image must be connected to the container. This disk device simply uses the lxd default data pool:
Launch a container
lxc launch command is used to launch a container for the first time. Use the following command to create and run an Ubuntu 20.04 container named “ros-foxy”:
lxc launch -p ros ubuntu:20.04 rosfoxy
Once the container is running, logging in is achieved by simply executing a shell within the container:
lxc exec rosfoxy -- bash
This connects to a shell on the container under the root userid; however, our configuration uses the ubuntu user account. In order to connect to a shell as the ubuntu user, execute the su command within the container:
lxc exec ros -- su --login ubuntu
This tends to be a bit cumbersome to type for every connection to the container. LXD aliases provide an easy way to simplify the command, and also make it more generally applicable. Shorten the command and make it more robust by creating an LXD alias using this command:
lxc alias add ubuntu 'exec @ARGS@ --user 1000 --group 1000 --env HOME=/home/ubuntu/ -- /bin/bash --login'
For more information about the options used with this alias, see this blog post from Simos Xenitellis.
Now connecting to an Ubuntu container with the proper context is as simple as the following command:
lxc ubuntu rosfoxy
Since the ROS repositories have been set up, installation is as simple as an apt-install command for the correct software bundle:
sudo apt install ros-foxy-ros-desktop
Since this container will always run ROS, we can source the ROS environment upon every login by adding it to our .bashrc startup script.
echo "source /opt/ros/foxy/setup.bash" >> ~/.bashrc
Most ROS commands support context-sensitive tab completion. Although not required, installing
python3-argcomplete will make typing commands much easier:
sudo apt install python3-argcomplete
Now that the installation is complete, try it out by running
rqt or a similar graphical application.
Using this ROS profile, building environments to work with different releases becomes trivial:
lxc launch -p ros ubuntu:20.04 rosnoetic
lxc ubuntu rosnoetic
sudo apt install ros-noetic-desktop-full
LXD provides a number of handy commands for working with containers. For instance we can clone a container by simply using the lxc copy command:
lxc copy rosfoxy rosfoxy-2
When work with the container is complete, simply remove it:
lxc delete rosfoxy-2
Share files between the host and the container, map USB devices–think robotics hardware–to the container with additional LXD configurations.
The possibilities for containers are only limited by your imagination. For some ideas on how to use LXD containers for ROS development, check out this blog post by Ted Kern. We’d appreciate any comments you have on your experiences using LXD containers with ROS!