main
阳光少年 1 year ago
parent a156635273
commit 66aeab6753

@ -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"] }

@ -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<String>,
#[arg(long)]
image: Option<String>,
#[arg(long)]
volume: Option<String>,
#[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<Vec<String>>
}
/// 从images解压到volumes
fn extend_image(image_name: &String) -> Result<(PathBuf)> {
fn extend_image(image_name: &String) -> Result<PathBuf> {
// 源文件
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<P: AsRef<Path>>(volume_path: P, upper_path: P, merged_
Ok(())
}
fn init_container_custom_volume<P: AsRef<Path>>(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::<Vec<&str>>();
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<P: AsRef<Path>>(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<P: AsRef<Path>>(path: P) -> Result<()> {
fn create_dir<P: AsRef<Path>>(path: P, is_any:bool) -> Result<()> {
fs::create_dir_all(&path)?;
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,35 +399,21 @@ 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");
let all_container_work_path = fs::read_dir(containers_path)?
.map(|res| res.map(|e| e.path()))
.filter_map(|p| p.ok())
.collect::<Vec<PathBuf>>();
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;
}
fn get_container_info(container_id: &String) -> Result<ContainerInfo> {
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 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())
@ -364,12 +423,67 @@ fn show_containers() -> Result<()> {
false
};
if is_running {
container_info.status = "✅".to_string();
container_info.status = ContainerStatus::RUNNING;
} else {
container_info.status = "❌".to_string();
container_info.status = ContainerStatus::STOP;
}
Ok(container_info)
}
println!("{}", container_info);
fn get_all_container_info() -> Result<Vec<ContainerInfo>> {
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())
.map(|f|f.to_string_lossy().to_string())
.filter_map(|s|get_container_info(&s).ok())
.collect::<Vec<ContainerInfo>>();
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}");
}
}
Ok(())
}
fn delete_container(containers_id: &Vec<String>) -> 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::<Vec<String>>();
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:?}");
}
}
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

Loading…
Cancel
Save