在Apache/PHP中如何运行一个独立的进程,并在Linux上的Apache重启后继续运行

8cdiaqws  于 2022-11-16  发布在  Apache
关注(0)|答案(6)|浏览(108)

我正在尝试启动一个php(在apache下)进程(通过从浏览器调用apache),它将在关闭apache服务器(sudo service apache2 stop)后仍然存在.即使我确保创建的进程没有父进程(parent 1),并且有它自己的会话,但是,不知何故,当我停止apache(或重新启动apache)时,进程还是死了.
我创建了一个test.php文件:

<?php
exec('setsid nohup sleep 1000 > /dev/null 2>/dev/null &');
?>

当我们对test.php执行HTTP GET时,确实会得到一个即时的OK响应,并且进程仍然存在。但是,当我们这样做时:

sudo service apache2 stop

睡眠进程死亡。当进程不属于它的组或会话,并且进程不是子进程时,如何杀死进程?

piok6c0g

piok6c0g1#

Apache可能会有一个派生进程的列表,并逐个杀死它们,而不是将它们作为一组。在这种情况下,列表中的所有进程都将被kill(2) ed。
请查看kill(2)系统调用的手册页。在ERRORS部分,失败的唯一可能性是:

  • EINVAL,表示传递了无效的信号编号。此处不适用。
  • ESRCH,流程(或流程组)不存在。也不适用。
  • EPERM,您没有发送信号的权限。这在此处适用,但只有(这与进程层次结构或父关系无关)您可以向哪些进程发送信号,这些进程运行的真实/保存的用户id等于发送方进程的有效用户id。因此,由于Apache有一个它启动的所有进程的注册表,它能够终止进程是正常的。

无论如何,您是否尝试过创建一个进程,从该进程创建一个子进程,并在孙子进程中执行setsid?这样,Apache进程就没有机会在派生进程列表中注册它。我没有尝试过,但它可以工作。
从FreeBSD kill(2)手册页:
对于有权向pid指定的进程发送信号的进程,用户必须是超级用户,或者**接收进程的真实的或已保存用户ID必须与发送进程的真实或有效用户ID匹配。**信号SIGCONT是一个例外,它始终可以发送到与发送方具有相同会话ID的任何进程。
(重点是我)在linux中,几乎是一样的,除了
...(如果是发送进程)在目标进程的用户名称空间中具有CAP_KILL功能...
但这里不适用。

yrefmtwq

yrefmtwq2#

我很好奇为什么apache服务器需要关闭,但是我倾向于说您可以尝试使用容器化的解决方案来完全不同的策略。公开一个可以触发您的php进程的nginx docker容器可能比使用Apache更稳定,因为docker守护进程总是以root身份运行。我认为这取决于您的用例的特定需求。因此解释一下为什么需要关闭Apache可能会得到更好的答案。

wztqucjr

wztqucjr3#

列出所有活动进程(例如ps aux)。Apache创建的进程以用户www-data的身份运行。
当您停止Apache服务时,我怀疑它们会根据该列表或某个内部列表被终止。apache2.service stop调用apachectl -k graceful-stop。这将SIGTERM所有“子进程”。不幸的是,我无法在代码中找到确切的位置。也许有人可以这样做来验证假设。

A解决方案:

以另一个用户的身份运行该流程。如何操作取决于您的情况。您必须定义某种接口。
例如,你可以让另一个进程监听localhost或者使用Unix域套接字,同样的事情也可以通过使用 gearman 来实现,正如@Akshay Vanjare在评论中指出的。
然后,您的PHP指令码就可以呼叫界面。

无解决方案:
  • 以这种方式控制守护进程是个坏主意,请参见this答案。
  • 作为用户www-data,您不能以另一个受密码保护的用户身份启动进程,因为su asks代表pass,switching UID不是一个好主意。
  • 为了测试,我设置了一个用户的空密码,并在PHP脚本中使用了su - NEW_USER -c "COMMAND"。不要在任何你关心的系统中这样做。这是不安全的。* 非常 * 不安全。而且每次调用脚本时,你都会有一个新的进程。你必须小心地杀死它。
