返回文章

文章 2024

使用 Rust 来实现自己的 Docker

使用 Rust 来构建自己的 Docker

RustDocker
本文目录11
  1. 背景
  2. 开搞
  3. 项目搭建
  4. 功能实现
  5. 1. 命令执行并输出
  6. 2. 处理 exit 逻辑
  7. 3. 文件系统隔离
  8. 4. 进程隔离
  9. 5. 其他
  10. 总结
  11. Reference

背景

最近刚刚学完 Rust 的语法,所以想做一个简单的《Build your own docker》,看到 CodeCrafters 有个的教程,所以赶紧来试一下。

开搞

项目搭建

教程中的项目有提供最基础的项目代码,在编写完成后提交代码会在云端进行测试验证,但是我想在本地直接进行验证(其实是后面的章节收费了,大雾),所以项目由以下两部分组成:

docker:完整的 my-docker 项目源码,执行 your_docker.sh 的时候会通过 cargo run 来执行代码

Untitled-8135b91d.png

tester:本地测试命令的项目,这部分代码在《[docker-tester](https://github.com/codecrafters-io/docker-tester/tree/18245703a5beed8ee0a7e1cbb7204a7ee3b3b5d1)》,不过这部分是用 go 写的,所以要运行在 docker 项目里

Untitled-4cee6fab.png

项目根目录会提供两个脚本方便测试 - test_your_docker.sh:在 docker 中执行 docker-tester 来验证 cli 的逻辑
bash
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:判断命令输出是否符合要求
bash
#!/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
bash
mydocker='docker container prune -f && docker build -t mydocker ./docker && docker run --cap-add="SYS_ADMIN" mydocker'

功能实现

1. 命令执行并输出

根据用户传入的命令,在本地电脑执行对应的命令,并且处理好对应的输入输出和退出逻辑,关键代码如下

rust
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 逻辑

在执行命令之后判断命令是否执行成功,如果没有则返回相应的退出码

rust
if !output.status.success() {
    exit(output.status.code().unwrap_or(1));
}

3. 文件系统隔离

文件系统是借用的宿主的 chroot 能力,该能力可以改变当前进程及其子进程根目录的操作,从而完成文件系统的隔离

rust
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 命名空间,来进行进程隔离

rust
fn setup_process_isolation() -> () {
    unsafe {
        libc::unshare(libc::CLONE_NEWPID);
    }
}

5. 其他

到此为止已对项目中的核心功能进行了复现,其他的部分以后再来探索吧:

  • 登录到 docker hub

  • 下载 docker 镜像

  • 运行镜像时使用 docker layer

总结

总的来说还是很有意思的,在熟悉了 rust 的同时也了解了 Linux 的 chrootPID namespace 的能力,并且对 docker 的基本运行原理有了更深一步的了解,Very nice!

Reference

https://github.com/JiyuShao/build-your-own-docker-rust

https://app.codecrafters.io/courses/docker/overview

https://github.com/codecrafters-io/docker-tester/tree/18245703a5beed8ee0a7e1cbb7204a7ee3b3b5d1

返回首页
上一篇啊鸡折腾之《IPV6 公网访问》下一篇啊鸡入坑硬件之《Mini 打印机》

Discussion

留言与讨论

想法、补充和不同意见都欢迎。