subprocess.Popen启动docker-compose无法获取容器stdout输出解决方法
当前使用Python subprocess.Popen 方法启动docker-compose进程,实现代码如下:
print(subprocess.Popen('docker-compose -f docker-compose_dev.yml up', shell=True, stdout=subprocess.PIPE).stdout.read())
运行上述代码得到的输出内容如下:
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them b'' Creating network "wagtail-react-blog_spa_network" with the default driver Creating wagtail-react-blog_backend_1 ... done Creating wagtail-react-blog_frontend_1 ... done Creating wagtail-react-blog_nginx_1 ... done
上述输出存在缺失:直接在终端运行相同命令时本应展示的Docker容器内部命令运行日志没有被捕获。
- 此前通过添加
--progress参数可以正常获取这部分输出,但目前docker-compose文档中已无该参数的相关说明,使用该参数也无法得到对应输出 - 已分别尝试通过
stdout.read()、stderr.read()读取流内容,返回结果一致 - 在其他脚本中使用
subprocess.check_call调用shell脚本可正常打印全部输出,但不希望通过额外编写shell脚本的方式实现需求,需要直接在Python代码层面落地的可行方案。
核心原因
docker-compose 默认会检测输出目标是否为交互式终端:如果是直接在终端运行,会输出全量日志(包括容器内部业务日志);如果输出被管道接管(也就是subprocess.PIPE的场景),会判定为非交互环境,默认只打印容器编排相关的状态信息,截断容器内部日志输出,这一行为和Python本身逻辑无关,是docker-compose自带的输出策略。subprocess.check_call默认不会接管子进程的输出流,子进程直接将内容打印到当前终端,docker-compose能检测到交互式终端环境,所以能正常输出全量内容。
方案1:添加-T参数禁用伪终端分配
启动docker-compose时加-T参数,强制关闭交互式TTY模式,所有日志(包括容器内部输出)都会以纯文本形式写入标准输出流,不需要依赖交互式终端即可被捕获:
import subprocess # 注意不要用一次性.read()读全量内容,避免管道缓冲区满导致进程卡死 proc = subprocess.Popen( 'docker-compose -f docker-compose_dev.yml up -T', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, # 将错误流合并到标准输出,避免漏读内容 text=True, encoding='utf-8' ) # 逐行迭代读取日志,支持实时输出 for line in proc.stdout: print(line, end='') proc.wait()
方案2:配置环境变量强制纯文本输出
如果加-T参数后仍有日志缺失,可以在启动子进程时传入自定义环境变量,关闭docker-compose的交互式渲染逻辑,强制输出全量纯文本日志:
import subprocess import os run_env = os.environ.copy() run_env['COMPOSE_INTERACTIVE_NO_CLI'] = '1' run_env['DOCKER_CLI_NO_STDIN'] = '1' proc = subprocess.Popen( 'docker-compose -f docker-compose_dev.yml up --no-color -T', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding='utf-8', env=run_env ) for line in proc.stdout: print(line, end='') proc.wait()
注意:不要直接调用
.stdout.read()一次性读取全部输出,这种方式会等子进程完全退出才会返回结果,当日志量较大时,管道缓冲区被写满后会直接导致docker-compose进程阻塞挂起,逐行迭代读取的方式稳定性更高,还能实现日志实时打印。
内容的提问来源于stack exchange,提问作者user19271630

