对分配伪 TTY 的 Docker -t 选项感到困惑

Posted

技术标签:

【中文标题】对分配伪 TTY 的 Docker -t 选项感到困惑【英文标题】:Confused about Docker -t option to Allocate a pseudo-TTY 【发布时间】:2015-07-20 03:43:16 【问题描述】:

这个选项到底有什么作用?我在 TTY 上读了很多书,但我仍然很困惑。我在没有-t-i 的情况下玩耍,似乎期望用户输入的程序在没有-t 的情况下抛出错误。为什么启用伪 TTY 很重要?

【问题讨论】:

【参考方案1】:

-t 选项用于说明 Unix/Linux 如何处理终端访问。过去,终端是硬线连接,后来是基于调制解调器的连接。这些具有物理设备驱动程序(它们是真实的设备)。一旦通用网络开始使用,就开发了一个伪终端驱动程序。这是因为它在理解可以使用哪些终端功能而无需直接将其写入程序(阅读sttycurses 上的手册页)之间产生了分离。

因此,以它为背景,运行一个没有选项的容器,默认情况下你有一个标准输出流(所以docker run | <cmd> 有效);使用-i 运行,你会添加标准输入流(所以<cmd> | docker run -i 有效);使用-t,通常是-it 的组合,并且您添加了一个终端驱动程序,如果您正在与进程交互,这可能是您想要的。它基本上使容器启动看起来像一个终端连接会话。

【讨论】:

这应该是最佳答案。虽然这不是最技术性的,但它解释了-it 标志的基本行为。 同意克里斯。我阅读了其他答案,但仍然完全困惑。这个答案清除了它。 是的,值得一提的是,“TTY”本身是来自“teletypewriter”(又名“teleprinter”)单词的首字母缩略词,它是允许您输入文本并将其发送出去的设备名称同时 - 就像电话发短信一样 ;-) 试试 docker run -i ubuntudocker run -it ubuntu 你会立即看到区别。 “-i”允许您让容器等待来自主机的交互,但在您使用标志“-t”“分配 tty 驱动程序”后,可以从控制台(终端)进行实际交互。 我可以在 docker 中启动 tty 吗?我有一些应用程序停止工作,我没有使用-t 运行 docker,但我无法在生产中修改 docker start 命令。所以我需要让应用认为它是以-t开头的。【参考方案2】:

迟到的答案,但可能对某人有所帮助

docker run/exec -i 会将容器内命令的 STDIN 连接到 docker run/exec 本身的 STDIN。

所以

docker run -i alpine cat 给你一个空行等待输入。输入“你好”,你会得到一个回声“你好”。在您发送 CTRL+D 之前,容器不会退出,因为主进程 cat 正在等待来自无限流的输入,即 docker run 的终端输入. 另一方面,echo "hello" | docker run -i alpine cat 将打印“hello”并立即退出,因为cat 注意到输入流已结束并自行终止。

如果您在退出上述任一方法后尝试docker ps,您将找不到任何正在运行的容器。在这两种情况下,cat 本身已经终止,因此 docker 已经终止了容器。

现在对于“-t”,这告诉 docker 内部的主进程它的输入是终端设备。

所以

docker run -t alpine cat 会给你一个空行,但如果你尝试输入“hello”,你将不会得到任何回声。这是因为cat 连接到终端输入时,此输入未连接到您的输入。您输入的“你好”没有到达cat 的输入。 cat 正在等待从未到达的输入。 echo "hello" | docker run -t alpine cat 也会给你一个空行,并且不会在 CTRL-D 上退出容器,但你不会得到 echo "hello" 因为你没有通过-i

如果你发送 CTRL+C,你会得到你的 shell,但是如果你现在尝试docker ps,你会看到cat 容器仍在运行。这是因为cat 仍在等待从未关闭的输入流。如果不与-i 结合使用,我还没有发现单独使用-t 有任何有用的用途。

现在,-it 在一起。这告诉 cat 它的输入是一个终端,同时将此终端连接到 docker run 的输入,这是一个终端。 docker run/exec 将确保它自己的输入实际上是一个 tty,然后再将其传递给 cat。这就是为什么如果你尝试echo "hello" | docker run -it alpine cat,你会得到一个input device is not a TTY,因为在这种情况下,docker run 的输入本身是来自先前回显的管道,而不是执行docker run 的终端

