5 releases (2 stable)
1.1.0 | Feb 22, 2024 |
---|---|
1.0.1 |
|
0.2.1 | Feb 3, 2024 |
0.1.0 | Feb 1, 2024 |
#322 in Operating systems
265KB
5K
SLoC
rustcracker
A crate for communicating with firecracker developed by Xue Haonan during development of PKU-cloud. Reference: firecracker-go-sdk
Thanks for supports from all members of LCPU (Linux Club of Peking University).
Example
Create 10 microVMs asynchronously. You may want to read this first.
//! Benchmark code of starting a Machine
use std::path::PathBuf;
use log::{error, info};
use run_script::ScriptOptions;
use rustcracker::{
components::{
command_builder::VMMCommandBuilder,
machine::{Config, Machine, MachineError},
},
model::{
balloon::Balloon,
cpu_template::{CPUTemplate, CPUTemplateString},
drive::Drive,
logger::LogLevel,
machine_configuration::MachineConfiguration,
network_interface::NetworkInterface,
},
utils::{check_kvm, StdioTypes},
};
// directory that hold all the runtime structures.
const RUN_DIR: &'static str = "/tmp/rustcracker/run";
// directory that holds resources e.g. kernel image and file system image.
const RESOURCE_DIR: &'static str = "/tmp/rustcracker/res";
// directory that holds snapshots
const SNAPSHOT_DIR: &'static str = "/tmp/rustcracker/snapshot";
// a tokio coroutine that might be parallel with other coroutines
#[tokio::main]
async fn main() -> Result<(), MachineError> {
// Initialize env logger
let _ = env_logger::builder().is_test(false).try_init();
// check that kvm is accessible
check_kvm()?;
// set up network
let (code, _output, error) = run_script::run_script!(
r#"
TAP_DEV="tap$1"
HOST_IFACE="eth$1"
TAP_IP="172.16.0.1"
MASK_SHORT="/30"
# Setup network interface
sudo ip link del "$TAP_DEV" 2> /dev/null || true
sudo ip tuntap add dev "$TAP_DEV" mode tap
sudo ip addr add "${TAP_IP}${MASK_SHORT}" dev "$TAP_DEV"
sudo ip link set dev "$TAP_DEV" up
# Enable ip forwarding
sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
# Set up microVM internet access
sudo iptables -t nat -D POSTROUTING -o "$HOST_IFACE" -j MASQUERADE || true
sudo iptables -D FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT \
|| true
sudo iptables -D FORWARD -i "$TAP_DEV" -o "$HOST_IFACE" -j ACCEPT || true
sudo iptables -t nat -A POSTROUTING -o "$HOST_IFACE" -j MASQUERADE
sudo iptables -I FORWARD 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -I FORWARD 1 -i "$TAP_DEV" -o "$HOST_IFACE" -j ACCEPT
"#,
&vec![format!("0")],
&ScriptOptions::new()
).unwrap();
// if networking is configured successfully, then run the machine `name{id}``
if !(code == 0 && error == "") {
error!(target: "main", "Fail to set up network");
return Err(MachineError::Execute(format!("Fail to set up network: {}",error)));
}
/* below are configurations that could be transmitted with json file (Serializable and Deserializable) */
/* ############ configurations begin ############ */
// the name of this microVM
let vmid = "name1";
// the directory that holds this microVM
// /tmp/rustfire/run/name1
let dir = PathBuf::from(RUN_DIR).join(vmid);
std::fs::create_dir_all(&dir).map_err(|e| {
MachineError::FileCreation(format!("fail to create {}: {}", dir.display(), e.to_string()))
})?;
// suppose that the logger is going to be created at "${RUN_DIR}/logger"
// /tmp/rustfire/run/name1/log.fifo
let log_fifo = dir.join("log.fifo");
// metrics path
// /tmp/rustcracker/run/name1/metrics.fifo
let metrics_fifo = dir.join("metrics.fifo");
// unix domain socket (communicate with firecracker) path
// /tmp/rustcracker/run/name1/api.sock
let socket_path = dir.join("api.sock");
// kernel image path (prepare valid kernel image here)
// /tmp/rustcracker/res/vmlinux
let vmlinux_path = PathBuf::from(&RESOURCE_DIR).join("vmlinux");
// root fs path (prepare valid root file system image here)
// /tmp/rustcracker/res/rootfs
let rootfs_path = PathBuf::from(&RESOURCE_DIR).join("rootfs");
// firecracker binary
// /tmp/rustcracker/res/firecracker
let firecracker_path = PathBuf::from(&RESOURCE_DIR).join("firecracker");
// path that holds snapshot
let snapshot_dir = PathBuf::from(&SNAPSHOT_DIR).join(vmid);
std::fs::create_dir_all(&snapshot_dir).map_err(|e| {
MachineError::FileCreation(format!("fail to create {}: {}", snapshot_dir.display(), e.to_string()))
})?;
let snapshot_mem = snapshot_dir.join("mem");
let snapshot_path = snapshot_dir.join("snapshot");
let init_metadata = r#"{
"name": "Xue Haonan",
"email": "xuehaonan27@gmail.com"
}"#;
// write the configuration of the firecraker process
let config = Config {
// microVM's name
vmid: Some(vmid.to_string()),
// the path to unix domain socket that you want the firecracker to spawn
socket_path: Some(socket_path.to_owned()),
kernel_image_path: Some(vmlinux_path),
log_fifo: Some(log_fifo.to_owned()),
metrics_fifo: Some(metrics_fifo.to_owned()),
log_level: Some(LogLevel::Debug),
// the configuration of the microVM
machine_cfg: Some(MachineConfiguration {
// give microVM 1 virtual CPU
vcpu_count: 1,
// config correct CPU template here (as same as physical CPU template)
cpu_template: Some(CPUTemplate(CPUTemplateString::None)),
// give microVM 256 MiB memory
mem_size_mib: 256,
// disable hyperthreading
ht_enabled: Some(false),
track_dirty_pages: None,
}),
drives: Some(vec![Drive {
// name that you like
drive_id: "root".to_string(),
// root fs is the ONLY root device that should be configured
is_root_device: true,
// if set true, then user cannot write to the rootfs
is_read_only: true,
path_on_host: rootfs_path,
partuuid: None,
cache_type: None,
rate_limiter: None,
io_engine: None,
socket: None,
}]),
// kernel_args might be overrided
// if any network interfaces configured, the `ip` field may be added or modified
kernel_args: Some("".to_string()),
network_interfaces: Some(vec![NetworkInterface {
guest_mac: Some("06:00:AC:10:00:02".to_string()),
host_dev_name: "tap0".into(),
iface_id: "net1".into(),
rx_rate_limiter: None,
tx_rate_limiter: None,
}]),
net_ns: Some("my_netns".into()),
balloon: Some(
Balloon::new()
.with_amount_mib(100)
.with_stats_polling_interval_s(5)
.set_deflate_on_oom(true),
),
init_metadata: Some(init_metadata.to_string()),
// configurations that could be set yourself and I don't want to set here
forward_signals: None,
log_path: None,
metrics_path: None,
initrd_path: None,
// virtio devices
vsock_devices: None,
// when running in production environment, don't set this true to avoid validation
disable_validation: false,
enable_jailer: false,
jailer_cfg: None,
seccomp_level: None,
mmds_address: None,
stdin: Some(StdioTypes::Null),
stdout: Some(StdioTypes::From {
path: log_fifo.to_owned(),
}),
stderr: Some(StdioTypes::From {
path: log_fifo.to_owned(),
}),
log_clear: Some(true),
metrics_clear: Some(true),
network_clear: Some(true),
agent_init_timeout: None,
agent_request_timeout: None,
};
/* ############ configurations end ############ */
/* ############ Launching microVM ############ */
// use sig_send to send a signal to firecracker process (yet implemented)
// let (sig_send, sig_recv) = async_channel::bounded(64);
#[allow(unused_variables)]
let mut machine = Machine::new(config)?;
// use exit_send to send a force stop instruction (MachineMessage::StopVMM) to the microVM
// build your own microVM command
let cmd = VMMCommandBuilder::new()
.with_socket_path(&socket_path)
.with_bin(&firecracker_path)
.build();
// set your own microVM command (optional)
// if not, then the machine will start using default command
// ${firecracker_path} --api-sock ${socket_path} --seccomp-level 0 --id ${config.vmid}
// (seccomp level 0 means disable seccomp)
machine.set_command(cmd.into());
// start the microVM
machine.start().await.map_err(|e| {
// remove the socket, log and metrics in case we start fail
let _ = std::fs::remove_file(&socket_path);
let _ = std::fs::remove_file(&log_fifo);
let _ = std::fs::remove_file(&metrics_fifo);
e
})?;
/* ############ Checking microVM ############ */
let metadata = machine.get_metadata().await?;
info!(target: "Metadata", "{}", metadata);
let instance_info = machine.describe_instance_info().await?;
info!(target: "InstanceInfo", "{:#?}", instance_info);
let balloon = machine.get_balloon_config().await?;
info!(target: "Balloon", "{:#?}", balloon);
let balloon_stats = machine.get_balloon_stats().await?;
info!(target: "BalloonStats", "{:#?}", balloon_stats);
/* ############ Modifying microVM ############ */
let new_metadata = r#"{
"name":"Mugen_Cyaegha",
"email":"897657514@qq.com"
}"#.to_string();
machine.update_metadata(&new_metadata).await?;
// machine.update_balloon(10).await?;
// machine.update_balloon_stats(3).await?;
machine.refresh_machine_configuration().await?;
/* ############ Checking microVM ############ */
let metadata = machine.get_metadata().await?;
info!(target: "Re-Metadata", "{}", metadata);
let instance_info = machine.describe_instance_info().await?;
info!(target: "Re-InstanceInfo", "{:#?}", instance_info);
let balloon = machine.get_balloon_config().await?;
info!(target: "Re-Balloon", "{:#?}", balloon);
let balloon_stats = machine.get_balloon_stats().await?;
info!(target: "Re-BalloonStats", "{:#?}", balloon_stats);
/* ############ Saving microVM ############ */
// one should always pause the microVM before trying to create snapshot for it
machine.pause().await?;
info!(target: "Pause", "Paused");
machine.create_snapshot(&snapshot_mem, &snapshot_path).await?;
machine.resume().await?;
info!(target: "Resume", "Resumed");
/* ############ Exiting microVm ############ */
// wait for the machine to exit.
// Machine::wait will block until the firecracker process exit itself
// machine.wait().await?;
// explicitly call Machine::shutdown() and Machine::stop_vmm() to terminate the machine.
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
machine.shutdown().await?;
machine.stop_vmm().await?;
Ok(())
}
Dependencies
~12–22MB
~291K SLoC