返回文章列表

NGINX 模組擴充套件與動態組態實踐

本文探討如何使用 njs、Lua 和 Perl 模組擴充套件 NGINX 功能,並結合 Consul 和 consul-template 實作動態組態更新,提升 Web 服務的彈性和可維護性。文章涵蓋 njs 模組安裝、JWT 解析、Lua 和 Perl 指令碼範例,以及 Ansible 和 Chef

Web 開發 系統管理

NGINX 作為廣泛使用的 Web 伺服器和反向代理,其模組化設計允許開發者透過第三方模組擴充套件其功能。本文將介紹如何使用 njs、Lua 和 Perl 模組增強 NGINX 的處理能力,並探討如何利用 Consul 和 consul-template 實作 NGINX 組態的動態更新。njs 模組允許在 NGINX 中執行 JavaScript 程式碼,實作更精細的請求和回應處理,例如解析 JWT。Lua 模組提供輕量級的指令碼執行環境,適用於快速原型開發和簡單邏輯處理。Perl 模組則適合處理更複雜的任務。此外,Ansible 和 Chef 等組態管理工具可以簡化 NGINX 的安裝和組態流程,提高自動化程度。最後,Consul 和 consul-template 的結合,讓 NGINX 組態能隨著服務註冊資訊動態變化,無需手動修改組態檔案和重啟服務,大幅提升 Web 服務的彈性和可維護性。

使用NGINX JavaScript模組(njs)擴充套件NGINX功能

問題描述

需要在NGINX中執行自定義邏輯來處理請求或回應。

解決方案

透過安裝NGINX JavaScript(njs)模組來啟用JavaScript功能。以下步驟假設您已為Linux發行版增加了官方的NGINX儲存函式庫,如第1章所示。

安裝njs模組

  • 使用APT套件管理器安裝NGINX Open Source的njs模組

    $ apt install nginx-module-njs
    
  • 使用APT套件管理器安裝NGINX Plus的njs模組

    $ apt install nginx-plus-module-njs
    
  • 使用YUM套件管理器安裝NGINX Open Source的njs模組

    $ yum install nginx-module-njs
    
  • 使用YUM套件管理器安裝NGINX Plus的njs模組

    $ yum install nginx-plus-module-njs
    

設定njs模組

  1. 建立JavaScript檔案存放目錄(如果尚未建立):

    $ mkdir -p /etc/nginx/njs
    
  2. 建立JavaScript檔案 /etc/nginx/njs/jwt.js,內容如下:

    function jwt(data) {
      var parts = data.split('.').slice(0,2)
        .map(v=>Buffer.from(v, 'base64url').toString())
        .map(JSON.parse);
      return { headers:parts[0], payload: parts[1] };
    }
    
    function jwt_payload_subject(r) {
      return jwt(r.headersIn.Authorization.slice(7)).payload.sub;
    }
    
    function jwt_payload_issuer(r) {
      return jwt(r.headersIn.Authorization.slice(7)).payload.iss;
    }
    
    export default {jwt_payload_subject, jwt_payload_issuer}
    

    內容解密:

    • jwt函式負責解碼JSON Web Tokens(JWTs)。它將JWT分割成header和payload兩部分,並進行base64url解碼和JSON解析。
    • jwt_payload_subjectjwt_payload_issuer函式利用jwt函式來提取JWT中的subject和issuer欄位。
    • .slice(7)用於移除Authorization頭部值的前七個字元(通常是“Bearer ”),以便直接傳遞JWT給jwt函式。
  3. 在NGINX主設定檔中載入njs模組並匯入JavaScript檔案

    load_module /etc/nginx/modules/ngx_http_js_module.so;
    
    http {
      js_path "/etc/nginx/njs/";
      js_import main from jwt.js;
      js_set $jwt_payload_subject main.jwt_payload_subject;
      js_set $jwt_payload_issuer main.jwt_payload_issuer;
      ...
    }
    

    內容解密:

    • load_module指令用於動態載入njs模組。
    • js_path定義了JavaScript檔案的存放路徑。
    • js_import匯入了之前建立的jwt.js檔案,並命名為main
    • js_set指令將NGINX變數與JavaScript函式的傳回值關聯起來。
  4. 定義一個伺服器區塊來傳回由JavaScript設定的變數

    server {
      listen 80 default_server;
      listen [::]:80 default_server;
      server_name _;
    
      location / {
        return 200 "$jwt_payload_subject $jwt_payload_issuer";
      }
    }
    

    內容解密:

    • 該伺服器組態監聽80埠,並傳回客戶端透過Authorization頭部提供的JWT中的subject和issuer值。

