What is RunC ?
RunC is a CLI tool for spawning and running containers according to the OCI specification. The Open Container Initiative (OCI) is a standardized format for creating a container. It defines a container as a pair of rootfs folder & configuration file in JSON format.
Setup in Buildroot/Lithosphere
-
Make sure kernel has all the futures needed for containerization. The easiest way to enable them is to use raspberrypi3_wpe_ml_container_defconfig.
-
Enable containers support in Thunder
Thunder -> Extensions -> Process Containers
-
Select RunC as the backend engine for containers.
Thunder -> Extensions -> Process Containers -> Containers Backend -> runc
-
(optional) Enable containers plugin for info about running containers
Thunder -> Plugins -> ProcessContainers
Setting Up runc Container
For demo purposes, we will use the OCDM plugin. To run a containerized ThunderNanoService you will need to create rootfs with all of the dependencies of the nanoservice and ThunderPlugin as executable. For this tutorial rootfs.tar generated by buildroot/lithosphere/yocto will be used as reference roots.
-
On target platform, create
/usr/share/Thunder/<Nanoservice Name>/Container
. This will be a directory containing all files defining a container.mkdir <SD_CARD>/usr/share/Thunder/OCDM/Container
-
Extract rootfs.tar generated eg. by buildroot into
/usr/share/Thunder/<Nanoservice Name>/Container/rootfs
. -
Create a reference configuration. You can either use a reference config file presented later in the instructions or use runc cmdline tool to do it for you, by navigating to
/usr/share/Thunder/<Nanoservice Name>/Container
and callingrunc spec
. Be careful - if you do it on the host platform, the "platform" section in config will have to be modified to match the target platform.
Adjusting Configuration for Thunder
Default config obtained by runc spec
gives you a nice starting point, that you can tailor as you wish. However, there are a few things that need to be set up right to work with Thunder:
- The most important thing is to mount
/tmp/communicator
pipe inside the container to allow for communication between ThunderPlugin inside a container namespace and Thunder. OCDM plugin that is used in the example also creates another pipe inside /tmp folder, so for tutorial whole /tmp folder is mounted"mounts": [ { "source": "/tmp", "destination": "/tmp", "options": ["rw", "bind"] }, [...] ]
- Set terminal to false
"process": { "terminal": false, [...] }
- By design, all of the configurations should be passed to Nanoservice by arguments/runtime. However, at the moment, there are some env variables that need to be set. This is considered a temporal solution and won't be needed in future releases.
"env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "TERM=xterm", "COMMUNICATOR_CONNECTOR=$COMMUNICATOR_CONNECTOR", "MESSAGE_DISPATCHER_CONFIG=$MESSAGE_DISPATCHER_CONFIG" ],
- Launched process will always be a Nanoservice, so container's args will be overided. You don't acctually need to do anything here, however it's good to have in mind that this option will have no effect.
"args": [ "sh", ]
is used so we will mount the whole tmp For now, you also need to set up some env variables as presented in the config, however, in future versions, this will no longer be needed.
Configure plugin to run as a containerized process
In plugin configuration (eg. /etc/Thunder/plugin/OCDM.json
for OCDM) change
"mode": Local
to
"mode": "Container"
If everything works fine, you should see OCDM working just lie an ordinary OOP plugin
Example configuration
This is an example of config generated by runc spec
with the following changes:
1. /tmp
folder mounted inside the container
2. Env variables set inside the container
3. terminal was set to false
{
"ociVersion": "1.0.2-dev",
"process": {
"terminal": false,
"user": {
"uid": 0,
"gid": 0
},
"args": [
"sh"
],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm",
"TRACE_FILENAME=/tmp/tracebuffer",
"TRACE_DOORBELL=/tmp/tracebuffer.doorbell",
"COMMUNICATOR_CONNECTOR=/tmp/communicator"
],
"cwd": "/",
"capabilities": {
"bounding": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"effective": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"permitted": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"ambient": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
]
},
"rlimits": [
{
"type": "RLIMIT_NOFILE",
"hard": 1024,
"soft": 1024
}
],
"noNewPrivileges": true
},
"root": {
"path": "rootfs",
"readonly": true
},
"hostname": "runc",
"mounts": [
{
"source": "/tmp",
"destination": "/tmp",
"options": ["rw", "bind"]
},
{
"destination": "/proc",
"type": "proc",
"source": "proc"
},
{
"destination": "/dev",
"type": "tmpfs",
"source": "tmpfs",
"options": [
"nosuid",
"strictatime",
"mode=755",
"size=65536k"
]
},
{
"destination": "/dev/pts",
"type": "devpts",
"source": "devpts",
"options": [
"nosuid",
"noexec",
"newinstance",
"ptmxmode=0666",
"mode=0620",
"gid=5"
]
},
{
"destination": "/dev/shm",
"type": "tmpfs",
"source": "shm",
"options": [
"nosuid",
"noexec",
"nodev",
"mode=1777",
"size=65536k"
]
},
{
"destination": "/dev/mqueue",
"type": "mqueue",
"source": "mqueue",
"options": [
"nosuid",
"noexec",
"nodev"
]
},
{
"destination": "/sys",
"type": "sysfs",
"source": "sysfs",
"options": [
"nosuid",
"noexec",
"nodev",
"ro"
]
},
{
"destination": "/sys/fs/cgroup",
"type": "cgroup",
"source": "cgroup",
"options": [
"nosuid",
"noexec",
"nodev",
"relatime",
"ro"
]
}
],
"linux": {
"resources": {
"devices": [
{
"allow": false,
"access": "rwm"
}
]
},
"namespaces": [
{
"type": "pid"
},
{
"type": "network"
},
{
"type": "ipc"
},
{
"type": "uts"
},
{
"type": "mount"
}
],
"maskedPaths": [
"/proc/acpi",
"/proc/asound",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/sys/firmware",
"/proc/scsi"
],
"readonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
}
}