openresty的agent進程

最近Openresty項目增長了一個新的功能,能夠在Nginx中開啓一個agent進程,這個agent進程不像Nginx的worker進程那樣監聽服務端口而後對外提供服務,而是繼承了master進程的用戶權限。nginx

出於安全的考慮,Nginx通常以root身份啓動,啓動後worker進程經過setuid和setgid以nobody方式運行,只有master進程保留了root身份。而agent進程擁有與master進程相同的權限,即可以實現對Nginx自身的控制,如Nginx的重載等功能。git

安裝

經過openresty安裝

經過openresty倉庫下載安裝openrestygithub

從源碼編譯

  • 1 須要對Nginx源碼打補丁, openresty的補丁
  • 2 下載lua-nginx-module,編譯Nginx,具體方法
  • 3 須要lua-resty-core的支持。

開啓agent進程

在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
    }

爲agent進程添加任務

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

利用agent進程實現Nginx自身的控制

以下面的代碼,每隔一小時向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

agent進程實現原理

openresty修改了Nginx源碼以實現agent進程的支持。lua

生成agent進程

在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);
}

agent進程執行

生成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進程初始化

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);
        }
    }

    .........
}

總結

  • agent進程不監聽服務端口,其工做任務須要在init_worker時設置。
  • agent繼承了master進程的用戶權限,能夠向master進行發送信號,控制Nginx進行重載,關閉等操做。
  • 若是Nginx以root啓動,agent進程會擁有root權限,能夠控制整個操做系統,使用需很是謹慎。
相關文章
相關標籤/搜索