使用 Rust 来实现自己的 Docker
Last Edited Time
May 29, 2024 07:05 AM
date
Mar 1, 2024
slug
build-your-own-docker-using-rust
status
Published
tags
Rust
Docker
summary
使用 Rust 来构建自己的 Docker
type
Post
背景
最近刚刚学完 Rust 的语法,所以想做一个简单的《Build your own docker》,看到 CodeCrafters 有个的教程,所以赶紧来试一下。
开搞
项目搭建
教程中的项目有提供最基础的项目代码,在编写完成后提交代码会在云端进行测试验证,但是我想在本地直接进行验证(其实是后面的章节收费了,大雾),所以项目由以下两部分组成:
docker:完整的 my-docker 项目源码,执行 your_docker.sh 的时候会通过 cargo run 来执行代码
tester:本地测试命令的项目,这部分代码在《docker-tester》,不过这部分是用 go 写的,所以要运行在 docker 项目里
项目根目录会提供两个脚本方便测试
- test_your_docker.sh:在 docker 中执行 docker-tester 来验证 cli 的逻辑
docker system prune --force
docker build -t docker-tester-dev . && docker run --cap-add "SYS_ADMIN" -e "TERM=xterm-256color" docker-tester-dev make test
- test_shell_output.sh:判断命令输出是否符合要求
#!/bin/bash
# 执行 shell 命令
# bash -c "xxxx"
# bash -c "ls non_existing_file.txt"
bash -c "ls"
# 获取退出代码
exit_code=$?
# 根据退出代码进行错误判断
if [[ $exit_code -eq 0 ]]; then
echo "命令执行成功"
else
echo "命令执行失败,退出代码:$exit_code"
# 根据具体的退出代码值进行错误处理
if [[ $exit_code -eq 1 ]]; then
echo "General Error: 1"
elif [[ $exit_code -eq 2 ]]; then
echo "Misuse of Shell Built-in: 2"
elif [[ $exit_code -eq 126 ]]; then
echo "Cannot Execute: 126"
elif [[ $exit_code -eq 127 ]]; then
echo "Command Not Found: 127"
fi
fi
如果想直接运行 mydocker,可以直接添加以下 alias
mydocker='docker container prune -f && docker build -t mydocker ./docker && docker run --cap-add="SYS_ADMIN" mydocker'
功能实现
1. 命令执行并输出
根据用户传入的命令,在本地电脑执行对应的命令,并且处理好对应的输入输出和退出逻辑,关键代码如下
let args: Vec<_> = std::env::args().collect();
let command = &args[3];
let command_args = &args[4..];
let output = Command::new(command)
.args(command_args)
// .stdout(Stdio::piped())
// .stderr(Stdio::piped())
.output()
.with_context(|| {
format!(
"Tried to run '{}' with arguments {:?}",
command, command_args
)
})?;
let std_out = std::str::from_utf8(&output.stdout)?;
print!("{}", std_out);
let std_err = std::str::from_utf8(&output.stderr)?;
eprint!("{}", std_err);
2. 处理 exit 逻辑
在执行命令之后判断命令是否执行成功,如果没有则返回相应的退出码
if !output.status.success() {
exit(output.status.code().unwrap_or(1));
}
3. 文件系统隔离
文件系统是借用的宿主的 chroot 能力,该能力可以改变当前进程及其子进程根目录的操作,从而完成文件系统的隔离
fn setup_filesystem_isolation(command: &str) -> Result<()> {
// container_path: /tmp/your_docker_container/
let container_path = Path::new("/tmp/your_docker_container");
std::fs::create_dir_all(container_path.join("dev/null"))?;
let command_path = PathBuf::from(command);
let command_file_name = command_path.file_name().unwrap();
let dest_command_path = container_path.join(command_path.parent().unwrap().strip_prefix("/")?);
if DEBUG == true {
println!(
"command_path: {}; command_path: {}",
command_path.display(),
dest_command_path.display()
);
}
std::fs::create_dir_all(dest_command_path.clone())?;
std::fs::copy(
command_path.clone(),
dest_command_path.join(command_file_name),
)?;
chroot(container_path)?;
std::env::set_current_dir("/")?;
Ok(())
}
4. 进程隔离
通过使用 PID namespaces 的方式来常见一个新的进程 ID 命名空间,来进行进程隔离
fn setup_process_isolation() -> () {
unsafe {
libc::unshare(libc::CLONE_NEWPID);
}
}
5. 其他
到此为止已对项目中的核心功能进行了复现,其他的部分以后再来探索吧:
- 登录到 docker hub
- 下载 docker 镜像
- 运行镜像时使用 docker layer
总结
总的来说还是很有意思的,在熟悉了 rust 的同时也了解了 Linux 的
chroot
和 PID namespace
的能力,并且对 docker
的基本运行原理有了更深一步的了解,Very nice!