最近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); } } ......... }