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

这个选项到底有什么作用?我读了很多关于TTY的文章,但我还是很困惑。我在没有-t和只有-i的情况下玩了一下,似乎期望用户输入的程序在没有-t的情况下会抛出错误。为什么启用伪tty很重要?

114333 次浏览

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的前两个字符。这个CONTAINER ID可以通过输入docker ps -a找到

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

-t-i标志分配一个伪tty并保持stdin打开 如果没有附上。这将允许您使用容器像

. sh

. sh

. sh

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

我对-t的了解如下:

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

但问题是当我想恢复数据库时。

通常我做__abc0 -这里我在容器中执行mysql命令并获得一个交互式终端。

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

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

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

最后一个可行。希望这能帮助到人们。

-t选项指向Unix/Linux如何处理终端访问。在过去,终端是硬线连接,后来是基于调制解调器的连接。它们具有物理设备驱动程序(它们是真正的设备部件)。一旦广义网络投入使用,伪终端驱动程序就被开发出来。这是因为它在理解可以使用哪些终端功能而不需要直接将其写入程序之间创建了一个分离(阅读sttycurses中的手册页)。

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

-it指示Docker分配一个伪tty连接到容器的stdin,在容器中创建一个交互式bash shell。

--interactive-i false保持STDIN打开,即使没有附加

--tty-t false分配伪tty

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

回答晚了,但也许能帮到别人

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

所以

  • docker run -i alpine cat给你一个空行等待输入。输入“hello”,你会得到一个回音“hello”。容器在发送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连接到一个终端输入时,这个输入并不连接到你的输入。你输入的“hello”没有到达cat的输入。cat正在等待从未到达的输入。
  • echo "hello" | docker run -t alpine cat也会给你一个空行,不会退出CTRL-D上的容器,但你不会得到一个回显“hello”,因为你没有传递-i

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

现在,一起为-it。这告诉cat它的输入是一个终端,同时将这个终端连接到docker run的输入,这是一个终端。docker run/exec将确保它自己的输入在传递给cat之前实际上是一个tty。这就是为什么如果你尝试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特定的行为,如屏蔽输入或为输出着色。

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

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

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

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

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

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

  • -i添加一个stdin流(即,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(即,没有分配流)。

这里的大多数答案都是很好的概念性答案,但我发现他们遗漏了太多细节,让我坐在电脑前无法使用这些信息。艾哈迈德Gnomin的答案正在成为程序化的,但让我们试着进一步推动它。

首先讲一点理论

TTY解密了中的两个图像是关键:

enter image description here

我不能声称完全理解了这幅图,但这里的关系是当xterm(或ubuntu中的gnome-terminal;以其中一个“用户过程”为代表;打开,它启动一个bash(或任何默认shell),然后通过内核伪终端(PTY)主和从向它发送键盘输入:

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

enter image description here

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

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

关键信息是TTY和stdin、stdout、stderr行。这表明每个进程都与TTY(电传终端)相关联,并且它们的3个流(stdin, stdout, stderr)非常自然地与这个TTY相关联,除非是管道或重定向(注意管道ls | sort将ls的stdout与sort的stdin相关联)。

现在来测试一下这个理论

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

>>> tty
/dev/pts/2

Bash因此与PTY从机2相关联(这可能意味着有另一个打开的终端,与主/从机对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

这是有意义的,因为-t 分配伪终端

-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 文档提到-a“附加到作为输入传递的流”,但我还没有找到解释这意味着什么,以及它如何与-i选项相关。