《自己动手写 docker》笔记和源码
建议先了解一下 Docker 的核心原理大致分析,可以看这几篇文章:
- 核心原理:深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs。
- 基于 namespace 的视图隔离:探索 Linux Namespace:Docker 隔离的神奇背后
- 基于 cgroups 的资源限制
- 基于 overlayfs 的文件系统:Docker 魔法解密:探索 UnionFS 与 OverlayFS
- 基于 veth pair、bridge、iptables 等等技术的 Docker 网络:揭秘 Docker 网络:手动实现 Docker 桥接网络
通过上述文章,大家对 Docker 的实现原理已经有了初步的认知,接下来我们就用 Golang 手动实现一下自己的 docker(mydocker)。
鸽了很久之后,终于开通了,欢迎关注。
一个云原生打工人的探索之路,专注云原生,Go,坚持分享最佳实践、经验干货。
扫描下面的二维码关注我的微信公众帐号,一起探索云原生
吧~
搭配 从零开始写 Docker:实现 run 命令 食用更加~。
开发环境如下:
root@mydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.2 LTS
Release: 20.04
Codename: focal
root@mydocker:~# uname -r
5.4.0-74-generic
测试脚本如下:
# 克隆代码
git clone -b feat-run https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试
./mydocker run -it /bin/ls # 需要 root 权限
正常结果
root@mydocker:~/mydocker# ./mydocker run -it /bin/ls
{"level":"info","msg":"init come on","time":"2024-01-08T09:32:52+08:00"}
{"level":"info","msg":"command: /bin/ls","time":"2024-01-08T09:32:52+08:00"}
{"level":"info","msg":"command:/bin/ls","time":"2024-01-08T09:32:52+08:00"}
LICENSE Makefile README.md container example go.mod go.sum main.go main_command.go mydocker run.go
root@mydocker:~/mydocker# ./mydocker run -it /bin/sh
{"level":"info","msg":"init come on","time":"2024-01-08T09:32:54+08:00"}
{"level":"info","msg":"command: /bin/sh","time":"2024-01-08T09:32:54+08:00"}
{"level":"info","msg":"command:/bin/sh","time":"2024-01-08T09:32:54+08:00"}
# ps -e
PID TTY TIME CMD
1 pts/1 00:00:00 sh
5 pts/1 00:00:00 ps
mydocker 的代码分为以下几个部分:
.
├── container
│ ├── container_process.go # 构建容器进程运行参数
│ └── init.go # 初始化容器进程,并执行容器进程
├── example
│ └── main.go # 单独的文件,可编译成独立的可执行文件, 一个 Go 中调用 namespace 和 Cgroups 的例子,不牵涉其他 go 文件
├── go.mod
├── go.sum
├── LICENSE
├── main_command.go # 命令行解析,包含两个部分 run 和 init
├── main.go # main 函数入口
├── Makefile
├── README.md
└── run.go # 启动子进程
下面介绍基本的执行流程:
当执行./mydocker run -it /bin/ls
时,会先执行到 main_command.go::cli.Command::Action
,在里面会提取出/bin/ls
命令(被保存在cmd
变量中),tty变量则是用于确定是否需要打开新终端。
之后会调用run.go::Run
函数,该函数会调用container/container_process.go::NewParentProcess
函数,构建子进程运行参数。在构建参数时,会指示创建新的namespaces.
之后run.go::Run
函数启动子进程。在container/container_process.go::NewParentProcess
中构建的子进程参数如下:/proc/self/exe init /bin/ls
,表示要创建的子进程就是自身,只不过要执行的命令是init
,参数是/bin/ls
。
在新创建的子进程中,mydocker会执行到main_command.go::cli.Command::Action
,这里会调用container/init.go::RunContainerInitProcess
函数。在该运行函数中,会挂在相应的目录,最后会执行/bin/ls
命令。