最后,如果-i 可以将您的输入连接到cat 的输入,那么您为什么需要传递-t?这是因为如果输入是终端,命令会以不同的方式处理输入。这也是最好的例子说明

docker run -e mysql_ROOT_PASSWORD=123 -i mariadb mysql -u root -p 会给你一个密码提示。如果您输入密码,字符会以可见的方式打印出来。 docker run -i alpine sh 会给你一个空行。如果您键入 ls 之类的命令,您会得到输出,但不会得到提示或彩色输出。

在最后两种情况下,您会出现这种行为,因为 mysqlshell 没有将输入视为 tty,因此没有使用 tty 特定的行为,例如屏蔽输入或着色输出。

【讨论】:

这里的最佳答案让我真正了解-t-i 选项的作用! 很棒的答案,预料到了我遇到的每一个问题 @Ahmed Ghonim。晶莹剔透。但是 docker run -a=stdin alpine cat 呢? @HKIIT "-a=stdin" 将标准输入流附加到容器,但没有分配内存。是 -i 标志在容器中为 stdin 流分配缓冲内存,因此描述“即使未附加也保持 STDIN 打开”,当传递 -i 时,无论附加标志如何,都会为 stdin 分配内存。没有这个分配的内存读取到标准输入是空的/eof。您还需要包含“-a=stdout”以查看来自 cat 命令的响应,例如:“docker run -i -a=stdin -a=stdout alpine cat”...当然没有必要这样做你可以只需运行“docker run -i alpine cat”。 “我没有发现单独的 -t 没有与 -i 结合有任何有用的用途”例如,你可以运行 docker run -t -d image sh 并且你会得到一个 docker 容器运行但什么都不做。您可以通过这种方式使用任何需要输入的命令,而不是在 docker 容器内使用 yes > /dev/null 命令来让它运行而不做任何事情。不过,我看不出这有什么用处。【参考方案3】:

根据 Google 搜索,-t 参数没有得到很好的记录,或者很多人经常提到。

当您通过在 Bash 提示符下键入 docker(最新版本为 1.8.1)来显示(应该是)所有 docker 客户端参数的列表时,它甚至不会显示。

事实上,如果您尝试通过键入docker -t --help 来获得有关此论点的具体帮助,如果给出了令人惊讶的模糊回复:

提供但未定义的标志:-t

所以,你不能因为对这个论点感到困惑而受到责备!

在 Docker 在线文档中提到它是“分配一个伪 tty”,并且经常与 -i 一起使用:

https://docs.docker.com/reference/run/

我在文档中看到它以下列方式用于极好的 jwilder/nginx-proxy docker 容器:

docker run -d -p 80:80 --name nginx -v /tmp/nginx:/etc/nginx/conf.d -t nginx

在这种情况下,它所做的是将输出发送到此 docker 容器内的“虚拟”tty(Bash 命令提示符/终端)。然后,您可以通过运行 docker 命令 docker logs CONTAINER 来查看此输出,其中 CONTAINER 是此容器 ID 的前几个字符。输入docker ps -a可以找到这个CONTAINER ID

我在下面的链接中看到了这个-t 参数,它说

-t-i 标志分配一个伪 tty 并保持标准输入甚至打开 如果没有附上。这将允许您像使用容器一样 传统 VM 只要 bash 提示符正在运行。

https://coreos.com/os/docs/latest/getting-started-with-docker.html

我希望这会有所帮助!我不确定为什么没有记录或使用太多。也许它是实验性的,将在即将发布的版本中作为文档化功能实现。

【讨论】:

文档显示为docker run --help,而不是docker -t --help-t, --tty=false Allocate a pseudo-TTY"【参考方案4】:

这里的大多数答案都是很棒的概念性答案,但我发现它们遗漏了太多细节,以至于我无法坐在电脑前使用这些信息。 Ahmed Gnomin 的答案正在走向程序化,但让我们尝试更进一步。

先说一点理论

The TTY Demystified 中的两张图片是关键:

