首页
如何正确使用 docker run -i、 -t、-d 参数

在使用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参数看明白了,其余两个参数的解释还是有点过于简单。伪TTYSTDIN是什么,甚至这个attach很多文章翻译成连接准确吗?我们没有具备一些操作系统的底层知识,对这些还是一脸懵逼的。

那么,我们就先讲一下我们能看懂的这个-d选项吧。

detach 选项

大多数情况下,我们都是希望 Docker 能够在后台运行容器,而不是直接在宿主机上与之交互。如果不加-d选项,容器会在宿主机终端运行,并把输出的结果(STDOUT)打印到宿主机上面。

image_1em99nlkqitv6a7mqp179hk4s9.png-50.4kB

使用-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, STDOUTSTDERR)附加到容器。

在 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。

image_1ema0o7qp11gj967n35ms411131m.png-19.1kB

那么我们可以做一个小实验。下面的语句是通过管道把宿主机的echo test命令执行的结果作为node容器的输入,在容器中执行cat命令再通过stdout输出到宿主机。可以看到打印出 test。

echo test | docker run --rm --name node -a stdin -a stdout -i node:12.18.3-slim cat

image_1ema1iu5ites1nge196u1i3q1t1k23.png-9.4kB

接下来我们不使用-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

image_1emb4pfnln5kh6bs991q4a36r9.png-11.6kB

可以看到,我们能够在宿主机上跟容器进行交互了,它会输出我们输入的结果。那么我们也可以运行/bin/bash来启动一个shell

docker run --rm --name node -i node:12.18.3-slim /bin/bash

如果我们执行的是一个持续与容器交互的程序,容器的状态就会一直保持运行状态。那么我们想结束掉与容器的交互呢?

docker attach 命令里有介绍到,使用CTRL-c可以让容器停止运行,它是通过发送一个STGKILL信号来实现的。

image_1emb7bg8d10tf1jcf1m0pbtpkni1m.png-42.4kB

但是我们尝试一下发现并不行,这是因为 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

image_1embc11vv1efmhb51q7sned171f23.png-9.8kB

可以看到容器正常启动并保持运行,我们在宿主机的终端输入任何字符都没有反应。

我们可以再开启另外一个终端,进入容器看一下 1 号进程,发现是node

$ docker exec -it node /bin/bash
$ ps -aux

image_1embccbtfkn1gjb1om61re61kv430.png-104.6kB

所以我们可以得出一个结论:如果容器启动的 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

image_1embjiati177np9t17uiuk31q5f3q.png-14.4kB

我们可以看到与-i不同的是,它会使用伪终端进入到 node 的 shell 中,但是我们输入任何字符都是没用的。关闭宿主机的终端后,容器依然运行。

这也就解释了为什么容器启动的 1 号进程是一个 shell 程序的话,我们使用-dt就可以保持容器持续在后台运行。

-it

前面我们知道了如果直接执行docker run,它默认会有 stdout 流,如果我们加了-i,它会保持 stdin 流打开。那么,我们再加上-t选项,就是说标准输入变成了一个伪 tty 终端。从这里也可以看出,交互模式下单独指定-t选项是没有意义的,因为如果容器的标准输入没有打开,我们是输入不了任何内容的。

简单来说,指定-t而不指定-i,意味着在容器里开启了一个伪终端,但是我们的输入并不会传递到伪终端的输入。

所以,官方文档也写到了,在交互模式下,-i-t选项必须结合使用,也就是-it

image_1embgldkgurj1t191201g081m6q3d.png-20kB

那么,我们使用-it选项来启动一个容器,看一下有哪些变化:

docker run --rm --name node -it node:12.18.3-slim

我们进入了node 的 shell,并且可以使用快捷键 Tab 来显示信息。

image_1embl9gtb11p18pl2j11au19v47.png-107.2kB

也可以通过CTRL-c来退出交互模式。

image_1emblccqol781u1n3rb9i43bm4k.png-30.6kB

参考:

  1. What's the difference between ^C and ^D for UNIX/Mac OS X terminal?
  2. Avoid running Node.js as PID 1 under Docker images
  3. Linux命令中Ctrl+z、Ctrl+c和Ctrl+d
  4. linux的 0号进程 和 1 号进程
  5. Termination-Signals
  6. Linux 的伪终端的基本原理 及其在远程登录(SSH,telnet等)中的应用
  7. What is a TTY on Linux? (and How to Use the tty Command)
  8. What does “the input device is not a TTY” exactly mean in “docker run” output?
  9. Confused about Docker -t option to Allocate a pseudo-TTY