コンテンツにスキップ

Nginx@Web系ミドルウェア

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. Nginxの仕組み

アーキテクチャ

nginx_architecture

Nginxは、マスタープロセス、ワーカープロセス、プロキシキャッシュストレージ、キャッシュローダー、キャッシュマネージャー、といったコンポーネントから構成される。

Nginxの起動時に最初にマスタープロセスが実行され、Nginxに設定を適用する。

また、マスタープロセスは子プロセスとしてのワーカープロセスを実行し、各ワーカープロセスがリクエストを並列的に処理する。

ワーカープロセスは、キャッシュローダーを使用して、静的ファイルのキャッシュをメモリ上のプロキシキャッシュストレージに保存し、加えて一方で保存されたキャッシュを取得する。

キャッシュマネージャは、保存されたキャッシュの有効期限を管理する。


モジュール

▼ 静的モジュール

静的モジュールは、ビルド後にApacheのバイナリに組み込む必要がある。

必要不要かにかかわらず、Nginxと一緒に強制的に実行する必要がある。

▼ 動的モジュール

動的モジュールは、ビルド後にNginxのバイナリに組み込む必要がない。

必要な場合にのみインストールし、また実行すればよい。

動的モジュールは、load_moduleディレクティブで読み込む。

load_module modules/<動的モジュール名>.so;

執筆時点 (2024/03/12) では、以下の動的モジュールがあらかじめインストールされている。

それ以外の動的モジュール (nginx-module-otel) はインストールする必要がある。

$ ls /etc/nginx/modules

ngx_http_geoip_module.so
ngx_http_js_module-debug.so
ngx_http_xslt_filter_module.so
ngx_stream_js_module-debug.so
ngx_http_image_filter_module-debug.so
ngx_http_js_module.so
ngx_stream_geoip_module-debug.so
ngx_stream_js_module.so
ngx_http_geoip_module-debug.so
ngx_http_image_filter_module.so
ngx_http_xslt_filter_module-debug.so
ngx_stream_geoip_module.so


02. ユースケース

リバースプロキシのミドルウェアとして

▼ 構成

Nginxを配置し、リクエストをwebサーバーにルーティングする。

リバースプロキシのミドルウェアとして使用する場合、Nginxをパブリックネットワークに公開しさえすれば、パブリックネットワークからNginxを介して、後段のwebサーバーにリクエストを送信できるようになる。

▼ HTTP/HTTPSプロトコルの場合

Nginxは、インバウンド通信をappサーバーにルーティングする。

また、appサーバーからのレスポンスのデータが静的ファイルであった場合、これのキャッシュをプロキシキャッシュストレージに保存する。

以降に同じ静的ファイルに関するインバウンド通信があった場合、Nginxはappサーバーにルーティングせずに、保存されたキャッシュを取得し、レスポンスとして返信する。

リバースプロキシサーバーとしてのNginx

*実装例*

#-------------------------------------
# HTTPリクエスト
#-------------------------------------
server {
    server_name example.com;
    listen 80;
    return 301 https://$host$request_uri;

    #-------------------------------------
    # 静的ファイルであればNginxでレスポンス
    #-------------------------------------
    location ~ ^/(images|javascript|js|css|flash|media|static)/ {
        root /var/www/foo/static;
        expires 30d;
    }

    #-------------------------------------
    # 動的ファイルであればwebサーバーにルーティング
    #-------------------------------------
    location / {
        proxy_pass $scheme://$host$request_uri;
    }
}

*実装例*

もし分散トレースを採用する場合、マイクロサービス間でトレースコンテキストを伝播する必要がある。

クライアント/サーバー側のリバースプロキシでトレースコンテキストを伝播できるようにする。

サービスメッシュツール (例:Istio) を使用すれば、これのサイドカープロキシがアップストリーム側のコンテナにトレースコンテキストを伝播してくれるが、Nginxであれば自前で実装する必要がある。

# クライアント側
http {
    # $_request_id
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$_request_id"';

    server {

        listen 80;

        # REQUEST-IDからリクエストIDを取得する
        set $tmp $request_id;

        # X-REQUEST-IDヘッダーにトレースIDがあれば、リクエストIDを上書きする
        if ($http_x_request_id) {
            set $tmp $http_x_request_id;
        }

        access_log logs/access.log  main;

        location / {
            proxy_pass $scheme://$host$request_uri;
            # X-REQUEST-IDヘッダーにトレースIDを設定し、リクエスト送信する
            proxy_set_header X-Request-ID $tmp;
        }
    }
}
# サーバー側
http {
    # $_request_id を参照する
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$_request_id"';

    server {

        listen 80;

        # REQUEST-IDからリクエストIDを取得する
        set $tmp $request_id;

        # X-REQUEST-IDヘッダーにトレースIDがあれば、リクエストIDを上書きする
        if ($http_x_request_id) {
            set $tmp $http_x_request_id;
        }

        access_log logs/access.log  main;

        location = /server {
            echo "This is server side";
        }
    }
}

