Docker 中的 PHP - fopen() 或 file_put_contents() 无法写入 /proc/1/fd/2 以进行标准输出写入

问题描述 投票:0回答:4

在基于CentOS7的Docker容器中运行php-fpm应用程序。

由于我使用 Docker,我必须将所有进程的日志输出写入 PID 1 的输出。这样,Docker 引擎就可以获取日志以供以后处理(因此它们显示在

docker logs
中)。

我可以通过将所有日志写入

/proc/1/fd/2
来完成此操作,在任何应用程序中都不会出现问题。我可以使用 nginx 来做到这一点,只需执行
echo "test" > /proc/1/fd/2
,它就可以很好地显示
docker logs

但是使用 php 软件我想做同样的事情,写信给

/proc/1/fd/2
但是无论我做什么,我似乎都无法获得文件流。

PHP-FPM 会产生许多子进程。因此,我必须明确写入 PID 1 的 stdout!

所以我正在做这样的事情:

$handle = fopen("/proc/1/fd/2", "w");
fwrite($handle, $msg);
fclose($handle);

我刚刚得到

Warning: fopen(/proc/1/fd/2): failed to open stream: No such file or directory

注意:我也用 https://www.php.net/manual/en/function.file-put-contents.php 尝试过此操作,并且发生了相同的结果。它本质上是 fopen、fwrite、fclose 的包装。

我尝试将它扔到符号链接上:

bash-4.2$ ls -l /tmp/stdout
lrwxrwxrwx. 1 nginx nginx 12 Apr 10 11:19 /tmp/stdout -> /proc/1/fd/2

然后将方法指向它,就像

fopen("/tmp/stdout", "w");

我得到了完全相同的错误

Warning: fopen(/tmp/stdout): failed to open stream: No such file or directory

这是我需要写入的输出:

bash-4.2$ ls -l /proc/1/fd/2
l-wx------. 1 nginx nginx 64 Apr 10 11:21 /proc/1/fd/2 -> pipe:[3523867]

我可以使用我尝试过的任何其他应用程序(包括 nginx 和 bash)毫无问题地写入它(直接或通过符号链接)。

PHP 进程与拥有输出流的同一用户运行

bash-4.2$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
nginx        1     0  0 11:21 ?        00:00:00 php-fpm: master process (/etc/php-fpm.conf)
nginx        7     1  0 11:21 ?        00:00:00 php-fpm: pool www
nginx        8     1  0 11:21 ?        00:00:00 php-fpm: pool www
nginx        9     1  0 11:21 ?        00:00:00 php-fpm: pool www
nginx       10     1  0 11:21 ?        00:00:00 php-fpm: pool www
nginx       11     1  0 11:21 ?        00:00:00 php-fpm: pool www
nginx       12     1  0 11:21 ?        00:00:00 php-fpm: pool www

bash-4.2$ ls -l /proc/1/fd/2
l-wx------. 1 nginx nginx 64 Apr 10 11:21 /proc/1/fd/2 -> pipe:[3523867]

我尝试向其添加一些疯狂的权限,但他们不接受:

bash-4.2$ chmod 777 /proc/1/fd/2
bash-4.2$ ls -l /proc/1/fd/2
l-wx------. 1 nginx nginx 64 Apr 10 11:21 /proc/1/fd/2 -> pipe:[3523867]

这是

/dev/
目录,我无法使用它们,因为它们指向
proc/self
而不是
/proc/1
:

lrwxrwxrwx. 1 root root   11 Apr 10 11:21 core -> /proc/kcore
lrwxrwxrwx. 1 root root   13 Apr 10 11:21 fd -> /proc/self/fd
crw-rw-rw-. 1 root root 1, 7 Apr 10 11:21 full
drwxrwxrwt. 2 root root   40 Apr 10 11:21 mqueue
crw-rw-rw-. 1 root root 1, 3 Apr 10 11:21 null
lrwxrwxrwx. 1 root root    8 Apr 10 11:21 ptmx -> pts/ptmx
drwxr-xr-x. 2 root root    0 Apr 10 11:21 pts
crw-rw-rw-. 1 root root 1, 8 Apr 10 11:21 random
drwxrwxrwt. 2 root root   40 Apr 10 11:21 shm
lrwxrwxrwx. 1 root root   15 Apr 10 11:21 stderr -> /proc/self/fd/2
lrwxrwxrwx. 1 root root   15 Apr 10 11:21 stdin -> /proc/self/fd/0
lrwxrwxrwx. 1 root root   15 Apr 10 11:21 stdout -> /proc/self/fd/1

我尝试更改

/dev/fd
符号链接以指向
/proc/1/fd/2
,然后尝试写入
php://fd/2
,但它也不起作用,同样的错误。

php docker logging stdout
4个回答
0
投票

php-fpm 在

nginx
用户下运行,我敢打赌 nginx 用户无权访问
/proc/1/fd/2

要查看 nginx 用户可以走多远,请运行:

sudo -u nginx /bin/sh -c 'namei -l /proc/1/fd/2'

我打赌它不会达到2,namei会告诉你错误发生在哪里,修复你的权限。

郑重声明,当我在 Debian Linux 系统上运行此程序时,我得到:

root@x2ratma:~# sudo -u www-data /bin/sh -c 'namei -l /proc/1/fd/2'
f: /proc/1/fd/2
drwxr-xr-x root root /
dr-xr-xr-x root root proc
dr-xr-xr-x root root 1
dr-x------ root root fd
                     2 - Permission denied

告诉我

www-data
用户没有 /proc/1/fd 文件夹的读取权限。 (对此的一个肮脏的快速修复是:
sudo chmod a+rx /proc/1/fd
,这意味着
give everybody read access to /proc/1/fd


0
投票

我通过将

error_log
中的
/proc/1/fd/2
设置为
php.ini
来解决这个问题,并使用error_log将日志发送到PHP的系统记录器。

error_log($msg, 0);

0
投票

php确实没有能力使用

/proc/*/fd/*
等功能来打开
fopen

在测试之前,请检查 - php 必须 由运行

/proc/1/fd/2

的同一用户运行

写入 /proc/1/fd/2 的最佳方法是使用 Direct IO php 扩展。使用 Direct IO 可使性能保持与使用 php://stderr

相同的水平

直接IO示例:

// Flags from error_log php sources (php_log_err_with_severity)
$fp = \dio_open('/proc/1/fd/2', O_CREAT | O_APPEND | O_WRONLY, 0644);
\dio_write($fp, $str);
\dio_close($fp);

接下来是解决此问题的其他可能方法,但是,由于某些问题,它们对我不起作用:

使用

popen
的性能太低。使用示例:

$fd = popen('cp /dev/stdin /proc/1/fd/2', 'wb');
fwrite($fp, $str);
fclose($fp);

使用

error_log
会向日志添加前缀,这是我想避免的。使用示例:

$previous = ini_get('error_log');
ini_set('error_log', '/proc/1/fd/2');
error_log($str, 0);
// I can't set error_log globally
ini_set('error_log', $previous);

-1
投票

我也面临着同样的问题。现在,作为一种解决方法,我将在真实日志文件上尝试后台“tail -f”,并将输出从 tail 重定向到 /proc/1/fd/1,如下所示:

tail -f file.log > /proc/1/fd/1 &

不是最好的解决方案,但它可以完成工作

© www.soinside.com 2019 - 2024. All rights reserved.