驗證

  • 使用一個已知的JWT向伺服器傳送請求,以驗證JavaScript程式碼是否正確執行並傳回預期值。

使用Lua和Perl擴充套件NGINX的功能

NGINX是一個高度可擴充套件的伺服器,可以透過多種語言模組進行擴充套件,包括Lua和Perl。這些模組允許開發者透過指令碼語言增強NGINX的功能,從而實作更複雜的邏輯和處理。

Lua模組的使用

Lua是一種輕量級的指令碼語言,NGINX的Lua模組允許開發者使用Lua語言編寫指令碼,以擴充套件NGINX的功能。下面是一個使用Lua模組的例子:

default_type text/html;
content_by_lua_block {
    ngx.say("hello, world")
}

內容解密:

  • default_type text/html;:設定預設的Content-Type為text/html,這樣NGINX就會將回應的內容當作HTML處理。
  • content_by_lua_block:這是一個NGINX的指令,用於執行Lua程式碼塊。
  • ngx.say("hello, world")ngx.say是Lua模組提供的一個函式,用於輸出字串到客戶端。這裡輸出的是"hello, world"。

Perl模組的使用

Perl是一種成熟的指令碼語言,NGINX的Perl模組允許開發者使用Perl語言編寫指令碼,以擴充套件NGINX的功能。下面是一個使用Perl模組的例子:

load_module modules/ngx_http_perl_module.so;
events {}
http {
    perl_set $app_endpoint 'sub { return $ENV{"APP_DNS_ENDPOINT"}; }';
    server {
        listen 8080;
        location / {
            proxy_pass http://$app_endpoint
        }
    }
}

內容解密:

  • load_module modules/ngx_http_perl_module.so;:載入Perl模組。
  • perl_set:這是一個NGINX的指令,用於設定一個變數的值,這個值是透過執行Perl程式碼獲得的。
  • $app_endpoint:這是一個NGINX變數,它的值是透過執行Perl程式碼sub { return $ENV{"APP_DNS_ENDPOINT"}; }獲得的,這段程式碼傳回環境變數APP_DNS_ENDPOINT的值。
  • proxy_pass http://$app_endpoint:將請求代理到$app_endpoint指定的地址。

使用Ansible安裝和組態NGINX

Ansible是一種流行的組態管理工具,可以用來安裝和組態NGINX。下面是一個使用Ansible安裝和組態NGINX的例子:

---
- hosts: all
  collections:
    - nginxinc.nginx_core
  tasks:
    - name: Install NGINX
      include_role:
        name: nginx
    - name: Configure NGINX
      ansible.builtin.include_role:
        name: nginx_config
      vars:
        nginx_config_http_template_enable: true
        nginx_config_http_template:
          - template_file: http/default.conf.j2
            deployment_location: /etc/nginx/conf.d/default.conf
            config:
              servers:
                - core:
                    listen:
                      - port: 80
                    server_name: localhost
                    log:
                      access:
                        - path: /var/log/nginx/access.log
                          format: main
                    sub_filter:
                      sub_filters:
                        - string: server_hostname
                          replacement: $hostname
                          once: false
                    locations:
                      - location: /
                        core:
                          root: /usr/share/nginx/html
                          index: index.html
        nginx_config_html_demo_template_enable: true
        nginx_config_html_demo_template:
          - template_file: www/index.html.j2
            deployment_location: /usr/share/nginx/html/index.html
            web_server_name: Ansible NGINX collection

內容解密:

  • hosts: all:指定Ansible要操作的主機。
  • collections: nginxinc.nginx_core:使用NGINX官方提供的Ansible集合。
  • include_role: name: nginx:使用nginx角色來安裝NGINX。
  • nginx_config角色用於組態NGINX,包括設定伺服器區塊、位置區塊等。

使用Chef安裝和組態NGINX

Chef是一種流行的組態管理工具,可以用來安裝和組態NGINX。下面是一個使用Chef安裝和組態NGINX的例子:

nginx_install 'nginx' do
  source 'repo'
end

nginx_config 'nginx' do
  default_site_enabled true
  keepalive_timeout 65
  worker_processes 'auto'
  action :create
  notifies :reload, 'nginx_service[nginx]', :delayed
end

內容解密:

  • nginx_install 'nginx' do source 'repo' end:從F5維護的倉函式庫安裝NGINX。
  • nginx_config 'nginx' do ... end:組態NGINX的基本設定,如預設站點是否啟用、keepalive超時時間、worker程式數等。

