1. rootfs
rootfs
: 进程运行后的根目录,又叫根文件系统。
操作系统 rootfs 包含了操作系统运行所需要的文件和目录。
对于镜像而言,应用以及它运行所需的所有依赖都被打包到了 rootfs。
容器进程运行后的根目录,就叫做 rootfs。这个根文件系统,一般来说包括下面这些文件/bin,/etc,/proc 等
运行容器时执行的/bin/bash 其实是容器的 rootfs 中/bin 目录下的 bash 可执行文件,和宿主机的 rootfs 中/bin 目录下的 bash 文件没关系。
2. chroot
chroot(change root)
: 改变进程的根目录,使它不能访问该目录之外的其他文件。而容器实际上就是一个特殊的进程,所以我们就可以使用 chroot 限制容器的文件访问,使得每个容器都有一个自己的文件系统。
chroot [OPTION] NEWROOT [COMMAND [ARG]...]
-
NEWROOT:表示切换到的新的 root 目录
-
COMMAND:表示切换 root 目录后执行的命令
现在写一个 go 项目输出/目录下的所有文件
func main(){
fds,err := os.ReadDir("/")
if err != nil{
panic(err)
}
for _,fd:=range fds{
if fd.IsDir(){
fmt.Print(fd.Name()+" ")
}
}
fmt.Println()
}
// 打包
go build -o server main.go
//将server复制到/opt/chroot下
cp server /opt/chroot
(base) root@ubuntu:/opt/chroot# ls -la
total 2040
drwxr-xr-x 2 root root 4096 Aug 3 15:43 .
drwxr-xr-x 6 root root 4096 Aug 3 15:43 ..
-rwxr-xr-x 1 root root 2080126 Aug 3 15:37 server
//执行server,将显示系统本身根目录下的目录
(base) root@ubuntu:/opt/chroot# ./server
app boot dev etc home lost+found media mnt opt proc project root run snap srv sys tmp usr var
创建一个目录 go-server,作为 server 命令的新的 root 目录,并在 go-server 目录下创建几个目录以供 server 命令使用。
(base) root@ubuntu:/opt/chroot# mkdir -p go-server/{test1,test2,test3,test4}
(base) root@ubuntu:/opt/chroot# ls go-server/
test1 test2 test3 test4
将 go-server 目录作为 server 命令来执行。
// 将server命令移到go-server的目录或者子目录下
(base) root@ubuntu:/opt/chroot# mv server go-server/
// 查看目录结构
(base) root@ubuntu:/opt/chroot# tree go-server/
go-server/
├── server
├── test1
├── test2
├── test3
└── test4
// 先单独执行一下 server 命令,会输出系统根目录下的目录
(base) root@ubuntu:/opt/chroot# ./go-server/server
app boot dev etc home lost+found media mnt opt proc project root run snap srv sys tmp usr var
// 添加新的根目录/go-server,执行
(base) root@ubuntu:/opt/chroot# chroot go-server/ /server
test1 test2 test3 test4
//更换根目录,再次执行
(base) root@ubuntu:/opt# ls -la chroot/
total 16
drwxr-xr-x 4 root root 4096 Aug 3 16:06 .
drwxr-xr-x 6 root root 4096 Aug 3 15:43 ..
drwxr-xr-x 6 root root 4096 Aug 3 15:53 go-server
drwxr-xr-x 5 root root 4096 Aug 3 16:06 ls_test
(base) root@ubuntu:/opt# chroot chroot/ /go-server/server
go-server ls_test
其实我们还可以给系统本身的命令新的 root 目录,这里以 ls 命令为例
// 创建所需的目录
(base) root@ubuntu:/opt/chroot# mkdir -p ls_test/{bin,lib,lib64}
// 将ls命令复制到ls_test/bin目录中
(base) root@ubuntu:/opt/chroot# cp /bin/ls ls_test/bin/
// 查看ls的所有依赖
(base) root@ubuntu:/opt/chroot# ldd /bin/ls
linux-vdso.so.1 (0x00007ffe396bc000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fc398b87000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc398995000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007fc398904000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc3988fe000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc398be1000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fc3988db000)
// 将所有依赖也放入到对应的目录中
(base) root@ubuntu:/opt/chroot# cp /lib/x86_64-linux-gnu/libselinux.so.1 ls_test/lib
(base) root@ubuntu:/opt/chroot# cp /lib/x86_64-linux-gnu/libc.so.6 ls_test/lib
(base) root@ubuntu:/opt/chroot# cp /lib/x86_64-linux-gnu/libpcre2-8.so.0 ls_test/lib
(base) root@ubuntu:/opt/chroot# cp /lib/x86_64-linux-gnu/libdl.so.2 ls_test/lib
(base) root@ubuntu:/opt/chroot# cp /lib64/ld-linux-x86-64.so.2 ls_test/lib64/
(base) root@ubuntu:/opt/chroot# cp /lib/x86_64-linux-gnu/libpthread.so.0 ls_test/lib
给 ls 命令添加新的 root 目录
// 先单独执行一下命令,显示的是我们系统根目录下的文件
(base) root@ubuntu:/opt/chroot# ./ls_test/bin/ls /
app lib mnt sbin usr wget-log.12 wget-log.18 wget-log.5
bin lib32 opt snap var wget-log.13 wget-log.19 wget-log.6
boot lib64 proc srv wget-log wget-log.14 wget-log.2 wget-log.7
dev libx32 project swap.img wget-log.1 wget-log.15 wget-log.20 wget-log.8
etc lost+found root sys wget-log.10 wget-log.16 wget-log.3 wget-log.9
home media run tmp wget-log.11 wget-log.17 wget-log.4
//给ls命令添加新的root目录
(base) root@ubuntu:/opt/chroot# chroot ./ls_test/ /bin/ls /
bin lib lib64
现在我们使用一下 docker 执行容器时,经常用到的/bin/bash 命令
// 创建一个新的root目录供bash命令使用
(base) root@ubuntu:/opt/chroot# mkdir new_rootfs
// 将上面的ls命令都复制到新的目录下
(base) root@ubuntu:/opt/chroot# cp -R ls_test/* new_rootfs/
*/ // 将 bash 命令复制到 new_rootfs/bin/目录下
(base) root@ubuntu:/opt/chroot# cp /bin/bash new_rootfs/bin/
// 查看bash命令依赖的库
(base) root@ubuntu:/opt/chroot# ldd /bin/bash
linux-vdso.so.1 (0x00007ffc9d9b8000)
libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007f62405ed000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f62405e7000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f62403f5000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6240753000)
// 将依赖库都复制到目录下
(base) root@ubuntu:/opt/chroot# cp /lib/x86_64-linux-gnu/libtinfo.so.6 /lib/x86_64-linux-gnu/libdl.so.2 /lib/x86_64-linux-gnu/libc.so.6 new_rootfs/lib/
(base) root@ubuntu:/opt/chroot# cp /lib64/ld-linux-x86-64.so.2 new_rootfs/lib64/
// 目录结构
(base) root@ubuntu:/opt/chroot# tree new_rootfs/
new_rootfs/
├── bin
│ ├── bash
│ └── ls
├── lib
│ ├── libc.so.6
│ ├── libdl.so.2
│ ├── libpcre2-8.so.0
│ ├── libpthread.so.0
│ ├── libselinux.so.1
│ └── libtinfo.so.6
└── lib64
└── ld-linux-x86-64.so.2
3 directories, 9 files
给 bash 命令新的 root 目录
(base) root@ubuntu:/opt/chroot# chroot new_rootfs/ /bin/bash
bash-5.0# ls -la ./*
*/ //目录结构
./bin:
total 1304
drwxr-xr-x 2 0 0 4096 Aug 3 16:44 .
drwxr-xr-x 5 0 0 4096 Aug 3 16:44 ..
-rwxr-xr-x 1 0 0 1183448 Aug 3 16:44 bash
-rwxr-xr-x 1 0 0 142144 Aug 3 16:44 ls
./lib:
total 3092
drwxr-xr-x 2 0 0 4096 Aug 3 16:46 .
drwxr-xr-x 5 0 0 4096 Aug 3 16:44 ..
-rwxr-xr-x 1 0 0 2029592 Aug 3 16:46 libc.so.6
-rw-r--r-- 1 0 0 18848 Aug 3 16:46 libdl.so.2
-rw-r--r-- 1 0 0 588488 Aug 3 16:44 libpcre2-8.so.0
-rwxr-xr-x 1 0 0 157224 Aug 3 16:44 libpthread.so.0
-rw-r--r-- 1 0 0 163200 Aug 3 16:44 libselinux.so.1
-rw-r--r-- 1 0 0 192032 Aug 3 16:46 libtinfo.so.6
./lib64:
total 196
drwxr-xr-x 2 0 0 4096 Aug 3 16:44 .
drwxr-xr-x 5 0 0 4096 Aug 3 16:44 ..
-rwxr-xr-x 1 0 0 191504 Aug 3 16:47 ld-linux-x86-64.so.2
// 使用 cat 命令试一下
bash-5.0# cat bin/ls
bash: cat: command not found
//发现并不行,这是因为我们在这个新的rootfs中只添加了ls命令
// 但是我们还可以使用bash的内置命令,`cd`、`echo`、`export` 等
bash-5.0# cd lib
bash-5.0# ls
libc.so.6 libdl.so.2 libpcre2-8.so.0 libpthread.so.0 libselinux.so.1 libtinfo.so.6
bash-5.0# echo helloworld
helloworld
// 当使用cd / 时也会到我们新的root目录下
其实到这里我们就大概明白 docker 是怎么调用 /bin/bash 命令了,应该就是 docker exec .. /bin/bash 时会使用 chroot 去为 docker 的进程中的/bin/bash 开一个新的 root 目录。
3. busybox
busybox
: 是一个集成了一百多个最常用 linux 命令和工具的软件,甚至还集成了一个 http 服务器和一个 telnet 服务器,但是占用储存很小。
像上面出现的问题,因为没有加入 cat 命令,然后 cat 就无法使用,那我们需要将所有的基础命令都一个一个加进来的话,一是很麻烦,二是占用内存很大,所以可以使用 busybox 实现 linux 基础命令的使用
获取 busybox 的方法:
- 官网下载 busybox 的包,然后手动进行编译。
- docker 运行 busybox 容器,并将 busybox 中的根目录复制出来。
我这里使用第二种:
// 创建busybox目录
(base) root@ubuntu:/opt/chroot# mkdir busybox/
// 启动busybox容器
(base) root@ubuntu:/opt/chroot# docker run -d --rm busybox sh -c "sleep 1000"
a3b7bb10695d3263fdc919745949537c02e62b1e5b4dfd91306869acecbd05d1
// 将容器中的根目录复制出来
(base) root@ubuntu:/opt/chroot# docker cp sad_maxwell:/bin /opt/chroot/busybox/
Successfully copied 1.47MB to /opt/chroot/busybox/
我们想在对比一下所占储存的大小
// 系统目录:bin目录占用393M,lib目录占用1.8G
(base) root@ubuntu:/opt/chroot/busybox# du -sh /usr/*
*/
393M /usr/bin
4.0K /usr/games
23M /usr/include
1.8G /usr/lib
4.0K /usr/lib32
4.0K /usr/lib64
140M /usr/libexec
4.0K /usr/libx32
254M /usr/local
33M /usr/sbin
225M /usr/share
264M /usr/src
// busybox目录:就只占用了1.2M
(base) root@ubuntu:/opt/chroot# du -sh busybox/*
*/
1.2M busybox/bin
// 当然系统中肯定有很多其他的命令,但是我们使用容器时,用到的命令,一般就是最基础的那些linux命令,所以这也是为什么容器中都会使用busybox中的命令
使用 busybox 添加一个创建一个新的 rootfs。
(base) root@ubuntu:/opt/chroot# chroot busybox/ /bin/sh
/ # ls -la
total 20
drwxr-xr-x 3 0 0 4096 Aug 3 19:25 .
drwxr-xr-x 3 0 0 4096 Aug 3 19:25 ..
drwxr-xr-x 2 0 0 12288 Dec 29 2021 bin
/ # veee
/bin/sh: eee: not found
/ # echo 'hello world' > hello
/ # ls -la
total 24
drwxr-xr-x 3 0 0 4096 Aug 3 19:34 .
drwxr-xr-x 3 0 0 4096 Aug 3 19:34 ..
drwxr-xr-x 2 0 0 12288 Dec 29 2021 bin
-rw-r--r-- 1 0 0 12 Aug 3 19:34 hello
/ # cat hello
hello world
/ # vi hello
/ # cat hello
hello world
hello vi
/ # exit
在这里我们就可以完整的使用 linux 最基本的命令了。
4. 检测隔离性
将宿主机中的进程挂载到新的 rootfs 中
// 创建一个proc目录
/ # mkdir proc
// 目录中没有任何文件
/ # ls -la proc/
total 8
drwxr-xr-x 2 0 0 4096 Aug 4 08:25 .
drwxr-xr-x 4 0 0 4096 Aug 4 08:25 ..
// 无法查看任何进程
/ # ps -ef
PID USER TIME COMMAND
// 将系统的进程挂载到新的rootfs下
/ # mount -t proc proc /proc/
// 查看 /proc文件夹 是有进程文件的
/ # ls /proc/ | wc -l
347
//查看进程
/ # ps -ef | tail
479750 0 0:00 tail
1339388 0 0:05 /lib/systemd/systemd --user
1339397 0 0:00 (sd-pam)
1370493 0 0:00 /root/.vscode-server/code-f1b07bd25dfad64b0167beb15359ae573aecd2cc command-shell --cli-data-dir /root/.vscode-server/cli --on-port --require-token 87f4bc72fac6
1370528 0 0:00 /bin/sh
1497443 0 0:00 /root/.vscode-server/code-f1b07bd25dfad64b0167beb15359ae573aecd2cc command-shell --cli-data-dir /root/.vscode-server/cli --on-port --require-token 9e0a5eb6aac7
1497478 0 0:00 /bin/sh
2246221 0 0:02 kubectl exec -it -n jenkins godemo-131-806d4-h13fx-nz0lq -c docker sh
2326527 111 10:54 /usr/bin/fwupdmgr refresh
3931508 0 16:16 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
现在检查一下隔离性
// 在宿主机中启动一个进程
(base) root@ubuntu:/opt/chroot# sleep 1000 &
[1] 480012
// 查看
(base) root@ubuntu:/opt/chroot# ps -ef | grep sleep
root 479780 476214 0 08:28 ? 00:00:00 sleep 180
root 480012 479830 0 08:31 pts/1 00:00:00 sleep 1000
root 480088 479830 0 08:31 pts/1 00:00:00 grep --color=auto sleep
// 进入新的rootfs下,可以看到是有这个进程的
/ # ps -ef | grep sleep
480012 0 0:00 sleep 1000
480111 0 0:00 sleep 180
480169 0 0:00 grep sleep
//现在将进程kill掉
/ # kill -9 480012
// 回到宿主机总查看,sleep 1000 的进程消失
(base) root@ubuntu:/opt/chroot# ps -ef | grep sleep
[1]+ Killed sleep 1000
root 480111 476214 0 08:31 ? 00:00:00 sleep 180
root 480277 480273 0 08:33 ? 00:00:00 sleep 1
root 480280 479830 0 08:33 pts/1 00:00:00 grep --color=auto sleep
所以我们看到进程并没有做到隔离,这在容器中是不一样的。
5. 小结
chroot
: 改变进程的根目录,是他不能访问该目录之外的其他目录。
rootfs
: 操作系统或者镜像运行的根目录,可以由 chroot 进行切换。
busybox
: 可以做小的 rootfs。
上面也只是实现了最基本的文件隔离,但是进程这些并没有进行隔离。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于