Skip to content

Commit

Permalink
Move container-hotplug from wrapper of docker to wrapper of runc
Browse files Browse the repository at this point in the history
This means:
* It can now be used for other container managers, e.g. podman or k8s
* We now only start the container when existing devices are attached
* We no longer need to interface with docker API, and no longer have to
  worry about lifecycle of the pod.

The options is changed from using CLI to use annotations.
  • Loading branch information
nbdd0121 committed Apr 23, 2024
1 parent 1c49a31 commit 0323def
Show file tree
Hide file tree
Showing 14 changed files with 364 additions and 1,302 deletions.
688 changes: 13 additions & 675 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,18 @@ anyhow = { version = "1", features = ["backtrace"] }
log = "0.4"
env_logger = "0.11"
clap = { version = "4", features = ["derive"] }
bytes = "1"
thiserror = "1"
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1"
tokio-util = { version = "0.7", features = ["full"] }
async-stream = "0.3"
udev = "0.8"
bollard = "0.16"
rustix = { version = "0.38", features = ["fs", "stdio", "termios", "process", "thread"] }
rustix = { version = "0.38", features = ["fs", "stdio", "process", "thread", "runtime", "pipe"] }
bitflags = "2"
once_cell = "1"
humantime = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

inotify = "0.10"
aya = { git = "https://github.com/aya-rs/aya.git" }

[build-dependencies]
Expand Down
84 changes: 67 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# container-hotplug

Hot-plug (and unplug) devices into a Docker container as they are (un)plugged.
Hot-plug (and unplug) devices into a container as they are (un)plugged.

## Description

Expand All @@ -18,32 +18,82 @@ It then interfaces directly with the container's cgroup to grant it access to th
To limit the devices the container can access, a _root device_ is specified.
The container will receive access to any device descending from the root device.
This is particularly useful if the root device is set to a USB hub.
However, since hubs are rarely interesting, it can be specified as "the parent of device X",
The hub can be specified directly, or it can be specified as "the parent of device X",
e.g., we can giving a container access to all devices connected to the same hub as an Arduino board.

Another concern is providing a container with well known paths for the devices.
On bare-metal systems this would usually be achieved with a `SYMLINK` directive in a udev rule.
This program tries to provide a similar functionality for containers, allowing you to specify symlinks for certain devices.

This tool supports both cgroup v1 and v2.
## Usage

## Example
This tool wraps `runc` command, with the additional hotplug feature. Therefore, it can be used as a drop in replace for
many container managers/orchestrators that makes use of runc as runtime. You need to ensure `runc` is available in your `PATH`
so `container-hotplug` can find it.

Give a container access to all devices connected to the same hub as a CW310 board.
It supports two annotations, `org.lowrisc.hotplug` and `org.lowrisc.symlinks`.

1. Find the USB VID and PID of the device using `lsusb`, for a CW310 that is `2b3e:c310`
2. Run (as root) the container using `container-hotplug`:
For Docker, you can specify an alternative runtime by [changing /etc/docker/daemon.json](https://docs.docker.com/engine/alternative-runtimes/#youki):
```json
{
"runtimes": {
"youki": {
"path": "/path/to/container-hotplug/binary"
}
}
}
```
container-hotplug run \
-d parent-of:usb:2b3e:c310 \
-- -it ubuntu:22.04 bash
and use it by `--runtime hotplug` and appropriate annotation, e.g.
```bash
sudo docker run --runtime hotplug -it --annotation org.lowrisc.hotplug=parent-of:usb:2b2e:c310 ubuntu:latest
```

If you want symlinks to the `tty` devices created by interfaces 1 and 3 of the CW310, run:
For podman, you can specify the path directly, by:
```bash
sudo podman run --runtime /path/to/container-hotplug/binary -it --annotation org.lowrisc.hotplug=parent-of:usb:2b2e:c310 ubuntu:latest
```
container-hotplug run \
-d parent-of:usb:2b3e:c310 \
-l usb:2b3e:c310:1=/dev/ttyACM_CW310_0 \
-l usb:2b3e:c310:3=/dev/ttyACM_CW310_1 \
-- -it ubuntu:22.04 bash
```

For containerd (e.g. when using kubernetes), you can `/etc/containerd/config.toml` to add:
```toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.hotplug]
runtime_type = "io.containerd.runc.v2"
pod_annotations = ["org.lowrisc.hotplug.*"]

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.hotplug.options]
SystemdCgroup = true
BinaryName = "/path/to/container-hotplug/binary"
```
this would allow you to use `hotplug` as handler in k8s, e.g. add a runtime class with
```yaml
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: hotplug
handler: hotplug
```
and use it in pod with
```yaml
apiVersion: v1
kind: Pod
metadata:
name: ubuntu
annotations:
org.lowrisc.hotplug.device: usb:0bda:5634
spec:
runtimeClassName: hotplug
containers:
- name: ubuntu
image: ubuntu:latest
stdin: true
tty: true
```
If you want symlinks to the `tty` devices created by interfaces 1 and 3 of the CW310, add
```
--annotation org.lowrisc.hotplug.symlinks=usb:2b3e:c310:1=/dev/ttyACM_CW310_0,usb:2b3e:c310:3=/dev/ttyACM_CW310_1
```
to docker/podman command line or
```
org.lowrisc.hotplug.symlinks: usb:2b3e:c310:1=/dev/ttyACM_CW310_0,usb:2b3e:c310:3=/dev/ttyACM_CW310_1
```
to k8s config.
43 changes: 0 additions & 43 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,48 +1,5 @@
pub mod device;
pub mod symlink;

use clap::{Parser, Subcommand};

pub use device::DeviceRef;
pub use symlink::Symlink;

#[derive(Parser)]
pub struct Args {
#[command(subcommand)]
pub action: Action,
}

#[derive(Subcommand)]
#[command(max_term_width = 180)]
pub enum Action {
/// Wraps a call to `docker run` to allow hot-plugging devices into a
/// container as they are plugged
Run(Run),
}

#[derive(clap::Args)]
pub struct Run {
#[arg(short = 'd', long, id = "DEVICE")]
/// Root hotplug device: [[parent-of:]*]<PREFIX>:<DEVICE> {n}
/// PREFIX can be: {n}
/// - usb: A USB device identified as <VID>[:<PID>[:<SERIAL>]] {n}
/// - syspath: A directory path in /sys/** {n}
/// - devnode: A device path in /dev/** {n}
/// e.g., parent-of:usb:2b3e:c310
pub root_device: DeviceRef,

#[arg(short = 'l', long, id = "SYMLINK")]
/// Create a symlink for a device: <PREFIX>:<DEVICE>=<PATH> {n}
/// PREFIX can be: {n}
/// - usb: A USB device identified as <VID>:<PID>:<INTERFACE> {n}
/// e.g., usb:2b3e:c310:1=/dev/ttyACM_CW310_0
pub symlink: Vec<Symlink>,

#[arg(short = 'u', long, default_value = "5", id = "CODE")]
/// Exit code to return when the root device is unplugged
pub root_unplugged_exit_code: u8,

#[arg(trailing_var_arg = true, id = "ARGS")]
/// Arguments to pass to `docker run`
pub docker_args: Vec<String>,
}
Loading

0 comments on commit 0323def

Please sign in to comment.