调试NGINX

引言

当程序出现问题时,调试有助于定位程序代码中的缺陷。它通常用于开发或测试第三方模块、实验性模块。

NGINX的调试功能包括调试日志,以及生成核心转储文件并进一步取得回溯信息。

配置用于调试的NGINX二进制程序

首先,需要在NGINX二进制程序中启用调试支持。NGINX Plus已经提供nginx-debug程序;开源NGINX则需要重新编译。

配置F5 NGINX Plus二进制程序

从Release 8开始,NGINX Plus在标准二进制程序之外同时提供nginx-debug。要启用调试,需要从nginx切换到nginx-debug。打开终端并运行:

service nginx stop && service nginx-debug start

完成后,在配置文件中启用调试日志。

编译开源NGINX二进制程序

要在开源NGINX中启用调试,需要在configure脚本中指定--with-debug标志并重新编译。

编译带有调试支持的开源NGINX:

  1. 下载并解压NGINX源码,进入源码目录。参见下载源码。

  2. 获取NGINX的配置参数列表,运行:

    nginx -V 2>&1 | grep arguments

  3. 将--with-debug添加到配置参数列表,运行configure脚本:

    ./configure --with-debug <other configure arguments>

  4. 编译并安装NGINX:

    shell
    sudo make
    sudo make install

  5. 重启NGINX。

NGINX与调试符号

调试符号可以提供额外的调试信息,包括函数、变量、数据结构、源文件和行号。

NGINX默认使用包含调试符号的-g标志编译。

但如果执行回溯时出现No symbol table info available错误,说明调试符号缺失,需要重新编译NGINX并加入调试符号支持。

具体编译器标志取决于所用编译器。例如,对GCC编译器:

  • 使用-g标志包含调试符号。

  • 通过-O0禁用编译优化,使调试器输出更易理解:

    ./configure --with-debug --with-cc-opt='-O0 -g' ...

在NGINX配置中启用调试日志

调试日志记录错误及调试相关信息,默认禁用。要启用它,先确保NGINX编译时支持调试,参见配置用于调试的NGINX二进制程序,再在配置文件中为error_log指令指定debug参数。调试日志可以写入文件、已分配的内存缓冲区、标准错误输出或syslog。

建议在配置的main层级启用调试日志,以获得完整情况。

将调试日志写入文件

在高负载下,写入文件可能降低性能。文件也可能快速增长并耗尽磁盘空间。为减少负面影响,可以改为写入内存缓冲区,或只记录指定IP地址。详见将调试日志写入内存和指定IP的调试日志。

启用文件调试日志:

  1. 确保NGINX配置时使用了--with-debug选项。运行以下命令,检查输出是否包含它:

    nginx -V 2>&1 | grep -- '--with-debug'

  2. 打开NGINX配置文件:

    sudo vi /etc/nginx/nginx.conf

  3. 找到默认位于main上下文中的error_log指令,将日志级别改为debug。必要时更改日志文件路径:

    error_log  /var/log/nginx/error.log debug;

  4. 保存配置并退出编辑器。

将调试日志写入内存

调试日志可以写入使用循环缓冲区的内存。其优势是:高负载下,debug级别日志不会显著影响性能。

启用内存调试日志:

  1. 确保NGINX配置时使用了--with-debug选项。运行以下命令并检查输出:

    nginx -V 2>&1 | grep -- '--with-debug'

  2. 在NGINX配置文件的main上下文中,使用error_log指定用于调试日志的内存缓冲区:

    nginx
    error_log memory:32m debug;
    ...
    http {
        ...
    }

从内存中提取调试日志

可以在GDB调试器中执行脚本,从内存缓冲区提取日志。

提取内存中的调试日志:

  1. 取得NGINX工作进程的PID:

    ps axu |grep nginx

  2. 启动GDB调试器:

    sudo gdb -p <nginx PID obtained at the previous step>

  3. 复制以下脚本,粘贴到GDB并按回车。它会将日志保存到当前目录中的debug_log.txt:

    nginx
    set $log = ngx_cycle->log
    while $log->writer != ngx_log_memory_writer
        set $log = $log->next
    end
    set $buf = (ngx_log_memory_buf_t *) $log->wdata
    dump binary memory debug_log.txt $buf->start $buf->end

  4. 按CTRL+D退出GDB。

  5. 打开当前目录中的debug_log.txt:

    sudo less debug_log.txt

指定IP的调试日志

可以仅为某个IP地址或一个IP范围启用调试日志。这种方式适合生产环境,可避免不必要的性能影响。IP地址通过events块中的debug_connection指令指定;该指令可以定义多次:

nginx
error_log /path/to/log;
...
events {
    debug_connection 192.168.1.1;
    debug_connection 192.168.10.0/24;
}

各虚拟主机的调试日志

通常,error_log指令在main上下文中指定,因此会应用到server和location等其他上下文。但若某个server或location块又定义了error_log,就会覆盖全局设置,并使用自己的日志路径和级别。

要为指定虚拟主机设置调试日志,在相应server块中添加error_log,指定新的日志路径及debug级别:

