Skip to content

Commit

Permalink
Add support for Docker runtime (#96)
Browse files Browse the repository at this point in the history
* add support for docker runtime
  • Loading branch information
infiniteregrets authored Jun 3, 2022
1 parent 26c0dc7 commit e780e2e
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 59 deletions.
11 changes: 10 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
components: rustfmt
- uses: Swatinem/rust-cache@v1
- run: sudo apt install -y libpcap-dev cmake
- run: cd mirrord-agent && cargo build
- run: sudo PATH=/home/runner/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin:/usr/bin:/usr/sbin /home/runner/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo test -p mirrord-agent

test_agent_image:
Expand Down Expand Up @@ -102,4 +103,12 @@ jobs:
- name: build mirrord
run: cargo +nightly build --manifest-path=./Cargo.toml
- name: run node tests
run: cargo test --manifest-path ./tests/Cargo.toml -- --test-threads 1
run: cargo test --package tests --lib -- tests --nocapture --test-threads 1
- name: switch minikube runtime
run: minikube delete && minikube start --container-runtime=docker && minikube image load test:latest
- name: setup nginx
run: kubectl apply -f tests/app.yaml
- name: wait for nginx pod
run: kubectl wait --for=condition=ready pod -l app=nginx --timeout=20s
- name: test with docker runtime
run: cargo test --package tests --lib -- tests --nocapture --ignored
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
## [Unreleased]
### Added
- Prompt user to update if their version is outdated in the VS Code extension or CLI.
- Set `MIRRORD_CHECK_VERSION` to false to make E2E tests not read update messages.
- Add support for docker runtime, closes [#95](https://github.com/metalbear-co/mirrord/issues/95).
- Add a keep-alive to keep the agent-pod from exiting, closes [#63](https://github.com/metalbear-co/mirrord/issues/63)

## 2.0.0
Expand Down
100 changes: 100 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions mirrord-agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ tracing.workspace = true
tracing-subscriber.workspace = true
tokio-stream.workspace = true
num-traits = "0.2.14"
bollard = "0.12.0"

[dev-dependencies]
test_bin = "0.4.0"
4 changes: 4 additions & 0 deletions mirrord-agent/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ pub struct Args {
#[clap(short, long)]
pub container_id: Option<String>,

/// Container runtime to use
#[clap(short = 'r', long)]
pub container_runtime: Option<String>,

/// Port to use for communication
#[clap(short = 'l', long, default_value_t = 61337)]
pub communicate_port: u16,
Expand Down
1 change: 1 addition & 0 deletions mirrord-agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ async fn start() -> Result<()> {
packet_command_rx,
args.interface.clone(),
args.container_id.clone(),
args.container_runtime.clone(),
));
loop {
select! {
Expand Down
81 changes: 39 additions & 42 deletions mirrord-agent/src/runtime.rs
Original file line number Diff line number Diff line change
@@ -1,68 +1,65 @@
use std::{
fs::File,
os::unix::io::{IntoRawFd, RawFd},
path::PathBuf,
};

use anyhow::{anyhow, Result};
use bollard::{container::InspectContainerOptions, Docker};
use containerd_client::{
connect,
services::v1::{containers_client::ContainersClient, GetContainerRequest},
services::v1::{tasks_client::TasksClient, GetRequest},
tonic::Request,
with_namespace,
};
use nix::sched::setns;
use serde::{Deserialize, Serialize};

const CONTAINERD_SOCK_PATH: &str = "/run/containerd/containerd.sock";
const DEFAULT_CONTAINERD_NAMESPACE: &str = "k8s.io";

#[derive(Serialize, Deserialize, Debug)]
struct Namespace {
#[serde(rename = "type")]
ns_type: String,
path: Option<String>,
pub async fn get_container_pid(container_id: &str, container_runtime: &str) -> Result<u64> {
match container_runtime {
"docker" => get_docker_container_pid(container_id.to_string()).await,
"containerd" => get_containerd_container_pid(container_id.to_string()).await,
_ => Err(anyhow!("Unsupported runtime: {}", container_runtime)),
}
}

#[derive(Serialize, Deserialize, Debug)]
struct LinuxInfo {
namespaces: Vec<Namespace>,
async fn get_docker_container_pid(container_id: String) -> Result<u64> {
let client = Docker::connect_with_local_defaults()?;
let inspect_options = Some(InspectContainerOptions { size: false });
let inspect_response = client
.inspect_container(&container_id, inspect_options)
.await?;

let pid = inspect_response
.state
.and_then(|state| state.pid)
.and_then(|pid| if pid > 0 { Some(pid as u64) } else { None })
.ok_or_else(|| anyhow!("No pid found"))?;
Ok(pid)
}

#[derive(Serialize, Deserialize, Debug)]
struct Spec {
linux: LinuxInfo,
async fn get_containerd_container_pid(container_id: String) -> Result<u64> {
let channel = connect(CONTAINERD_SOCK_PATH).await?;
let mut client = TasksClient::new(channel);
let request = GetRequest {
container_id,
..Default::default()
};
let request = with_namespace!(request, DEFAULT_CONTAINERD_NAMESPACE);
let response = client.get(request).await?;
let pid = response
.into_inner()
.process
.ok_or_else(|| anyhow!("No pid found"))?
.pid;

Ok(pid as u64)
}

pub fn set_namespace(ns_path: &str) -> Result<()> {
pub fn set_namespace(ns_path: PathBuf) -> Result<()> {
let fd: RawFd = File::open(ns_path)?.into_raw_fd();
setns(fd, nix::sched::CloneFlags::CLONE_NEWNET)?;
Ok(())
}

pub async fn get_container_namespace(container_id: String) -> Result<String> {
let channel = connect(CONTAINERD_SOCK_PATH).await?;
let mut client = ContainersClient::new(channel);
let request = GetContainerRequest { id: container_id };
let request = with_namespace!(request, DEFAULT_CONTAINERD_NAMESPACE);
let resp = client.get(request).await?;
let resp = resp.into_inner();
let container = resp
.container
.ok_or_else(|| anyhow!("container not found"))?;
let spec: Spec = serde_json::from_slice(
&container
.spec
.ok_or_else(|| anyhow!("invalid data from containerd"))?
.value,
)?;
let ns_path = spec
.linux
.namespaces
.iter()
.find(|ns| ns.ns_type == "network")
.ok_or_else(|| anyhow!("network namespace not found"))?
.path
.as_ref()
.ok_or_else(|| anyhow!("no network namespace path"))?;
Ok(ns_path.to_owned())
}
28 changes: 23 additions & 5 deletions mirrord-agent/src/sniffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{
collections::{HashMap, HashSet},
hash::{Hash, Hasher},
net::{IpAddr, Ipv4Addr},
path::PathBuf,
};

use anyhow::{anyhow, Result};
Expand All @@ -22,13 +23,15 @@ use tokio::{
use tracing::{debug, error};

use crate::{
runtime::{get_container_namespace, set_namespace},
runtime::{get_container_pid, set_namespace},
util::IndexAllocator,
};

const DUMMY_BPF: &str =
"tcp dst port 1 and tcp src port 1 and dst host 8.1.2.3 and src host 8.1.2.3";

const DEFAULT_RUNTIME: &str = "containerd";

type ConnectionID = u16;

#[derive(Debug)]
Expand Down Expand Up @@ -236,13 +239,28 @@ pub async fn packet_worker(
mut rx: Receiver<SnifferCommand>,
interface: String,
container_id: Option<String>,
container_runtime: Option<String>,
) -> Result<()> {
debug!("setting namespace");
if let Some(container_id) = container_id {
let namespace = get_container_namespace(container_id).await?;
debug!("Found namespace to attach to {:?}", &namespace);
set_namespace(&namespace)?;

let pid = match (container_id, container_runtime) {
(Some(container_id), Some(container_runtime)) => {
get_container_pid(&container_id, &container_runtime)
.await
.ok()
}
(Some(container_id), None) => get_container_pid(&container_id, DEFAULT_RUNTIME).await.ok(),
(None, Some(_)) => return Err(anyhow!("Container ID not specified")),
_ => None,
};

if let Some(pid) = pid {
let namespace = PathBuf::from("/proc")
.join(PathBuf::from(pid.to_string()))
.join(PathBuf::from("ns/net"));
set_namespace(namespace).unwrap();
}

debug!("preparing sniffer");
let sniffer = prepare_sniffer(interface)?;
debug!("done prepare sniffer");
Expand Down
Loading

0 comments on commit e780e2e

Please sign in to comment.