最近Openresty项目增长了一个新的功能,能够在Nginx中开启一个agent进程,这个agent进程不像Nginx的worker进程那样监听服务端口而后对外提供服务,而是继承了master进程的用户权限。nginx
出于安全的考虑,Nginx通常以root身份启动,启动后worker进程经过setuid和setgid以nobody方式运行,只有master进程保留了root身份。而agent进程拥有与master进程相同的权限,即可以实现对Nginx自身的控制,如Nginx的重载等功能。git
经过openresty仓库下载安装openrestygithub
在Nginx中,master进程生成worker进程、cache-manager进程、agent进程等都是在init和init_worker两个阶段之间进行的,要启用agent进程,必需要init_by_lua中进行设置。以下所示开启agent进程。安全
init_by_lua_block { local process = require "ngx.process" local ok, err = process.enable_privileged_agent() if not ok then ngx.log(ngx.ERR, "enable privileged agent failed") return end }
Nginx以事件驱动的方式工做,时间的来源主要有IO事件和定时器时间两种。agent进程关闭了监听的端口,没法经过网络IO的方式来驱动,貌似只能经过定时器的方式来实现。网络
方法是在init_worker_by_lua中,经过ngx.timer.at设置定时器,在定时器中完成相应的工做。dom
init_worker_by_lua_block { function do_work() while true do ngx.log(ngx.ERR, "privileged agent process") ngx.sleep(5) end end local process = require "ngx.process" if process.type() == "privileged agent" then ngx.timer.at(0, do_work) end }
固然直接在init_worker_by_lua中执行工做,不用定时器也能够,只是一直在init_worker阶段,功能受到限制,没法使用ngx.socket,ngx.sleep,ngx.thread等API。socket
以下面的代码,每隔一小时向Nginx的master进程发送重载信号。函数
lua_package_path "./lib/?.lua;;"; init_by_lua_block { local process = require "ngx.process" local ok, err = process.enable_privileged_agent() if not ok then ngx.log(ngx.ERR, "enable privileged agent failed") return end } init_worker_by_lua_block { local process = require "ngx.process" if process.type() ~= "privileged agent" then return end local function do_work() while true do ngx.sleep(3600) os.execute([[kill -HUP `ps -ef|grep "nginx: master process" |grep -v grep |awk '{print $2}'`]]) end end ngx.timer.at(0, do_work) }
这里为了方便直接用os.execute执行,实际代码中os.execute效率比较低。ui
openresty修改了Nginx源码以实现agent进程的支持。lua
在ngx_master_process_cycle中调用ngx_start_privileged_agent_processes生成agent进程。
static void ngx_start_privileged_agent_processes(ngx_cycle_t *cycle, ngx_uint_t respawn) { ngx_channel_t ch; ngx_core_conf_t *ccf; ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); if (!ccf->privileged_agent) { return; } ngx_spawn_process(cycle, ngx_privileged_agent_process_cycle, "privileged agent process", "privileged agent process", respawn ? NGX_PROCESS_JUST_RESPAWN : NGX_PROCESS_RESPAWN); ngx_memzero(&ch, sizeof(ngx_channel_t)); ch.command = NGX_CMD_OPEN_CHANNEL; ch.pid = ngx_processes[ngx_process_slot].pid; ch.slot = ngx_process_slot; ch.fd = ngx_processes[ngx_process_slot].channel[0]; ngx_pass_open_channel(cycle, &ch); }
生成argent进程后,执行ngx_privileged_agent_process_cycle函数。ngx_privileged_agent_process_cycle会执行ngx_close_listening_sockets关闭全部的监听端口。而后经过ngx_worker_process_init进行一些初始化工做。最后在for循环中和worker进程同样,经过ngx_process_events_and_timers执行相应工做。
static void ngx_privileged_agent_process_cycle(ngx_cycle_t *cycle, void *data) { char *name = data; /* * Set correct process type since closing listening Unix domain socket * in a master process also removes the Unix domain socket file. */ ngx_process = NGX_PROCESS_HELPER; ngx_is_privileged_agent = 1; ngx_close_listening_sockets(cycle); ngx_worker_process_init(cycle, -1); ngx_use_accept_mutex = 0; ngx_setproctitle(name); for ( ;; ) { if (ngx_terminate || ngx_quit) { ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); ngx_worker_process_exit(cycle); } if (ngx_reopen) { ngx_reopen = 0; ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs"); ngx_reopen_files(cycle, -1); } ngx_process_events_and_timers(cycle); } }
agent执行ngx_worker_process_init进行进程的初始化时,与普通的worker进程主要区别是没有执行setgid和setuid操做,保持了和master进程相同的权限。
以下面的代码所示,agent进程的ngx_is_privileged_agent为1,所以不会执行setgid和setuid。
static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker) { ............... if (!ngx_is_privileged_agent && geteuid() == 0) { if (setgid(ccf->group) == -1) { ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, "setgid(%d) failed", ccf->group); /* fatal */ exit(2); } if (initgroups(ccf->username, ccf->group) == -1) { ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, "initgroups(%s, %d) failed", ccf->username, ccf->group); } if (setuid(ccf->user) == -1) { ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, "setuid(%d) failed", ccf->user); /* fatal */ exit(2); } } ......... }