From 66aeab67537220164fc7cf6c176d2ad3e8c2cbd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=B3=E5=85=89=E5=B0=91=E5=B9=B4?= <849317537@qq.com> Date: Thu, 1 Aug 2024 05:05:38 +0000 Subject: [PATCH] =?UTF-8?q?rm=E5=88=9D=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- src/main.rs | 214 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 166 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 148e7d8..b6a6eb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" uuid = {version = "1.3", features = ["v4"]} toml = "0.8.8" clap = {version = "4.5.0", features = ["derive"]} -nix = {version = "0.29", features = ["sched", "process", "hostname", "mount", "fs", "env", "user", "term"]} +nix = {version = "0.29", features = ["sched", "process", "hostname", "mount", "fs", "env", "user", "term", "signal"]} serde = { version = "1.0", features = ["derive"] } diff --git a/src/main.rs b/src/main.rs index e35a453..8703619 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use std::os::fd::AsRawFd; use std::os::unix::fs::PermissionsExt; use nix::libc::{self, setgid, CLONE_NEWCGROUP, MS_NODEV, MS_NOSUID}; use nix::sched::{clone, CloneCb, CloneFlags}; +use nix::sys::signal::{kill, Signal}; use nix::sys::wait::{wait, waitpid, waitid, WaitPidFlag}; use nix::unistd::{chdir, chroot, dup2, execv, pivot_root, setuid, sleep, Gid, Pid, Uid, User, setgroups}; use nix::mount::{mount, MntFlags, MsFlags, umount2, umount}; @@ -34,6 +35,8 @@ struct RockerArgs { run: Option, #[arg(long)] image: Option, + #[arg(long)] + volume: Option, #[arg(long)] log: bool, @@ -47,11 +50,18 @@ struct RockerArgs { // --ps #[arg(long)] ps: bool, + // --psa + #[arg(long)] + psa: bool, + + // rm container_id + #[arg(long)] + rm: Option> } /// 从images解压到volumes -fn extend_image(image_name: &String) -> Result<(PathBuf)> { +fn extend_image(image_name: &String) -> Result { // 源文件 let image_path = Path::new(WORKSPACE).join("images").join(image_name); if image_path.exists() == false { @@ -64,7 +74,7 @@ fn extend_image(image_name: &String) -> Result<(PathBuf)> { if volume_path.exists() { return Ok(volume_path); } else { - create_dir_and_set777(&volume_path)?; + create_dir(&volume_path, true)?; } let volume_path_str = volume_path.to_str().unwrap(); // 安全的unwrap @@ -124,6 +134,45 @@ fn init_container_overlay>(volume_path: P, upper_path: P, merged_ Ok(()) } +fn init_container_custom_volume>(container_merged_path: P, custom_volume_s: &String) -> Result<()> { + for custom_volume in custom_volume_s.split(",") { + let custom_volume_v = custom_volume.split(":").collect::>(); + if custom_volume_v.len() < 2 { + return Err(RockerError::OtherError(format!("volume 参数格式不正确: {custom_volume}"))); + } + let host_path = custom_volume_v[0]; + let container_path_buf = { + if custom_volume_v[1].starts_with("/") { + container_merged_path.as_ref().join(&custom_volume_v[1][1..]) + } else { + container_merged_path.as_ref().join(&custom_volume_v[1]) + } + }; + let container_path = container_path_buf.to_string_lossy().to_string(); + + // 创建宿主机和容器内的目录 + create_dir(Path::new(host_path), false)?; + create_dir(&container_path, true)?; + + // 绑定 + let out = std::process::Command::new("mount") + .arg("-o") + .arg("bind") + .arg(host_path) + .arg(container_path) + .output()?; + let std_out = String::from_utf8_lossy(&out.stdout); + let std_err = String::from_utf8_lossy(&out.stderr); + if std_err.len() == 0 { + println!("创建自定义 volume: {custom_volume:?}"); + } else { + return Err(RockerError::OtherError(format!("创建volume失败: {std_err}"))) + } + } + Ok(()) +} + + fn init_container_pivot>(merged_path: P) -> Result<()> { // 在我们没有设置 chroot之前, 需要先把所有挂载点的传播类型改为 private, 避免进程中的系统调用污染全局 mount(None::<&str>, "/", None::<&str>, MsFlags::MS_PRIVATE | MsFlags::MS_REC, None::<&str>)?; @@ -166,7 +215,7 @@ fn init_container_mount() -> Result<()> { fn init_container_log(log: bool) -> Result<()> { let log_path = Path::new("logs"); - create_dir_and_set777(log_path)?; + create_dir(log_path, true)?; let log_fd = File::create(log_path.join("log"))?; if log { unsafe { @@ -193,9 +242,11 @@ fn init_container_user(uid: Uid, gid: Gid) -> Result<()>{ Ok(()) } -fn create_dir_and_set777>(path: P) -> Result<()> { +fn create_dir>(path: P, is_any:bool) -> Result<()> { fs::create_dir_all(&path)?; - fs::set_permissions(&path, PermissionsExt::from_mode(0o777))?; + if is_any { + fs::set_permissions(&path, PermissionsExt::from_mode(0o777))?; + } Ok(()) } @@ -243,9 +294,9 @@ fn run_container(cmd: &String, args: &RockerArgs, volume_path: &PathBuf) -> Resu let container_work_path = Path::new(WORKSPACE).join("containers").join(&_container_id); let container_upper_path = container_work_path.join("upper"); let container_merged_path = container_work_path.join("merged"); - create_dir_and_set777(&container_work_path)?; - create_dir_and_set777(&container_upper_path)?; - create_dir_and_set777(&container_merged_path)?; + create_dir(&container_work_path, true)?; + create_dir(&container_upper_path, true)?; + create_dir(&container_merged_path, true)?; let rocker_user_info = User::from_name(USER_NAME)?.ok_or(RockerError::OtherError(format!("没找到 用户: {USER_NAME}")))?; let rocker_uid = rocker_user_info.uid; @@ -254,6 +305,9 @@ fn run_container(cmd: &String, args: &RockerArgs, volume_path: &PathBuf) -> Resu let _cb = || { init_container_lock(&container_work_path).unwrap(); init_container_overlay(volume_path, &container_upper_path, &container_merged_path).unwrap(); + if let Some(custom_volume) = &args.volume { + init_container_custom_volume(&container_merged_path, custom_volume).unwrap(); + } init_container_pivot(&container_merged_path).unwrap(); init_container_mount().unwrap(); init_container_log(args.log).unwrap(); @@ -298,15 +352,34 @@ fn run_container(cmd: &String, args: &RockerArgs, volume_path: &PathBuf) -> Resu } } +#[derive(Deserialize, Serialize, Debug, PartialEq)] +enum ContainerStatus { + READY, + RUNNING, + STOP, +} + +impl Display for ContainerStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::READY => write!(f, "😀"), + Self::RUNNING => write!(f, "✅"), + Self::STOP => write!(f, "❌"), + } + } + +} + + #[derive(Deserialize, Serialize, Debug)] struct ContainerInfo { id: String, pid: i32, - run: String, - image: String, - volume: String, - env: String, - status: String, + run: String, // /bin/bash + image: String, // busybox + volume: String, // /root/tmp:/root/tmp,/root/tmp1:/root/tmp1 + env: String, // a=1,b=2,c=3 或者 env文件路径 + status: ContainerStatus, } impl Display for ContainerInfo { @@ -326,50 +399,91 @@ fn save_container_info(args: &RockerArgs, container_id: &String, pid: i32) -> Re image: args.image.as_ref().unwrap().clone(), volume: "".to_string(), env: "".to_string(), - status: "".to_string(), + status: ContainerStatus::READY, }; let toml_str = toml::to_string(&container_info)?; fs::write(container_info_path, toml_str)?; Ok(()) } -/// 读取所有容器的状态 -fn show_containers() -> Result<()> { - let containers_path = Path::new(WORKSPACE).join("containers"); - - println!("{:<10}{:<8}{:<10}{:<20}{:<20}{:<20}{:<10}", "id", "pid", "image", "run", "volume", "env", "status"); +fn get_container_info(container_id: &String) -> Result { + let container_work_path = Path::new(WORKSPACE).join("containers").join(container_id); + let container_info_path = container_work_path.join(INFO_FILE); + let lock_path = container_work_path.join(LOCK_FILE); + let info_str = fs::read_to_string(container_info_path)?; + let mut container_info: ContainerInfo = toml::from_str(&info_str)?; + + // 判断是否正在运行, 首先得到该容器所有的fd + let proc_fd_path = Path::new("/proc").join(container_info.pid.to_string()).join("fd"); + let is_running = if let Ok(fd_dir) = fs::read_dir(proc_fd_path) { + fd_dir.filter_map(|p|p.ok()) + .filter_map(|f| fs::read_link(f.path()).ok()) + .any(|p|p == lock_path) + } else { + false + }; + if is_running { + container_info.status = ContainerStatus::RUNNING; + } else { + container_info.status = ContainerStatus::STOP; + } + Ok(container_info) +} - let all_container_work_path = fs::read_dir(containers_path)? - .map(|res| res.map(|e| e.path())) +fn get_all_container_info() -> Result> { + let containers_path = Path::new(WORKSPACE).join("containers"); + let all_containers_info = fs::read_dir(containers_path)? + .map(|res| res.map(|e| e.file_name())) .filter_map(|p| p.ok()) - .collect::>(); - - for container_work_path in all_container_work_path.iter() { - let info_path = container_work_path.join(INFO_FILE); - let mut container_info: ContainerInfo; - if let Ok(info_str) = fs::read_to_string(info_path) { - container_info = toml::from_str(&info_str)?; - } else { - continue; + .map(|f|f.to_string_lossy().to_string()) + .filter_map(|s|get_container_info(&s).ok()) + .collect::>(); + Ok(all_containers_info) +} + +/// 读取所有容器的状态 +fn show_containers(is_show_all: bool) -> Result<()> { + println!("{:<10}{:<8}{:<10}{:<20}{:<20}{:<20}{:<10}", "id", "pid", "image", "run", "volume", "env", "status"); + for container_info in get_all_container_info()? { + if is_show_all{ + println!("{container_info}"); + } else if container_info.status == ContainerStatus::RUNNING { + println!("{container_info}"); } - - // 判断是否正在运行, 首先得到该容器所有的fd - let lock_path = container_work_path.join(LOCK_FILE); - let proc_fd_path = Path::new("/proc").join(container_info.pid.to_string()).join("fd"); - let is_running = if let Ok(fd_dir) = fs::read_dir(proc_fd_path) { - fd_dir.filter_map(|p|p.ok()) - .filter_map(|f| fs::read_link(f.path()).ok()) - .any(|p|p == lock_path) - } else { - false - }; - if is_running { - container_info.status = "✅".to_string(); - } else { - container_info.status = "❌".to_string(); + } + Ok(()) +} + +fn delete_container(containers_id: &Vec) -> Result<()> { + for container_id in containers_id { + if let Ok(container_info) = get_container_info(container_id) { + let container_work_path = Path::new(WORKSPACE).join("containers").join(container_id); + let container_merged_path = container_work_path.join("merged"); + // 正在运行中的需要 kill + if container_info.status == ContainerStatus::RUNNING { + kill(Pid::from_raw(container_info.pid), Signal::SIGTERM)?; + } + + // 卸载自定义挂载点 + let volumes_path = container_info.volume + .split(",") + .filter_map(|v| v.split(":").last()) + .map(|v| container_merged_path.join(v).to_string_lossy().to_string()) + .collect::>(); + for volume_path in volumes_path { + umount(volume_path.as_str())?; + println!("卸载卷: {volume_path:?}"); + } + + // 卸载overlayfs + umount(container_merged_path.to_str().unwrap())?; + + // 删除容器目录 + fs::remove_dir_all(container_work_path)?; + println!("删除容器: {container_id:?}"); } - println!("{}", container_info); } + Ok(()) } @@ -380,9 +494,11 @@ fn main() -> Result<()>{ if let (Some(cmd), Some(image_name)) = (&args.run, &args.image) { let volume_path = extend_image(image_name)?; let (container_id, pid) = run_container(cmd, &args, &volume_path)?; - save_container_info(&args, &container_id, pid)?; - } else if args.ps { - show_containers()? + save_container_info(&args, &container_id, pid)?; // todo 无论出不错, 都要保存一个信息, 后面需要删除用清理 + } else if args.ps || args.psa { + show_containers(args.psa)? + } else if let Some(containers_id) = &args.rm { + delete_container(containers_id)?; } // exec