这个问题有点历史渊源了,其实如果玩过早期的docker的话,可能会记得,docker 最开始的时候是没有shim 进程的。那时候所有的容器进程都挂在dockerd进程下面,也就是说,如果dockerd 重启了,那么所有的容器都得跟着挂了。
还是IT 界那句俗话,没有什么东西是加一层不能解决的。那么为了解决这个问题,就得容器进程找个”爹“,这个爹就是shim。shim 会调用runc 启动容器,容器的进程的父ID 就是shim进程。
我们可以通过一个Demo 演示一下shim的作用。之前的文章已经单独分享过 runc了,感兴趣的可以翻看一下。我们通过runc 启动容器。启动前我们简单介绍一下容器里面的运行的脚本
#!/bin/sh
function report_sigpipe() {
echo "got SIGPIPE, exiting..." > /var/trap.log;
exit 1;
}
trap report_sigpipe SIGPIPE;
while sleep 1
do
echo "time is $(date)";
done
代码非常简单,就是死循环,输出时间,然后sleep 1s。如果接到 SIGPIPE 信号则退出。
初始化rootfs
$ mkdir -p container1/rootfs
$ cd container1
$ sudo bash -c 'docker export $(docker create busybox) | tar -C rootfs -xvf -'
$ runc spec
修改启动命令为上面的脚本,直接粘贴规律
$ cat > rootfs/entrypoint.sh <<EOF
#!/bin/sh
function report_sigpipe() {
echo "got SIGPIPE, exiting..." > /var/trap.log;
exit 1;
}
trap report_sigpipe SIGPIPE;
while sleep 1
do
echo "time is $(date)";
done
EOF
$ sed -i 's/"sh"/"sh", "entrypoint.sh"/' config.json
$ sed -i 's/"terminal": true/"terminal": false/' config.json
通过 detach 方式启动容器
$ sudo runc run --detach cont1-detached
上面是最后一行是通过命令行方式启动,接下来我们模仿docker 通过一个简单go 程序调用runc 命令启动容器。
package main
import (
"fmt"
"os"
"os/exec"
"strings"
)
func main() {
// 创建一个管道
rd, wr, err := os.Pipe()
if err != nil {
panic(err)
}
defer rd.Close()
defer wr.Close()
// 启动runc
fmt.Println("Launching runc")
cmd := exec.Command("runc", "run", "--detach", os.Args[1])
cmd.Stdout = wr
if err := cmd.Run(); err != nil {
panic(err)
}
// 读标准输出
buf := make([]byte, 1024)
for i := 0; i < 10; i++ {
n, err := rd.Read(buf)
if err != nil {
panic(err)
}
output := strings.TrimSuffix(string(buf[:n]), "\n")
fmt.Printf("Container produced: [%s]\n", output)
}
// 退出
fmt.Println("We are done, exiting...")
}
运行程序可以看到如下输出
$ sudo `which go` run main.go cont2
Launching runc
Container produced: [time is Sun Dec 15 16:34:25 UTC 2019]
Container produced: [time is Sun Dec 15 16:34:25 UTC 2019]
Container produced: [time is Sun Dec 15 16:34:25 UTC 2019]
Container produced: [time is Sun Dec 15 16:34:25 UTC 2019]
Container produced: [time is Sun Dec 15 16:34:25 UTC 2019]
Container produced: [time is Sun Dec 15 16:34:25 UTC 2019]
Container produced: [time is Sun Dec 15 16:34:25 UTC 2019]
Container produced: [time is Sun Dec 15 16:34:25 UTC 2019]
Container produced: [time is Sun Dec 15 16:34:25 UTC 2019]
Container produced: [time is Sun Dec 15 16:34:25 UTC 2019]
We are done, exiting...
很奇怪吧,当我们读IO 程序退出后,即便是通过 --detach 的方式启动的runc 容器也跟着退出了。所以为了让这个容器一直运行,我们就得保证我们的shim 一直运行,并且接收容器的标准输出,并写到一个文件里面。这样后续的 docker log 或者 kubectl log 才能正常读取这些标准输出。
除此之外shim 还承担着 docker exec启动进程,以及回收子进程的功能,避免产生僵尸进程。