- K8sDriver.with_localhost_seccomp(profile): sandbox pods run under the STRICT allowlist instead of the runtime default. Proven live on kind: the harness installs the profile onto the node, a pod runs ordinary work as uid 10001, and unshare is kernel-denied inside the pod — the same probe the Docker suite uses, now passing on both targets - Helm: sandbox.seccomp=localhost renders a DaemonSet that installs the chart-shipped profile into /var/lib/kubelet/seccomp on every node (ConfigMap + hostPath); ci/check-helm.sh enforces the chart copy stays byte-identical to images/seccomp/agent-profile.json and asserts the hardened render (DaemonSet + profile + HPA) - server HPA (autoscaling/v2, CPU target) behind server.autoscaling.enabled - SandboxManager.warm(n): a background warmer keeps n pre-provisioned sandboxes ready so an agent's first exec skips container startup; unhealthy pool entries are discarded, reuse never drains the pool, shutdown destroys assigned AND pooled. [sandbox] warm_pool config (default 0). Real-Docker test: prefill -> assign -> refill -> reuse -> clean shutdown 160 Rust tests + 4 live kind tests. Co-Authored-By: Claude Fable 5 <[email protected]>
81 lines
2.4 KiB
Rust
81 lines
2.4 KiB
Rust
//! Warm sandbox pool: pre-provisioned containers absorb the first-exec
|
|
//! latency. Real Docker — the pool fills in the background, an exec
|
|
//! takes a sandbox from it, and the warmer restores the target.
|
|
|
|
use std::process::Command;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use tc_domain::AgentId;
|
|
use tc_runtime::SandboxManager;
|
|
use tc_sandbox::DockerDriver;
|
|
|
|
const IMAGE: &str = "teamclaw/agent-base:dev";
|
|
|
|
fn ensure_image() {
|
|
let exists = Command::new("docker")
|
|
.args(["image", "inspect", IMAGE])
|
|
.output()
|
|
.expect("docker available")
|
|
.status
|
|
.success();
|
|
if !exists {
|
|
let root = env!("CARGO_MANIFEST_DIR");
|
|
let status = Command::new("docker")
|
|
.args([
|
|
"build",
|
|
"-t",
|
|
IMAGE,
|
|
"-f",
|
|
&format!("{root}/../../images/agent-base/Dockerfile"),
|
|
&format!("{root}/../../images/agent-base"),
|
|
])
|
|
.status()
|
|
.expect("docker build runs");
|
|
assert!(status.success());
|
|
}
|
|
}
|
|
|
|
async fn pool_reaches(manager: &SandboxManager, target: usize) {
|
|
for _ in 0..120 {
|
|
if manager.pool_size().await == target {
|
|
return;
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(250)).await;
|
|
}
|
|
panic!(
|
|
"pool never reached {target} (now {})",
|
|
manager.pool_size().await
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn the_pool_prefills_assigns_and_refills() {
|
|
ensure_image();
|
|
let driver: Arc<dyn tc_sandbox::SandboxDriver> =
|
|
Arc::new(DockerDriver::connect().expect("docker reachable"));
|
|
let manager = Arc::new(SandboxManager::new(driver, IMAGE)).warm(2);
|
|
|
|
// The warmer fills the pool without any exec happening.
|
|
pool_reaches(&manager, 2).await;
|
|
|
|
// An exec is served from the pool — and works.
|
|
let agent = AgentId::new();
|
|
let result = manager.exec(agent, "id -u").await.unwrap();
|
|
assert_eq!(result.stdout.trim(), "10001");
|
|
|
|
// The warmer restores the target while the agent keeps its sandbox.
|
|
pool_reaches(&manager, 2).await;
|
|
let again = manager.exec(agent, "echo still-mine").await.unwrap();
|
|
assert_eq!(again.stdout.trim(), "still-mine");
|
|
assert_eq!(
|
|
manager.pool_size().await,
|
|
2,
|
|
"reuse must not drain the pool"
|
|
);
|
|
|
|
// Shutdown destroys assigned AND pooled sandboxes.
|
|
manager.shutdown().await;
|
|
assert_eq!(manager.pool_size().await, 0);
|
|
}
|