一个功能完善的web服务可能总是离不开定时任务和守护任务。
前者经常用来完成一些定时触发的任务;例如跑数据
后者则多用于进行一些需要一直执行的任务,例如做为MQ消息的消费者,需要一直监听消息
在一般的Linux环境下,定时任务的执行可以直接配置crontab,而守护任务通常可以使用supervisor服务
一般地这两个服务都会默认以root用户执行;而web服务则通常不会以root用户执行,例如nginx一般都会以低权限的www-data:www-data 用户执行
而在代码层面,守护进程、定时任务和web服务通常会使用一套代码
这就带来一个问题;高权限用户身份(例如root)执行的crontab/supervisor进程创建的文件属于root;这就会导致该文件无法被低权限用户身份(例如www-data)执行的web服务所读写;这就会带来线上的问题;
一个典型的场景是,被守护进程、定时任务、web服务共用的日志组件代码在守护进程、定时任务中执行的时候创建的日志文件;无法被web服务读写。直接导致线上发生500(服务器内部错误)
如何解决?
一个方案是将定时任务、守护进程都改为低权限用户执行:
例如crontab 使用 crontab -u www-data -e 就可以在用户www-data 权限下配置定时任务
而supervisor的program段也可以指定user参数,来实现子进程以指定用户身份运行的目的,示例配置(注意礼貌 user 字段的配置):
[program:abc]
directory = /data/var/www/abc/
command = /usr/bin/php abc.php
numprocs = 1
user = www-data
autostart = true
autorestart = true
stdout_logfile = /var/log/stdout.log
stderr_logfile = /var/log/stderr.log
值得注意的是,如果supervisor的program是新增的user配置。需要在supervisorctl 中执行update命令;而不不仅仅是在supervisorctl下执行restart abc命令;单纯的restart并不会改变子进程的用户;
这个可以通过supervisorctl下status查到子进程的pid,然后通过 ps aux
查看对应pid的用户证实,亲测必须update命令才能更新子进程的user身份
该方案降低了定时任务和守护任务的用户身份,来达到不影响web服务的目的。同时带来的问题是,部分守护任务和定时任务依赖的服务,并不一定是能以www-data 身份执行的。同时对python等程序来说,更改身份也会带来环境的问题。所以该方案会带来较大的复杂性和不确定性
还有一个方案是,在代码中对可能创建同时web服务可能write的文件(大部分文件权限对于其他用户都是可读的);直接进行chown操作;
还是以日志文件示例,代码:
private static function createLoggerByModule(string $module): ?Logger
{
$dir = ProjectConfig::logModuleDir($module);
$filename = date('Y-m-d') . ".log";
$logFile = $dir . '/' . $filename;
if (!is_file($logFile)){
$webUser = ProjectConfig::getInstance()->webUser();
if (!empty($webUser)){
`touch $logFile`;
`chown $webUser $logFile`;
}
}
...
}
对于不存在的日志文件。直接先touch创建,然后执行chown,将文件的owner修改为web服务用户。
该方案的好处在于对守护任务和定时任务无需改动,但是需要梳理代码中操作文件,尤其是写文件的地方;在这些地方在编码上注意文件所有者的设置(或者通过chmod修改文件的权限配置也可以,原理一样)
网友评论