nginx
error_log /path1/to/log debug;
...
http {
    ...
    server {
    error_log /path2/to/log debug;
    ...
    }
}

要关闭某个虚拟主机的调试日志,在其server块中定义error_log,仅指定日志文件路径:

nginx
error_log /path/to/log debug;
...
http {
    ...
    server {
    error_log /path/to/log;
    ...
    }
}

启用核心转储

核心转储文件有助于定位和修复导致NGINX崩溃的问题。它可能包含密码、私钥等敏感信息,必须安全处理。

要生成核心转储文件,必须同时在操作系统与NGINX配置中启用。

在操作系统中启用核心转储

在操作系统中执行:

  1. 指定保存核心转储的工作目录,例如/tmp/cores:

    mkdir /tmp/cores

  2. 确保NGINX工作进程能够写入该目录:

    shell
    sudo chown root:root /tmp/cores
    sudo chmod 1777 /tmp/cores

  3. 取消核心转储文件的最大大小限制:

    sudo prlimit --core=unlimited:unlimited --pid $(cat /run/nginx.pid)

    如果操作返回Cannot modify limit: operation not permitted,运行以下命令:

    sudo sh -c "ulimit -c unlimited && exec su $LOGNAME"

  4. 为setuid和setgid进程启用核心转储。

    对CentOS 7.0、Debian 8.2和Ubuntu 14.04,运行:

    shell
    echo "/tmp/cores/core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern
    sudo sysctl -w fs.suid_dumpable=2
    sysctl -p

    对FreeBSD,运行:

    shell
    sudo sysctl kern.sugid_coredump=1
    sudo sysctl kern.corefile=/tmp/cores/%N.core.%P

在NGINX配置中启用核心转储

在NGINX配置文件中启用核心转储:

  1. 打开配置文件:

    sudo vi /usr/local/etc/nginx/nginx.conf

  2. 使用working_directory指令指定保存核心转储的目录。该指令位于main配置层级:

    working_directory /tmp/cores/;

  3. 确保目录存在,并允许NGINX工作进程写入。打开终端运行:

    shell
    sudo chown root:root /tmp/cores
    sudo chmod 1777 /tmp/cores

  4. 使用worker_rlimit_core指定核心转储文件的最大允许大小,该指令同样位于main层级。如果转储文件超过这个大小,就不会生成。

    worker_rlimit_core 500M;

示例:

nginx
worker_processes   auto;
error_log          /var/log/nginx/error.log debug;
working_directory  /tmp/cores/;
worker_rlimit_core 500M;

events {
    ...
}

http {
    ...
}

使用这些设置,仅当核心转储文件不超过500MB时,才会在/tmp/cores/目录中生成。

从核心转储文件取得回溯

回溯从核心转储文件中提供程序崩溃时的异常情况。

取得回溯:

  1. 使用以下模式,在GDB中打开核心转储文件:

    sudo gdb <nginx_executable_path> <coredump_file_path>

  2. 输入backtrace,取得崩溃时的调用栈:

    (gdb) backtrace

如果backtrace返回No symbol table info available,需要重新编译NGINX二进制程序并加入调试符号。参见NGINX与调试符号。

从运行中的进程导出NGINX配置

可以从内存中的主进程提取当前NGINX配置。这在以下情况很有帮助:

  • 核实实际加载的是哪个配置。
  • 磁盘上的配置被意外移除或覆盖后,恢复此前的配置。

只要NGINX具有调试支持,就可以通过GDB脚本导出配置。

  1. 确保NGINX构建时启用了调试支持,即配置参数列表中包含--with-debug。运行命令检查:

    nginx -V 2>&1 | grep -- '--with-debug'

  2. 取得NGINX工作进程的PID:

    ps axu | grep nginx

  3. 启动GDB调试器:

    sudo gdb -p <nginx PID obtained at the previous step>

  4. 复制以下脚本并粘贴到GDB,按回车。脚本将配置保存到当前目录的nginx_conf.txt:

    nginx
    set $cd = ngx_cycle->config_dump
    set $nelts = $cd.nelts
    set $elts = (ngx_conf_dump_t*)($cd.elts)
    while ($nelts-- > 0)
    set $name = $elts[$nelts]->name.data
    printf "Dumping %s to nginx_conf.txt\n", $name
    append memory nginx_conf.txt \
          $elts[$nelts]->buffer.start $elts[$nelts]->buffer.end
    end

  5. 按CTRL+D退出GDB。

  6. 打开当前目录中的nginx_conf.txt:

    sudo vi nginx.conf.txt

寻求帮助

寻求调试帮助时,请提供以下信息:

  1. NGINX版本、编译器版本及配置参数。运行:

    nginx -V

  2. 当前完整NGINX配置,参见从运行中的进程导出NGINX配置。

  3. 调试日志,参见在NGINX配置中启用调试日志。

  4. 取得的回溯信息,参见启用核心转储与取得回溯。


原文:调试NGINX;作者:F5 NGINX文档贡献者。版本或日期:以抓取时的官方版本为准。原文及源码权利归相应权利人所有。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容