我不能声称完全理解这张图,但这里感兴趣的关系是当 xterm(或 ubuntu 中的 gnome-terminal;由上图中的“用户进程”气泡之一表示)打开时,它启动一个 bash(或任何默认 shell),然后通过内核伪终端 (PTY) 主从设备向它发送键盘输入:

xterm -> ptmx (pty master) -> pts (pty slave) -> bash

第二张图片代表这个简短的 bash 会话中涉及的进程:

>>> cat
>>> ls | sort
...

信息的关键位是 TTY 和 stdin、stdout、stderr 行。这表明每个进程都与一个 TTY(电传终端)相关联,并且它们的 3 个流(stdin、stdout、stderr)非常自然地与这个 TTY 相关联,除了管道或重定向的情况(注意管道 @987654331 @ 将 ls 的标准输出关联到排序的标准输入)。

现在来测试一下这个理论

我们可以通过输入tty找到bash使用的伪终端:

>>> tty
/dev/pts/2

因此,Bash 与 2 号 PTY 从站相关联(这可能意味着有另一个终端打开,与主/从对 1 相关联)。我们还可以获取 bash 的 stdin、stdout 和 stderr 流:

>>> ls -l /proc/$$/fd
lrwx------ 1 samlaf samlaf 64 Jun 17 21:50 0 -> /dev/pts/2
lrwx------ 1 samlaf samlaf 64 Jun 17 21:50 1 -> /dev/pts/2
lrwx------ 1 samlaf samlaf 64 Jun 17 21:50 2 -> /dev/pts/2

确实,它们都与 bash 的天然 TTY slave 有关。 ($$ 是一个 bash 变量,它返回 bash 的 PID。我们同样可以通过使用 ps 并手动输入来找到它。

最后用这个理论来回答最初的 Docker 问题

我们重现了上述步骤,但这次是在 docker 容器中:

>>> docker run --rm -t ubuntu tty
/dev/pts/0
>>> docker run --rm ubuntu tty
not a tty

这是有道理的,因为-tallocates a pseudo-terminal。

-i 相关的命令更难解释。

>>> docker run --rm ubuntu bash -c "ls -l /proc/\$\$/fd"
lrwx------ 1 root root 64 Jun 18 02:37 0 -> /dev/null
l-wx------ 1 root root 64 Jun 18 02:37 1 -> pipe:[9173789]
l-wx------ 1 root root 64 Jun 18 02:37 2 -> pipe:[9173790]
>>> docker run --rm -t ubuntu bash -c "ls -l /proc/\$\$/fd"
lrwx------ 1 root root 64 Jun 18 02:39 0 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 1 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 2 -> /dev/pts/0
>>> docker run --rm -it ubuntu bash -c "ls -l /proc/\$\$/fd"
lrwx------ 1 root root 64 Jun 18 02:39 0 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 1 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 2 -> /dev/pts/0

我仍然无法弄清楚 -i 到底做了什么……我希望得到一些帮助! 我能找到的唯一有趣的命令似乎有所区别:

>>> docker run --rm -a stdout -i ubuntu bash -c "ls -l /proc/\$\$/fd"
lr-x------ 1 root root 64 Jun 18 02:43 0 -> pipe:[9199896]
l-wx------ 1 root root 64 Jun 18 02:43 1 -> pipe:[9199897]
l-wx------ 1 root root 64 Jun 18 02:43 2 -> pipe:[9199898]
>>> docker run --rm -a stdout ubuntu bash -c "ls -l /proc/\$\$/fd"
lrwx------ 1 root root 64 Jun 18 02:43 0 -> /dev/null
l-wx------ 1 root root 64 Jun 18 02:43 1 -> pipe:[9197938]
l-wx------ 1 root root 64 Jun 18 02:43 2 -> pipe:[9197939]

Docker documentation 提到 -a “附加到作为输入传递的流”,但我无法找到解释这意味着什么,以及它与 -i 选项的关系。

【讨论】:

虽然这很有趣,但它并没有真正回答“这是有道理的,因为 -t 分配了一个伪终端”。正是手册所说的;)“-i”允许您与容器交互,即键入容器拾取的内容。如果您正在运行 bash 等交互式应用程序,则需要它。 你能“告诉”我“-i”是做什么的吗? aka 你能运行一个带有和不带有“-i”的容器,并告诉我某些属性在某处发生了变化。 “允许你与容器交互”在实际运行过程中是如何体现的? 如果您阅读其他答案应该会很清楚,但您也可以通过比较docker run -ti alpine shdocker run -t alpine sh 的结果来快速查看。后者只是退出,因为您需要 stdin (-i) 才能使用 shell。 这是有道理的。但在我的电脑上,它并没有退出,它只是处于不确定状态;我可以继续在终端上打字,但似乎什么都没有发送。但我还是不明白为什么,因为docker run --rm -t ubuntu bash -c "ls -l /proc/\$\$/fd" 显示stdin 连接到/dev/pts/0。【参考方案5】:

