在上一篇的文章中, 學習如何安裝 echo-nginx-module 這個第三方模組。

今天就來試試看第二種動態安裝模組的方式。

首先安裝相關依賴 pcre2 zlib openssl

apk add pcre2-dev
apk add zlib-dev
apk add openssl

這邊我們選擇下載 echo-nginx-module 這個第三方模組與 Nginx 原始碼。

wget -P ~ https://github.com/openresty/echo-nginx-module/archive/refs/tags/v0.63.tar.gz
tar zxf v0.63.tar.gz
wget -P ~ https://nginx.org/download/nginx-1.26.1.tar.gz
tar zxf nginx-1.26.1.tar.gz
cd ~/nginx-1.26.1

注意這邊設定的時候就不需要添加 --add-module 參數了 --with-compat 這個新的參數可以讓之後編譯模組時能夠比較方便添加到 Nginx 內部, 建議之後編譯 Nginx 都添加上去。

./configure \
--prefix=/var/lib/nginx \
--modules-path=/usr/lib/nginx/modules \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--pid-path=/run/nginx/nginx.pid \
--lock-path=/run/nginx/nginx.lock \
--with-http_ssl_module \
--with-compat

設定完後進行安裝,到這邊就是一般的編譯與安裝流程。

make && make install

現在開始就是編譯動態模組的流程了,這邊使用 --add-dynamic-module 並不是使用 --add-module 參數

./configure \
--with-compat \
--add-dynamic-module=/root/echo-nginx-module-0.63

另外如果編譯 Nginx 時並沒有使用 --with-compat 參數的話,下個設定編譯動態模組的步驟就可以不用帶入 --with-compat, 取額代之的是要把當初編譯的參數全部帶進來,之後載入模組才能正常運作。

因為編譯Nginx 跟編譯模組可能是不同人或電腦進行操作的所以可以使用 -V 參數查看編譯時所帶入的參數,也就是最底下的 configure arguments

$ nginx -V
nginx version: nginx/1.26.1
built by gcc 12.2.1 20220924 (Alpine 12.2.1_git20220924-r10) 
built with OpenSSL 3.1.4 24 Oct 2023
TLS SNI support enabled
configure arguments: --prefix=/var/lib/nginx --modules-path=/usr/lib/nginx/modules --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --pid-path=/run/nginx/nginx.pid --lock-path=/run/nginx/nginx.lock --with-http_ssl_module

所以我們編譯的時候沒有使用 --with-compat 參數就需要輸入以下命令之後模組才能正常運作。

./configure \
--prefix=/var/lib/nginx --modules-path=/usr/lib/nginx/modules --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --pid-path=/run/nginx/nginx.pid --lock-path=/run/nginx/nginx.lock --with-http_ssl_module \
--add-dynamic-module=/root/echo-nginx-module-0.63

也可以使用以下命令自動去抓 configure arguments 內容。

./configure \
$(nginx -V 2>&1 | egrep  "^configure" | cut -d: -f2) \
--add-dynamic-module=/root/echo-nginx-module-0.63

最後使用以下命令來編譯模組,編譯的結果會輸出到 objs 這個資料夾內部。

make modules

注意到 ngx_http_echo_module.so 這個副檔名為 so 的檔案就是我們編譯後的結果。

$ ls -lh ~/nginx-1.26.1/objs/
total 5M     
-rw-r--r--    1 root     root       46.1K Aug  7 08:29 Makefile
drwxr-xr-x    3 root     root          17 Aug  7 08:29 addon
-rw-r--r--    1 root     root       19.6K Aug  7 08:29 autoconf.err
-rwxr-xr-x    1 root     root        4.3M Aug  7 08:28 nginx
-rw-r--r--    1 root     root        5.4K Aug  7 08:28 nginx.8
-rw-r--r--    1 root     root        8.1K Aug  7 08:29 ngx_auto_config.h
-rw-r--r--    1 root     root         657 Aug  7 08:29 ngx_auto_headers.h
-rwxr-xr-x    1 root     root      433.0K Aug  7 08:29 ngx_http_echo_module.so
-rw-r--r--    1 root     root         339 Aug  7 08:29 ngx_http_echo_module_modules.c
-rw-r--r--    1 root     root       18.3K Aug  7 08:29 ngx_http_echo_module_modules.o
-rw-r--r--    1 root     root        5.9K Aug  7 08:29 ngx_modules.c
-rw-r--r--    1 root     root       26.6K Aug  7 08:28 ngx_modules.o
drwxr-xr-x    9 root     root          91 Aug  7 08:27 src

