Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move container-hotplug from wrapper of docker to wrapper of runc #6

Merged
merged 7 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
702 changes: 5 additions & 697 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ anyhow = { version = "1", features = ["backtrace"] }
log = "0.4"
env_logger = "0.11"
clap = { version = "4", features = ["derive"] }
clap-verbosity-flag = "2"
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"
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` with the additional hotplug feature, therefore it can be used as a drop in replace for
many container managers/orchestrators such as Docker, Podman, and Kubernetes. 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.device` and `org.lowrisc.hotplug.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": {
"hotplug": {
"path": "/path/to/container-hotplug/binary"
}
}
}
```
container-hotplug run \
-d parent-of:usb:2b3e:c310 \
-- -it ubuntu:22.04 bash
and use it with the `--runtime hotplug` flag and appropriate annotation, e.g.
```bash
sudo docker run --runtime hotplug -it --annotation org.lowrisc.hotplug.device=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.device=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 edit `/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 a pod with
```yaml
apiVersion: v1
kind: Pod
metadata:
name: ubuntu
annotations:
org.lowrisc.hotplug.device: parent-of: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.
85 changes: 0 additions & 85 deletions src/cli/logfmt.rs

This file was deleted.

64 changes: 0 additions & 64 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,69 +1,5 @@
pub mod device;
pub mod logfmt;
pub mod symlink;

use clap::{Parser, Subcommand};
use clap_verbosity_flag::{InfoLevel, Verbosity};

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

#[derive(Parser)]
pub struct Args {
#[command(flatten)]
pub verbosity: Verbosity<InfoLevel>,

#[arg(
short = 'L',
long,
default_value = "+l-pmt",
id = "FORMAT",
global = true
)]
/// Log mesage format: [[+-][ltmp]*]* {n}
/// +/-: enable/disable {n}
/// l: level {n}
/// t: timestamp {n}
/// m/p: module name/path {n}
///
pub log_format: LogFormat,

#[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