为什么在一个独立的Docker容器中运行Python应用程序时没有任何输出?

324
我有一个Python(2.7)应用程序,在我的dockerfile中启动。
CMD ["python","main.py"]

main.py启动时打印一些字符串,然后进入一个循环:

print "App started"
while True:
    time.sleep(1)

只要我使用-it标志启动容器,一切都按预期工作:
$ docker run --name=myapp -it myappimage
> App started

我之后可以通过日志看到相同的输出。
$ docker logs myapp
> App started

如果我尝试使用-d标志运行相同的容器,容器似乎正常启动,但我看不到任何输出。
$ docker run --name=myapp -d myappimage
> b82db1120fee5f92c80000f30f6bdc84e068bafa32738ab7adb47e641b19b4d1
$ docker logs myapp
$ (empty)

但是容器似乎仍在运行;
$ docker ps
Container Status ...
myapp     up 4 minutes ... 

附件也没有显示任何内容。
$ docker attach --sig-proxy=false myapp
(working, no output)

有什么想法出了问题吗?在后台运行时,“print”的行为是否不同?
Docker版本:
Client version: 1.5.0
Client API version: 1.17
Go version (client): go1.4.2
Git commit (client): a8a31ef
OS/Arch (client): linux/arm
Server version: 1.5.0
Server API version: 1.17
Go version (server): go1.4.2
Git commit (server): a8a31ef
14个回答

576

终于我找到了一种方法,在Docker中运行守护程序时查看Python输出,感谢@ahmetalpbalkan在GitHub上的帮助。这里亲自回答以供日后参考:

使用无缓冲输出来

CMD ["python","-u","main.py"]

取代

CMD ["python","main.py"]
解决了该问题;您现在可以通过(包括stderr和stdout)查看输出。
docker logs myapp

为什么要使用 -u 参数?参考链接

- print is indeed buffered and docker logs will eventually give you that output, just after enough of it will have piled up
- executing the same script with python -u gives instant output as said above
- import logging + logging.warning("text") gives the expected result even without -u

python -u是什么意思 ref. > python --help | grep -- -u

-u     : force the stdout and stderr streams to be unbuffered;

8
您好,-u 对我来说似乎可用,但是有没有某个文档描述它到底是做什么的? - Little geek
17
如其他答案所建议,如果 -u 标志不起作用,您可以尝试设置环境变量 ENV PYTHONUNBUFFERED=0 - Farshid T
2
这也是我的问题。有关更详细的解释,请参见https://dev59.com/cGAg5IYBdhLWcg3wfrEn#24183941。 - Jonathan Stray
3
在Python3上运行得非常好,而设置PYTHONUNBUFFERED=0并没有帮助。 - Lech Migdal
2
谢谢,这帮助我们开始在生产环境中进行调试。 - Yash Gupta
显示剩余4条评论

156

对我而言,使用 -u 运行 Python 并没有改变任何内容。然而,起作用的是将 PYTHONUNBUFFERED=1 设置为环境变量:

在我的情况下,使用 -u 在 Python 中运行并没有产生任何变化。然而,关键之处在于将 PYTHONUNBUFFERED=1 设置为环境变量:

docker run --name=myapp -e PYTHONUNBUFFERED=1 -d myappimage

[编辑]: 根据Lars的评论,将PYTHONUNBUFFERED=0更新为PYTHONUNBUFFERED=1。这不会改变行为并增加了清晰度。


7
对于我的情况,添加-e PYTHONUNBUFFERED=0有所帮助。 - David Ng
1
谢谢!我一直在为这个问题苦恼了好几个小时,即使使用了“-u”参数也无法让日志正常工作。你的解决方案在Docker for Mac上成功解决了我的问题,我正在使用Django。 - Someguy123
2
我认为这是一个更好的解决方案,我们不必重新构建Docker镜像就可以看到输出结果。 - FF0605
5
非常感谢,值得一提的是,根据文档,这只需要是一个非空字符即可正常工作。PYTHONUNBUFFERED - A Star
19
PYTHONUNBUFFERED=0 这个语句是具有误导性的,因为它暗示着不会进行缓冲。实际上,它被启用了,因为 Python 会寻找一个“非空”的字符串。所以,最好使用 PYTHONUNBUFFERED=1,这样可以产生相同的效果,而且不会导致错误的假设。 - Lars Blumberg
显示剩余2条评论

43
如果您想要在运行docker-compose up时将打印输出添加到Flask输出中,请将以下内容添加到您的Docker Compose文件中。
web:
  environment:
    - PYTHONUNBUFFERED=1