完成後參考 --modules-path 的路徑把這個 ngx_http_echo_module.so 複製過去。

mkdir -p /usr/lib/nginx/modules
cp ~/nginx-1.26.1/objs/ngx_http_echo_module.so /usr/lib/nginx/modules

到此為止只剩下最後一個步驟就是編輯 nginx.conf 並使用 load_module 指令通知 Nginx 有新的模組需要載入。

vi /etc/nginx/nginx.conf
worker_processes  1;

load_module /usr/lib/nginx/modules/ngx_http_echo_module.so;

events {
    worker_connections  1024;
}   

http {
    include       mime.types;
    default_type  application/octet-stream;
    
    sendfile        on;
    keepalive_timeout  65;
    
    server {
        listen       80;
        server_name  localhost;
        
        location / {
            root   html;
            index  index.html index.htm;
        }   
        
        location /echo {
            default_type text/plain;
            echo hello, world!$arg_text1$arg_text2;
        }   

        location /echo_with_sleep {
            echo hello;
            echo "sleep 3 second";
            echo_flush;
            echo_sleep 3;
            echo world;
        }   

        location /echo_with_timer {
            echo_reset_timer;
            echo_sleep 3;  # in sec
            echo hello world;
            echo "'hello world' takes about $echo_timer_elapsed sec.";
        }   

        location /echo_with_exec {
            echo_exec /hello text1=$arg_mytext1&text2=$arg_mytext2;
        }   

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }   
    }
}

最後重啟 Nginx 並測試,能看到回傳代表我們的模組已經動態加載成功。

nginx -s reload

$ curl "127.0.0.1/echo"
hello, world!

Problem

  1. 這個錯誤代表編譯的動態模組參數有問題。
nginx: [emerg] module "/usr/lib/nginx/modules/ngx_http_echo_module.so" is not binary compatible in /etc/nginx/nginx.conf:11

正確的流程如下列:

  1. 編譯 Nginx 有帶入 --with-compat 參數,編譯動態模組時需要帶入 --with-compat 參數,並且可以省略 configure arguments
  2. 編譯 Nginx 沒有帶入 --with-compat 參數,編譯動態模組時不能帶入 --with-compat 參數,並且要添加 configure arguments

會跳出錯誤如下列:

  1. 編譯 Nginx 有帶入 --with-compat 參數,編譯動態模組時沒有帶入 --with-compat 參數。
  2. 編譯 Nginx 有帶入 --with-compat 參數,編譯動態模組時沒有帶入 --with-compat 參數並且添加 configure arguments
  3. 編譯 Nginx 沒有帶入 --with-compat 參數,編譯動態模組時帶入 --with-compat 參數。
  4. 編譯 Nginx 沒有帶入 --with-compat 參數,編譯動態模組時帶入 --with-compat 參數並且添加 configure arguments
  1. 這個錯誤代表添加 load_module 指令時位置不對,只需要把 load_module 提升到最上方即可。
nginx: [emerg] "load_module" directive is specified too late in /etc/nginx/nginx.conf:16

Alpine

最後來看看使用 --add-dynamic-module 的好處,這裡可以看一下 Alpine 是怎麼處理的, 我們先安裝 Alpine 打包的 Nginx。

可以看到 Alpine 在編譯 Nginx 時就已經把 --add-dynamic-module 帶入進來了,並且沒有使用 --with-compat 參數

apk add nginx