进一步思考:
  • 我还尝试了一些替代“nohup”命令的方法,如daemonizing、fork()、disown等。它们对我不起作用。
  • 我并没有在新的过程中努力忽略SIGTERM。也许这样解决问题也是可能的。
  • 对我来说,Apache在停止时做(积极的)清理是有意义的,这是我对Web浏览器的期望行为,它必须处理大量的子对象。
xurqigkl

xurqigkl4#

首先关于我的答案,这不是一个好主意,控制守护程序从网络。
一旦这样说,你可以有一个PHP脚本,写一个标志或类似的东西,无论是在数据库,文件,redis等。
另一方面,创建一个PHP脚本,用cron来查找标记。如果找到了,脚本可以启动一个PHP守护进程来分离。注意运行cron脚本和PHP守护进程时要确保用户和权限。
但再次要小心安全问题。

fdx2calv

fdx2calv5#

从PHP开发人员的Angular 来看,我不认为有一种方法可以实现这一点。
或者你可以实现一个队列和工作者来完成你的任务/执行。为此你可以使用Redis或任何其他数据库。
https://laravel.com/docs/9.x/queues

7vhp5slm

7vhp5slm6#

您可以编写一个服务,然后使用sudo service start|stop|restart进行控制
您可以使用visudo来编辑sudoers档案,以允许www-data使用sudo来执行特定的sh档案,而不需要密码提示。此sh档案将包含service your_service start行。
示例:我希望能够从客户端 AJAX 请求重新启动apache 2。
上下文:没有连接屏幕/键盘的文件服务器。我有SSH设置,但我不想在笔记本电脑中启动客户端。我用apache密码保护目录保护重新启动页面,沿着其他管理页面。例如:我有一个页面来重新启动/关闭整个系统。
"我这么做了“
注册要与sudo service apache_restarter_service start一起运行的服务
此服务文件调用一个简单的sh脚本,它是一个两行脚本:sleep 1 second and then call service apache2 restart .
使用visudo编辑sudoers文件。允许用户www-data使用sudo运行调用service apache_restarter_service start的sh脚本。
服务文件只用于启动一个root权限的进程,不需要很长时间,所以在服务定义中不需要Restart=always,这个进程不能被apache杀死,apache甚至不会知道它,它只知道sh脚本返回0。
sudoers文件的更改示例:

www-data ALL=(ALL) NOPASSWD:/opt/apache_restarter/from_php.sh

from_php.sh只调用service apache_restarter_service start。这里不需要sudo,因为你的php脚本在从_php. sh调用时已经使用了sudo。
这里需要from_php.sh,因为你不希望php直接调用service。因为你的脚本中的BUG或攻击可能会对你的服务器造成损害。这样,我们只在sudoers中授权了from_php.sh
php脚本必须用sudo调用from_php.sh,你可以使用exec("sudo /opt/apache_restarter/from_php.sh"),但不需要在末尾加上&,因为apache不会立即重启。
服务定义文件apache_restarter_service.service的示例:

[Unit]
Description=Apache restarter one shot
After=multi-user.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=no
User=root
ExecStart=/opt/apache_restarter/run.sh

[Install]
WantedBy=multi-user.target

为了避免服务在系统重新启动时运行。没有什么坏处,但也是无用的。你可以让服务总是禁用,并在启动它之前在from.php脚本中启用它。或者只是调整服务文件,使其在系统启动时不运行一次。
您可以根据需要调整StartLimitIntervalSec=0
您可以将User=root调整为一个更安全、权限更低的用户。在我的情况下,我不在乎,但也许您会在乎。您可能希望使用www-data。我不知道为什么这可能不起作用,但到目前为止,我还没有尝试使用www-data
你可以用多种方式编写run.sh,并执行不同的任务,最简单的方法是等待1秒钟,然后用service apache2 restart重新启动apache。
虽然我感兴趣的是能够从客户端重新启动apache,但这也适用于生成一个在apache停止/重新启动时不会终止的进程。
我也尝试了nohup&setsid等等。我会保存人们一些时间。什么都不起作用。Apache应该像已经建议的那样终止所有子进程。

相关问题