forked from wrenn/wrenn
feat: rewrite envd guest agent in Rust (envd-rs)
Complete Rust rewrite of the Go envd guest daemon that runs as PID 1 inside Firecracker microVMs. Feature-complete across all 8 phases: - Health, metrics, and env var endpoints - Crypto (SHA-256/512, HMAC), auth (secure token, signing), init/snapshot - Connect RPC via connectrpc + buffa (process + filesystem services) - File transfer (GET/POST /files) with gzip, multipart, chown, ENOSPC - Port subsystem (/proc/net/tcp scanner, socat forwarder) - Cgroup2 manager with noop fallback - Snapshot/restore lifecycle (conntracker, port subsystem stop/restart) - SIGTERM graceful shutdown, --cmd initial process spawn - MMDS metadata polling for Firecracker mode 42 source files, ~4200 LOC, 4.1MB stripped release binary. Makefile updated: build-envd now targets Rust (musl static), build-envd-go preserved for Go builds.
This commit is contained in:
73
envd-rs/src/host/metrics.rs
Normal file
73
envd-rs/src/host/metrics.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use std::ffi::CString;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Metrics {
|
||||
pub ts: i64,
|
||||
pub cpu_count: u32,
|
||||
pub cpu_used_pct: f32,
|
||||
pub mem_total_mib: u64,
|
||||
pub mem_used_mib: u64,
|
||||
pub mem_total: u64,
|
||||
pub mem_used: u64,
|
||||
pub disk_used: u64,
|
||||
pub disk_total: u64,
|
||||
}
|
||||
|
||||
pub fn get_metrics() -> Result<Metrics, String> {
|
||||
use sysinfo::System;
|
||||
|
||||
let mut sys = System::new();
|
||||
sys.refresh_memory();
|
||||
sys.refresh_cpu_all();
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
sys.refresh_cpu_all();
|
||||
|
||||
let cpu_count = sys.cpus().len() as u32;
|
||||
let cpu_used_pct = sys.global_cpu_usage();
|
||||
let cpu_used_pct_rounded = if cpu_used_pct > 0.0 {
|
||||
(cpu_used_pct * 100.0).round() / 100.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let mem_total = sys.total_memory();
|
||||
let mem_used = sys.used_memory();
|
||||
|
||||
let (disk_total, disk_used) = disk_stats("/")?;
|
||||
|
||||
let ts = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
|
||||
Ok(Metrics {
|
||||
ts,
|
||||
cpu_count,
|
||||
cpu_used_pct: cpu_used_pct_rounded,
|
||||
mem_total_mib: mem_total / 1024 / 1024,
|
||||
mem_used_mib: mem_used / 1024 / 1024,
|
||||
mem_total,
|
||||
mem_used,
|
||||
disk_used,
|
||||
disk_total,
|
||||
})
|
||||
}
|
||||
|
||||
fn disk_stats(path: &str) -> Result<(u64, u64), String> {
|
||||
let c_path = CString::new(path).unwrap();
|
||||
let mut stat: libc::statfs = unsafe { std::mem::zeroed() };
|
||||
let ret = unsafe { libc::statfs(c_path.as_ptr(), &mut stat) };
|
||||
if ret != 0 {
|
||||
return Err(format!("statfs failed: {}", std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
let block = stat.f_bsize as u64;
|
||||
let total = stat.f_blocks * block;
|
||||
let available = stat.f_bavail * block;
|
||||
|
||||
Ok((total, total - available))
|
||||
}
|
||||
113
envd-rs/src/host/mmds.rs
Normal file
113
envd-rs/src/host/mmds.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use serde::Deserialize;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::config::{MMDS_ADDRESS, MMDS_POLL_INTERVAL, MMDS_TOKEN_EXPIRATION_SECS, WRENN_RUN_DIR};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct MMDSOpts {
|
||||
#[serde(rename = "instanceID")]
|
||||
pub sandbox_id: String,
|
||||
#[serde(rename = "envID")]
|
||||
pub template_id: String,
|
||||
#[serde(rename = "address")]
|
||||
pub logs_collector_address: String,
|
||||
#[serde(rename = "accessTokenHash", default)]
|
||||
pub access_token_hash: String,
|
||||
}
|
||||
|
||||
async fn get_mmds_token(client: &reqwest::Client) -> Result<String, String> {
|
||||
let resp = client
|
||||
.put(format!("http://{MMDS_ADDRESS}/latest/api/token"))
|
||||
.header(
|
||||
"X-metadata-token-ttl-seconds",
|
||||
MMDS_TOKEN_EXPIRATION_SECS.to_string(),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("mmds token request failed: {e}"))?;
|
||||
|
||||
let token = resp.text().await.map_err(|e| format!("mmds token read: {e}"))?;
|
||||
if token.is_empty() {
|
||||
return Err("mmds token is an empty string".into());
|
||||
}
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
async fn get_mmds_opts(client: &reqwest::Client, token: &str) -> Result<MMDSOpts, String> {
|
||||
let resp = client
|
||||
.get(format!("http://{MMDS_ADDRESS}"))
|
||||
.header("X-metadata-token", token)
|
||||
.header("Accept", "application/json")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("mmds opts request failed: {e}"))?;
|
||||
|
||||
resp.json::<MMDSOpts>()
|
||||
.await
|
||||
.map_err(|e| format!("mmds opts parse: {e}"))
|
||||
}
|
||||
|
||||
pub async fn get_access_token_hash() -> Result<String, String> {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.no_proxy()
|
||||
.build()
|
||||
.map_err(|e| format!("http client: {e}"))?;
|
||||
|
||||
let token = get_mmds_token(&client).await?;
|
||||
let opts = get_mmds_opts(&client, &token).await?;
|
||||
Ok(opts.access_token_hash)
|
||||
}
|
||||
|
||||
/// Polls MMDS every 50ms until metadata is available.
|
||||
/// Stores sandbox_id and template_id in env_vars and writes to /run/wrenn/ files.
|
||||
pub async fn poll_for_opts(
|
||||
env_vars: Arc<DashMap<String, String>>,
|
||||
cancel: CancellationToken,
|
||||
) -> Option<MMDSOpts> {
|
||||
let client = reqwest::Client::builder()
|
||||
.no_proxy()
|
||||
.build()
|
||||
.ok()?;
|
||||
|
||||
let mut interval = tokio::time::interval(MMDS_POLL_INTERVAL);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = cancel.cancelled() => {
|
||||
tracing::warn!("context cancelled while waiting for mmds opts");
|
||||
return None;
|
||||
}
|
||||
_ = interval.tick() => {
|
||||
let token = match get_mmds_token(&client).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
tracing::debug!(error = %e, "mmds token poll");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let opts = match get_mmds_opts(&client, &token).await {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
tracing::debug!(error = %e, "mmds opts poll");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
env_vars.insert("WRENN_SANDBOX_ID".into(), opts.sandbox_id.clone());
|
||||
env_vars.insert("WRENN_TEMPLATE_ID".into(), opts.template_id.clone());
|
||||
|
||||
let run_dir = std::path::Path::new(WRENN_RUN_DIR);
|
||||
let _ = std::fs::write(run_dir.join(".WRENN_SANDBOX_ID"), &opts.sandbox_id);
|
||||
let _ = std::fs::write(run_dir.join(".WRENN_TEMPLATE_ID"), &opts.template_id);
|
||||
|
||||
return Some(opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
envd-rs/src/host/mod.rs
Normal file
2
envd-rs/src/host/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod metrics;
|
||||
pub mod mmds;
|
||||
Reference in New Issue
Block a user