$ nginx -V
nginx version: nginx/1.24.0
built with OpenSSL 3.1.3 19 Sep 2023 (running with OpenSSL 3.1.4 24 Oct 2023)
TLS SNI support enabled
configure arguments: 
--prefix=/var/lib/nginx 
--sbin-path=/usr/sbin/nginx 
--modules-path=/usr/lib/nginx/modules 
--conf-path=/etc/nginx/nginx.conf 
--pid-path=/run/nginx/nginx.pid 
--lock-path=/run/nginx/nginx.lock 
--http-client-body-temp-path=/var/lib/nginx/tmp/client_body 
--http-proxy-temp-path=/var/lib/nginx/tmp/proxy 
--http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi 
--http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi 
--http-scgi-temp-path=/var/lib/nginx/tmp/scgi 
--with-perl_modules_path=/usr/lib/perl5/vendor_perl 
--user=nginx 
--group=nginx 
--with-threads 
--with-file-aio 
--without-pcre2 
--with-http_ssl_module 
--with-http_v2_module 
--with-http_realip_module 
--with-http_addition_module 
--with-http_xslt_module=dynamic 
--with-http_image_filter_module=dynamic 
--with-http_geoip_module=dynamic 
--with-http_sub_module 
--with-http_dav_module 
--with-http_flv_module 
--with-http_mp4_module 
--with-http_gunzip_module 
--with-http_gzip_static_module 
--with-http_auth_request_module 
--with-http_random_index_module 
--with-http_secure_link_module 
--with-http_degradation_module 
--with-http_slice_module 
--with-http_stub_status_module 
--with-http_perl_module=dynamic 
--with-mail=dynamic 
--with-mail_ssl_module 
--with-stream=dynamic 
--with-stream_ssl_module 
--with-stream_realip_module 
--with-stream_geoip_module=dynamic 
--with-stream_ssl_preread_module 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/njs-0.7.11/nginx 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/ngx_devel_kit-0.3.2/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/traffic-accounting-nginx-module-2.0/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/array-var-nginx-module-0.06/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx-auth-jwt-0.2.1/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/ngx_brotli-1.0.0rc/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/ngx_cache_purge-2.5.3/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx_cookie_flag_module-1.1.0/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx-dav-ext-module-3.0.0/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/echo-nginx-module-0.63/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/encrypted-session-nginx-module-0.09/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/ngx-fancyindex-0.5.2/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/ngx_http_geoip2_module-3.4/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/headers-more-nginx-module-0.34/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx-keyval-0.1.0/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx-log-zmq-1.0.0/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/lua-nginx-module-0.10.24/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/lua-upstream-nginx-module-0.07/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/naxsi-1.3/naxsi_src 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nchan-1.3.6/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/redis2-nginx-module-0.15/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/set-misc-nginx-module-0.33/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx-http-shibboleth-2.0.1/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/ngx_http_untar_module-1.1/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx-upload-module-2.3.0/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx-upload-progress-module-0.9.2/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx-upstream-fair-0.1.3/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/ngx_upstream_jdomain-1.4.0/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx-vod-module-1.31/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx-module-vts-0.2.1/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/mod_zip-1.3.0/ 
--add-dynamic-module=/home/buildozer/aports/main/nginx/src/nginx-rtmp-module-1.2.2/

沒有使用 --with-compat 參數就會造成一個問題,那就是如果我今天想要添加模組到 Alpine 打包的 Nginx 的話我就必須參考他當初編譯模組的 路徑也就是 /home/buildozer/aports/main/nginx/src 這個路徑底下,操作起來會比較麻煩。

接下來編譯完動態模組後 Alpine 會進行上傳,所以我們可以使用 apk install 直接下載動態模組,先使用 apk list 尋找所有 Nginx 模組。

[node1] (local) root@192.168.0.8 ~
$ apk list nginx-mod*
nginx-mod-devel-kit-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-accounting-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-array-var-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-auth-jwt-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-brotli-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-cache-purge-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-cookie-flag-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-dav-ext-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-echo-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-encrypted-session-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-fancyindex-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-geoip-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-geoip2-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-headers-more-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-image-filter-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-js-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-keyval-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-log-zmq-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-lua-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-lua-upstream-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-naxsi-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-nchan-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-perl-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-redis2-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-set-misc-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-shibboleth-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-untar-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-upload-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-upload-progress-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-upstream-fair-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-upstream-jdomain-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-vod-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-vts-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-xslt-filter-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-http-zip-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-mail-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-rtmp-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-stream-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-stream-geoip-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-stream-geoip2-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)
nginx-mod-stream-js-1.24.0-r7 x86_64 {nginx} (BSD-2-Clause)

接下來使用 apk 安裝 echo 模組

apk add nginx-mod-http-echo

可以使用模組管理網頁來查看這個包裡面有什麼內容 Nginx08-Alpine-Echo-Content

  1. /etc/nginx/modules/10_http_echo.conf #只有一段指令 load_module "modules/ngx_http_echo_module.so";
  2. /usr/lib/nginx/modules/ngx_http_echo_module.so #編譯後的 so 模組檔案

這樣重啟 Nginx 後就動態模組就載入成功了。


Summary

今天學習如何動態載入第三方的模組,這種方式可以快速載入新的功能,也可以避免重新編譯 Nginx, 如果當初規劃就已經確定要使用某個第三方模組那麼使用 --add-module 參數也是不錯的選擇。