https://docs.docker.com/compose/environment-variables/


这是一个指向Docker Compose环境变量文档的链接。

有人能详细解释一下为什么这个变量在 Dockerfile 中定义时不起作用,而在 docker-compose 文件中却可以?我原以为这不应该有影响,但它却像魔法一样奏效了! - YetAnotherDuck
@0x78f1935 你是否在使用 ARG 指令而不是 ENV 指令?它应该在 Dockerfile 中起作用。 - sox with Monica
1
对我不起作用 :/ 有什么想法为什么它可能不起作用吗? - Tobias Wilfert

39
请参考此文章,其中详细解释了该行为的原因:
引用:

通常有三种缓冲模式:

  • 如果文件描述符是无缓冲的,则根本不会发生缓冲,并且读取或写入数据的函数调用会立即发生(并将阻塞)。
  • 如果文件描述符是完全缓冲的,则使用固定大小的缓冲区,并且读取或写入调用仅从缓冲区中读取或写入。直到缓冲区填满后才刷新缓冲区。
  • 如果文件描述符是行缓冲的,则缓冲区会等待看到换行符。因此,数据将被缓冲,直到看到 \n,然后在那个时间点上刷新所有已缓冲的数据。实际上,缓冲区通常有一个最大大小(就像完全缓冲的情况一样),因此规则实际上更像是“缓冲,直到看到换行符或遇到 4096 字节的数据,以先出现者为准”。

而GNU libc (glibc)使用以下规则进行缓冲:

Stream               Type          Behavior
stdin                input         line-buffered
stdout (TTY)         output        line-buffered
stdout (not a TTY)   output        fully-buffered
stderr               output        unbuffered

因此,如果使用-t,根据docker文档,它将分配一个伪tty,然后stdout变为line-buffered,因此docker run --name=myapp -it myappimage可以看到一行输出。
而如果只使用-d,则没有分配tty,因此stdoutfully-buffered,一行App started肯定无法刷新缓冲区。
因此,使用-dt使stdout line buffered或在python中添加-uflush the buffer是解决此问题的方法。

21

鉴于我还没有看到这个答案:

你也可以在打印后刷新stdout:

import time

if __name__ == '__main__':
    while True:
        print('cleaner is up', flush=True)
        time.sleep(5)

2
这对我完美地起作用了,很蠢需要这样,但现在很好用。 - james-see
3
这对我也起作用了。没有哪种envar方法或“-u”方法适用于我。 - tlochner95
3
如果你在多个 print 中间加入 flush=True,并在最后一个 print 处进行刷新,那么你就可以看到所有之前的 print 直到带有 flush=True 的那一个。 - Mattia Paterna

20

尝试将这两个环境变量添加到您的解决方案中:PYTHONUNBUFFERED=1PYTHONIOENCODING=UTF-8


1
为什么需要PYTHONIOENCODING - MPękalski
摆脱无 ASCII 字符。 - Lukasz Dynowski

8

如果您将print更改为logging,则可以在分离的图像上查看日志。

main.py:

import time
import logging
print "App started"
logging.warning("Log app started")
while True:
    time.sleep(1)

Dockerfile:
FROM python:2.7-stretch
ADD . /app
WORKDIR /app
CMD ["python","main.py"]

1
好的。提示:使用Python 3。 - adhg
问题是在Python 2中(没有括号的print语句),因此我在这里使用2。尽管在Python3.6上的行为完全相同,所以感谢您的提示;) - The Hog

7
如果有人在使用conda运行python应用程序,那么应该在命令中添加--no-capture-output,因为conda默认将输出捕获到stdout缓冲区。
ENTRYPOINT ["conda", "run", "--no-capture-output", "-n", "my-app", "python", "main.py"]

谢谢!现在它不仅可以记录到交互式控制台,还能在使用docker log命令时记录! - Dhruv Kapoor

5

我不得不在我的docker-compose.yml文件中使用PYTHONUNBUFFERED=1才能看到django runserver的输出。


4
作为一个快速解决方法,尝试以下操作:
from __future__ import print_function
# some code
print("App started", file=sys.stderr)

当我遇到同样的问题时,这对我有用。但是,说实话,我不知道为什么会出现这个错误。


谢谢你的建议!我尝试将所有的打印语句替换为你的版本,但不幸的是它对我没有起作用,我仍然无法通过docker logs获得任何输出(在sys.stderr / sys.stdout之间切换并没有产生明显的结果)。这是Docker的一个bug吗? - jpdus
请查看我的回答,原因是:stderr未被缓冲,所以您可以使用您的解决方案来修复它。 - atline

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接