▼ FastCGIプロトコルの場合

NginxとPHP-FPMの組み合わせ

PHP-FPMはFastCGIプロトコルでリクエストを受信するため、これに変換する必要がある。

静的ファイルのインバウンド通信が送信されてきた場合、Nginxはそのままレスポンスを返信する。

動的ファイルのインバウンド通信が送信されてきた場合、Nginxは、FastCGIプロトコルを介して、PHP-FPMにインバウンド通信をリダイレクトする。

# 設定ファイルのバリデーション
$ php-fpm -t

*実装例*

#-------------------------------------
# HTTPリクエスト
#-------------------------------------
server {
    listen      80;
    server_name example.com;
    root        /var/www/foo/public;
    index       index.php index.html;

    include /etc/nginx/default/nginx.conf;

    #『/』で始まる全てのインバウンド通信の場合
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    #--------------------------------------------------
    # インバウンド通信をFastCGIプロトコルでルーティングする。
    # OSによって、fastcgi_paramsファイルの必要な設定が異なる
    #--------------------------------------------------
    location ~ \.php$ {
        # ルーティング先のTCPソケット
        fastcgi_pass   127.0.0.1:9000;
        # もしくは、Unixドメインソケット
        # fastcgi_pass unix:/run/php-fpm/www.sock;

        # ルーティング先のURL (rootディレクティブ値+パスパラメータ)
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;

        # 設定ファイルからデフォルト値を読み込む
        include        fastcgi_params;
    }
}


フォワードプロキシのミドルウェアとして

▼ 構成

フォワードプロキシのミドルウェアとして使用できる。

クライアントサイドにNginxを配置し、リクエストを外部ネットワークにルーティングする。

▼ HTTP/HTTPSプロトコルの場合

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

     server {
         listen                         3128;

         resolver                       8.8.8.8;

         proxy_connect;
         proxy_connect_allow            443 563;
         proxy_connect_connect_timeout  10s;
         proxy_connect_read_timeout     10s;
         proxy_connect_send_timeout     10s;

         # 受信したリクエストを外部ネットワークにルーティングする。
         location / {
             proxy_pass $scheme://$host$request_uri;
             proxy_set_header Host $host;
             proxy_set_header X-Forwarded-Proto $scheme;
             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
             proxy_set_header X-Forwarded-Port $remote_port;
         }
     }
}


L4/L7ロードバランサ-のミドルウェアとして

L7ロードバランサーの場合

L7ロードバランサーとして使用できる。

Nginxは、HTTPプロコトルのインバウンド通信を複数のwebサーバーに負荷分散的に振り分ける。

受信した通信がHTTPプロトコルであった場合、HTTPSリクエストにリダイレクトすると良い。

また、HTTPSプロトコルであれば、HTTPに変換してルーティングすると良い。

ただし、HTTPSプロトコルのリクエストを受信するために、NginxにSSL証明書を設定する必要がある。

*実装例*

#-------------------------------------
# HTTPリクエスト
#-------------------------------------
server {
    server_name example.com;
    listen 80;
    # リダイレクト
    return 301 https://$host$request_uri;
}

#-------------------------------------
# HTTPSリクエスト
#-------------------------------------
server {
    server_name example.com;
    listen 443 ssl http2;
    index index.php index.html;

    #-------------------------------------
    # SSL
    #-------------------------------------
    ssl on;
    ssl_certificate /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    add_header Strict-Transport-Security "max-age=86400";

    location / {
        proxy_pass http://foo_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Port $remote_port;
    }

    # ルーティング先のリスト
    upstream foo_servers {
        server srv1.example.com;
        server srv2.example.com;
        server srv3.example.com;
    }
}

L4ロードバランサーの場合

Nginxは、TCPスリーウェイハンドシェイクを複数のサーバーにTCPプロトコルのまま負荷分散的に振り分ける。

L4ロードバランサーのため、宛先のサーバーでTCPプロトコルをHTTPプロトコルに変換するように処理しなければならない。

*実装例*

#-------------------------------------
# TCPスリーウェイハンドシェイク
#-------------------------------------
stream {
    error_log /var/log/nginx/stream.log info;
    proxy_protocol on;

    upstream grpc_servers {
        server 192.168.0.1:50051;
        server 192.168.0.2:50051;
    }

    server {
        listen 50051;
        proxy_pass grpc_servers;
    }
}


API Gatewayとして

NginxをAPI Gatewayとして使用する。

API Gatewayのため、リバースプロキシやロードバランサーとは異なり、以下の機能を持つ必要がある。

  • 受信した通信を適切なマイクロサービスのAPIにルーティング
  • 認証
  • トレースIDの付与
  • キャッシュの作成
  • リクエスト制限

*実装例*

server {
   listen 80 default_server;
   listen [::]:80 default_server;

   # Products API
   location /api/products {
       proxy_pass http://products.api.com:80;
   }

   # Users API
   location /api/users {
       proxy_pass http://users.api.com:80;
   }
}