使用NGINX進行基本認證組態

問題描述

需要在應用程式或內容前增加HTTP基本認證來進行保護。

解決方案

首先,需要生成一個特定格式的檔案,其中密碼需要被加密或雜湊處理,支援的格式包括:

# 註解
使用者名稱1:密碼1
使用者名稱2:密碼2:註解
使用者名稱3:密碼3

使用者名稱是第一欄位,密碼是第二欄位,中間以冒號分隔。第三欄位是可選的,用於新增對使用者的註解。NGINX支援多種密碼格式,其中一種是使用crypt()函式加密的密碼,可以透過openssl passwd命令列工具生成。

生成加密密碼

使用openssl命令生成加密密碼:

$ openssl passwd MyPassword1234

輸出的字串可以被NGINX用於密碼檔案中。

組態NGINX基本認證

在NGINX組態中使用auth_basicauth_basic_user_file指令來啟用基本認證:

location / {
    auth_basic "私人網站";
    auth_basic_user_file conf.d/passwd;
}

auth_basic指令可以放置在httpserverlocation上下文中,它接受一個字串引數,該引數會在使用者未認證時顯示在基本認證彈窗中。auth_basic_user_file指令指定了存放使用者帳號密碼的檔案路徑。

測試組態

使用curl命令加上-u--user引數來測試組態:

$ curl --user myuser:MyPassword1234 https://localhost

詳細說明

有多種方法可以生成基本認證的密碼,並且支援不同的格式,提供不同程度的安全性。除了openssl命令外,Apache的htpasswd命令也可以用來生成密碼。兩者都可以生成NGINX支援的apr1演算法密碼。密碼也可以是加鹽的MD5格式。

程式碼解說

NGINX基本認證組態範例

location / {
    auth_basic "私人網站";
    auth_basic_user_file conf.d/passwd;
}

內容解密:

  1. location /:此指令定義了基本認證將被套用的URL路徑。在此例中,所有請求到根目錄(/)及以下路徑都會受到基本認證保護。
  2. auth_basic "私人網站";:啟用基本認證,並在瀏覽器彈出的認證視窗中顯示「私人網站」。
  3. auth_basic_user_file conf.d/passwd;:指定存放使用者名稱和密碼的檔案位置。NGINX會讀取此檔案來驗證使用者的憑證。

實務經驗與技術深度探討

在實際應用中,基本認證是一種快速且簡單的保護機制,但它有其限制,如傳輸過程中的安全性問題等。因此,在生產環境中,可能需要考慮更安全的認證方式,如使用HTTPS加密傳輸,或者結合其他驗證機制來提升安全性。

相關資源

  • NGINX官方檔案關於基本認證的說明
  • 使用NGINX Plus進行JWT驗證的

使用Consul和consul-template動態組態NGINX

問題描述

需要在環境變化時自動更新NGINX組態。

解決方案

使用Consul和consul-template工具來實作NGINX組態的動態更新。

Consul範本範例

upstream backend { {{range service "app.backend"}}
server {{.Address}};{{end}}
}

這個範本會遍歷Consul中標記為app.backend的服務節點,並為每個節點生成一個server指令。

執行consul-template

$ consul-template -consul-addr consul.example.internal -template \
./upstream.template:/etc/nginx/conf.d/upstream.conf:"nginx -s reload"

這條命令指示consul-template守護程式連線到指定的Consul叢集,使用本地的upstream.template範本檔案來生成/etc/nginx/conf.d/upstream.conf組態檔案,並在組態檔案變更後重新載入NGINX。

詳細說明

Consul是一個強大的服務發現和組態儲存工具。它提供了RESTful API介面,並允許透過DNS介面查詢節點資訊。consul-template工具可以根據Consul中的變化動態地重新範本化組態檔案,使得NGINX組態能夠隨著環境變化而動態調整。

程式碼解說

Consul範本範例程式碼分析

upstream backend { {{range service "app.backend"}}
server {{.Address}};{{end}}
}

內容解密:

  1. upstream backend { ... }:定義了一個名為backend的上游伺服器群組。
  2. {{range service "app.backend"}}:遍歷Consul中註冊為app.backend服務的所有節點。
  3. server {{.Address}};:為每個服務節點生成一個server指令,其中.Address代表節點的IP地址。
  4. {{end}}:結束遍歷迴圈。

透過這種方式,NGINX的上游伺服器組態能夠根據Consul中的服務註冊資訊動態變化,無需手動修改組態檔案。