在使用docker run
的时候,我们经常会使用到-i、-t
和-d
参数,那么这几个参数的作用究竟是什么呢,这篇文章就来简单分析一下。
选项说明
官方文档对这几个选项的说明如下:
--interactive , -i Keep STDIN open even if not attached
--tty , -t Allocate a pseudo-TTY
--detach , -d Run container in background and print container ID
-d
是--detach
的简写,它的作用是在后台运行容器,并且打印容器id-t
是--tty
的简写,它的作用是分配一个伪TTY-i
是--interactive
的简写,它的作用是即使没有attached,也要保持 STDIN 打开状态
对于官方的解释,表示只有-d
参数看明白了,其余两个参数的解释还是有点过于简单。伪TTY
、STDIN
是什么,甚至这个attach
很多文章翻译成连接
准确吗?我们没有具备一些操作系统的底层知识,对这些还是一脸懵逼的。
那么,我们就先讲一下我们能看懂的这个-d
选项吧。
detach 选项
大多数情况下,我们都是希望 Docker 能够在后台运行容器,而不是直接在宿主机上与之交互。如果不加-d
选项,容器会在宿主机终端运行,并把输出的结果(STDOUT)打印到宿主机上面。
使用-d
选项后,容器会在后台执行并输出容器 ID。我们可以使用docker logs [container]
来查看容器的输出结果。如果想跟容器进行交互,可以使用docker exec -it [container] /bin/bash
来操作。
需要注意的是,容器是否会持久运行,和
-d
选项无关。关于这一点,在docker run 如何让容器启动后不会自动停止这篇文章中也有介绍。
interactive 选项
选项详解
-i
选项根据文档的解释就比较费解了,我们不妨先看另外一个选项-a
。
--attach , -a Attach to STDIN, STDOUT or STDERR
它的意思是把宿主机标准输入,输出和错误流(也就是STDIN
, STDOUT
和STDERR
)附加到容器。
在 Linux 中,STDIN
是标准的输入流,通常对应终端的键盘;STDOUT
是标准输出流,STDERR
是标准错误输出流,它们都对应终端的屏幕。进程将从标准输入文件中得到输入数据,将正常输出数据输出到标准输出文件,而将错误信息送到标准错误文件中。关于这方面的更多了解可以参照 What Are stdin, stdout, and stderr on Linux? 这篇文章。
那么我们再来看-i
的解释:Keep STDIN open even if not attached
。
Keep STDIN open
也就是说让容器的标准输入保持打开状态,even if not attached
强调的是即使没有把宿主机标准输入,输出和错误流附加到容器依然保持容器的标准输入为打开状态。
很多人比较费解的都是后半句,其实它只是一个强调作用。在 Docker run reference 中有一段提到如果没有指定-a
选项,默认会 attach stdout 和 stderr。
那么我们可以做一个小实验。下面的语句是通过管道把宿主机的echo test
命令执行的结果作为node
容器的输入,在容器中执行cat
命令再通过stdout
输出到宿主机。可以看到打印出 test。
echo test | docker run --rm --name node -a stdin -a stdout -i node:12.18.3-slim cat
接下来我们不使用-a
选项,得到的也是一样的结果。
echo test | docker run --rm --name node -i node:12.18.3-slim cat
这也就是说,如果加了-i
选项,容器的STDIN
就会一直保持打开,我们就可以以交互模式(interactive mode)来运行容器了。
交互模式
我们可以让容器运行后执行一条命令,比如前面提到的cat
,看一下会发生什么:
docker run --rm --name node -i node:12.18.3-slim cat
可以看到,我们能够在宿主机上跟容器进行交互了,它会输出我们输入的结果。那么我们也可以运行/bin/bash
来启动一个shell
docker run --rm --name node -i node:12.18.3-slim /bin/bash
如果我们执行的是一个持续与容器交互的程序,容器的状态就会一直保持运行状态。那么我们想结束掉与容器的交互呢?
在 docker attach 命令里有介绍到,使用CTRL-c
可以让容器停止运行,它是通过发送一个STGKILL
信号来实现的。
但是我们尝试一下发现并不行,这是因为 Linux 1号进程的特殊性。不过,我们可以通过CTRL-d
去停止容器,因为它不是发送一个信号,而是表示一个特殊的二进制值EOF
其实这里又涉及到了几个 Linux 的知识点:
- Linux PID 1(Linux 的 1 号进程)
- Linux 的信号(SIGINT、SIGTERM、SIGQUIT、SIGKILL、SIGHUP)
- EOF
如果想让CTRL-c
也可以生效,我们可以在docker run
命令加上--init
选项
docker run --rm --init --name node -i node:12.18.3-slim /bin/bash
对上面提到的几个知识做了一定了解后,我们再来看直接通过-i
启动一个容器而不执行命令会发生什么。
docker run --rm --name node -i node:12.18.3-slim
可以看到容器正常启动并保持运行,我们在宿主机的终端输入任何字符都没有反应。
我们可以再开启另外一个终端,进入容器看一下 1 号进程,发现是node
$ docker exec -it node /bin/bash
$ ps -aux
所以我们可以得出一个结论:如果容器启动的 1 号进程是一个 shell 程序的话,我们通过-i
选项就可以与之交互,保持容器持续运行。不指定-i
,容器启动后就会自动退出。
现在还有一个问题就是,如果只指定了-i
选项,我们在宿主机终端输入任何字符都没有反应。这个问题,我们可以用-t
来解决,所以接下来就介绍一下-t
选项的作用了。
tty 选项
如果对 Linux 的 TTY 比较熟悉的话,对于这个选项就比较了解了。我们可以把它理解为 Linux 的终端程序,所以-t
选项就可以理解为为容器分配一个伪 tty 终端并绑定到容器的标准输入上,之后用户就可以通过终端来控制容器了。
一般-t
都是与-i
一起出现的,也就是-it
。在讲这个之前,我们先单独分析一下-t
吧。
我们首先试一下运行容器后执行一个交互命令,看与-i
的区别是什么:
docker run --rm --name node -t node:12.18.3-slim cat
执行完后,容器也可以持续运行。与单独指定-i
不一样的是,在终端中输入任何字符都没有反应;CTRL-d
终止不了容器;直接关闭宿主机的终端,容器还继续保持运行。
我们来解释一下执行的结果为什么是这样的。首先,输入任何字符都没有反应,是因为容器没有打开标准输入,我们输入的任何字符都是没有传递进容器的。所以cat
一直会在等待永远不会到来的输入,也就一直保持容器运行状态。CTRL-d
同样也不会传递到容器,直接关闭宿主机的终端也是同样的道理,因为它是在容器内部分配了一个伪终端。所以,我们关闭宿主机的终端后,开启一个新的终端输入docker ps
,可以看到容器仍然在运行。
通过-t
选项直接运行一个容器也是一样的结果。
docker run --rm --name node -t node:12.18.3-slim
我们可以看到与-i
不同的是,它会使用伪终端进入到 node 的 shell 中,但是我们输入任何字符都是没用的。关闭宿主机的终端后,容器依然运行。
这也就解释了为什么容器启动的 1 号进程是一个 shell 程序的话,我们使用-dt
就可以保持容器持续在后台运行。
-it
前面我们知道了如果直接执行docker run
,它默认会有 stdout 流,如果我们加了-i
,它会保持 stdin 流打开。那么,我们再加上-t
选项,就是说标准输入变成了一个伪 tty 终端。从这里也可以看出,交互模式下单独指定-t
选项是没有意义的,因为如果容器的标准输入没有打开,我们是输入不了任何内容的。
简单来说,指定-t
而不指定-i
,意味着在容器里开启了一个伪终端,但是我们的输入并不会传递到伪终端的输入。
所以,官方文档也写到了,在交互模式下,-i
与-t
选项必须结合使用,也就是-it
。
那么,我们使用-it
选项来启动一个容器,看一下有哪些变化:
docker run --rm --name node -it node:12.18.3-slim
我们进入了node 的 shell,并且可以使用快捷键 Tab 来显示信息。
也可以通过CTRL-c
来退出交互模式。
参考:
- What's the difference between ^C and ^D for UNIX/Mac OS X terminal?
- Avoid running Node.js as PID 1 under Docker images
- Linux命令中Ctrl+z、Ctrl+c和Ctrl+d
- linux的 0号进程 和 1 号进程
- Termination-Signals
- Linux 的伪终端的基本原理 及其在远程登录(SSH,telnet等)中的应用
- What is a TTY on Linux? (and How to Use the tty Command)
- What does “the input device is not a TTY” exactly mean in “docker run” output?
- Confused about Docker -t option to Allocate a pseudo-TTY