-it 组合选项称为交互式模式。

默认情况下,容器只有一个标准输出流(即docker run | CMD 有效),要与我们的容器交互,我们需要这些选项:

-i 添加标准输入流(即CMD | docker run 有效); -t 分配一个伪 TTY 主/从对,其中从属部分与容器中正在运行的进程相关联,而主部分与您的 docker 命令相关联。

stdin 流将容器附加到 shell 的 stdin(Docker 继承了 shell 的 stdin 流),而 TTY 行规则使您能够以键盘方式与容器交互。

TTY 行规则由内核提供给 TTY 设备的低级特性组成,例如编辑缓冲区和基本行编辑命令。

如下图,可以通过以下命令查看标准文件描述符:

docker run --rm -i ubuntu sh -c "ls -l /proc/\$\$/fd"

如果您删除 -i,您将看到 stdin 指向 /dev/null(即没有分配流)。

【讨论】:

【参考方案6】:

我对@9​​87654321@ 的了解如下:

docker exec -ti CONTAINER bash - 允许我在容器中“登录”。感觉就像 ssh-ing(不是)。

但问题出在我想恢复数据库时。

通常我做docker exec -ti mysql.5.7 mysql - 这里我在容器中执行mysql命令,得到一个交互式终端。

我在前面的命令中添加了<dump.sql,这样我就可以恢复一个数据库。但是 cannot enable tty mode on non tty input 失败了。

删除-t 有帮助。还是不明白为什么:

docker exec -i mysql.5.7 mysql < dump.sql

最后一个有效。希望这对人们有所帮助。

【讨论】:

我可以在 docker 中启动 tty 吗?我有一些应用程序停止工作,我没有使用-t 运行 docker,但我无法在生产中修改 docker start 命令。所以我需要让应用认为它是以-t开头的。【参考方案7】:

每个进程都有三个数据流,即STDIN/ STDOUT/ STDERR。当一个进程在容器中运行时,默认情况下终端连接到容器中运行的进程的 STDOUT 流。因此,在终端中运行docker run 命令时,所有输出流都将可见。但是如果你想为容器中正在运行的进程提供输入,那么你必须连接到进程的 STDIN 通道,这不是默认的,而是通过docker run -i 命令完成的。

-t 用于交互式/格式化输入操作。

【讨论】:

【参考方案8】:

在linux中,当你运行一个命令时,你需要一个终端(tty)来执行它。

所以当你想连接到 docker(或者在 docker 容器中运行命令)时,你必须提供选项 -t 考虑到 docker 容器内的终端。

【讨论】:

我不知道你为什么认为你需要一个 tty。许多程序在没有 tty 的情况下调用其他可执行文件。在这种情况下,STDIN/STDOUT 只是普通的输入/输出流。【参考方案9】:

-it 指示 Docker 分配一个连接到容器标准输入的伪 TTY,在容器中创建一个交互式 bash shell。

--interactive, -i false 即使未连接,也保持 STDIN 打开

--tty, -t false 分配一个伪 TTY

https://docs.docker.com/engine/reference/commandline/run/

【讨论】:

以上是关于对分配伪 TTY 的 Docker -t 选项感到困惑的主要内容,如果未能解决你的问题,请参考以下文章

如果分配了伪 tty,为啥通过 ssh 运行后台任务会失败?

Linux 伪终端(pty)

stty(set tty)

docker exec -it 返回“无法在非 tty 输入上启用 tty 模式”

如何进入已经使用新 TTY 运行的 Docker 容器

为啥我们要在 docker 中同时使用 --detach 开关和 --interactive 和 --tty ?