了解 Docker 如何启动 process

了解 CMD 与 ENTRYPOINT 曾提到 container 即 process,那接下来就要了解 Docker 是如何启动 process 的。

exec 模式与 shell 模式

build Laravel image 有提到 CMD 在执行指令的模式有分 exec 模式与 shell 模式,先回顾这两个模式在写法上的差异如下:

# exec 模式
CMD ["php", "artisan", "serve"]
# shell 模式
CMD php artisan serve

因为是执行指令,所以 RUN 与 ENTRYPOINT 也有一样的模式。

虽然最终都会跑 php artisan serve 指令,但跑的方法不大一样。以下使用 ubuntu image 来做说明

exec 模式

简单来说,就是直接执行指令,因此会是 PID 1 process。使用 PID 1 process 的好处是,使用 docker stop 指令发出 SIGTERM 後,会是由 PID 1 process 收到,做 graceful shutdown 相容比较简单。

FROM ubuntu
CMD ["ps", "-o", "ppid,pid,user,args"]

因 CMD 设定的指令是要执行 ps -o ppid,pid,user,args,所以 ps 出来的结果,ps 指令为 PID 1。

这是官方建议的方法。

shell 模式

Shell 模式是透过 /bin/sh -c 执行指令,因此会先有 /bin/sh -c 的 PID 1 process,然後在底下开子 process。在这个情况下,docker stop 指令发出的 SIGTERM 信号将会由 shell 收到。

FROM ubuntu
CMD ps -o ppid,pid,user,args

这次 ps 指令为 PID 7。

当然,使用 shell 也是有方便的地方,它可以直接在指令上使用环境变数,比方说:

FROM node_project
CMD npm run $NODE_ENV

这在 exec 是办不到的。有些讨论会提到 exec 如果要取环境变数,可以改成下面的写法:

FROM ubuntu
CMD ["/bin/sh", "-c", "cd $HOME && ps -o ppid,pid,user,args"]

相信现在读者应该知道了,其实改成 shell 的写法就行了:

FROM ubuntu
CMD cd $HOME && ps -o ppid,pid,user,args

反过来说,exec mode 才能自行决定要用什麽 shell 来执行指令。

image 差异

上面使用 ubuntu image 做范例,说明了 exec 模式和 shell 模式的差异。但不同 image 在 shell 模式下又会不大一样,原因是 /bin/sh 在大多数 image 都是用 link 的形式连结到其他 shell:

$ docker run --rm -it ubuntu ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Jul 18  2019 /bin/sh -> dash

$ docker run --rm -it debian ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Sep  8 07:00 /bin/sh -> dash

$ docker run --rm -it centos ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Nov  8  2019 /bin/sh -> bash

$ docker run --rm -it alpine ls -l /bin/sh
lrwxrwxrwx    1 root     root            12 May 29 14:20 /bin/sh -> /bin/busybox

从上面范例可以看到,四个 image 就有三种不同的 shell:bashdashash(BusyBox)

这些 shell 执行 /bin/sh -c 在 process 表现的行为又不大一样,可以参考以下范例:

Debian 因没有内建 ps,且与 Ubuntu 一样是使用 dash,因此就不做为范例

docker run --rm -it ubuntu /bin/sh -c ps
docker run --rm -it ubuntu ps

docker run --rm -it centos /bin/sh -c ps
docker run --rm -it centos ps

docker run --rm -it alpine /bin/sh -c ps
docker run --rm -it alpine ps

这里可以发现,只有 ubuntu 才会多卡一层 process,其他不会。就这个范例来看,其实只有 dash 才会有 PID 1 process 被 shell 占走的问题。笔者建议若要使用 shell 模式的话,还是拿 base image 实验一下比较保险。

docker exec

除了 docker run 以外,还有 docker exec 也是透过 Docker 启动 process 的,我们来看看它启动会发生什麽事:

# Terminal 1
docker run --rm -it --name test alpine

# 使用 top 指令查看目前的 process
top

# Terminal 2
docker exec -it test sh

# 安装 Vim
apk add --no-cache vim

# Terminal 1 离开的时候,Terminal 2 的 process 也会跟着结束
exit 

首先 Terminal 1 启动 container 进入 shell,然後启动 top,可以看到目前 PID 1 为 /bin/sh--也就是启动 container 那时的 shell。而 top 的 PPID 是 PID 1。

接着切换 Terminal 2 使用 docker exec 进入 container 并下安装指令,这时 top 里面看到多出两个 process,一个是 docker execsh 指令 PID 7,它的 PPID 跟启动 container 的 /bin/sh 一样为 0,笔者推测这应该代表都是从 Docker 直接启动的 process。另一个 process 则是安装指令的 process。

最後 Terminal 1 执行 exit 把 PID 1 结束後,没有父子关系的 PID 7 还是一样被结束掉了。笔者推测 Docker 主要是因为要把 container 移除,所以会把里面全部的 process 都净空,只是使用什麽信号就不确定了,照范例的执行速度来看,很有可能是 SIGKILL

因笔者对发信号实际运作不熟悉,因此这里仅能做推测。

今日自我回顾

因为 container 即 process,所以了解 process 的生命周期非常重要,包括如何启动,以及什麽时候要回收资源等。


<<:  选取组件与档案组件-金鱼都能懂的Bootstrap5网页框架开发入门

>>:  DAY26-SQL语法(VIEW应用)

最小平方法 | ML#Day16

wiki页面:最小平方法 详细的内容就不在这里赘述了,这里依旧是介绍一点简单的概念。 为什麽我们要提...

Day24 - Time complexity (DS篇)

大家好我是长风青云,今天不停被打扰QAQ。是有没有要让我录影片,不是打电话过来狂CALL,就是直接呼...

Apple 音乐下载到电脑 5 个方法轻松做到【必学】

把 Apple Music 歌曲下载下来,可以让你在没有网路的时候也能收听音乐。想要下载 Apple...

[Day28] 第二十八课 Azure灾害复原(DRaaS)-1[进阶]

先前提到Azure有很多客户拿来做第三份备份存放外,异地备援陆续也有不少客户来询问 了解,原因就在异...

全端入门Day18_前端程序撰写之CSS终

昨天介绍了些F12的功能,今天要来说CSS的框架。 CSS的框架:Bootstrap 因为我目前碰的...