コンテンツにスキップ

ビルトインモジュール@Nginx

はじめに

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


01. ngx_http_auth_request_module

ディレクティブ

▼ auth_request

認可リクエストのURLのパスを設定する。

Nginxは、認可レスポンスのステータスコードが200であれば認証成功、401ステータスまたは403ステータスであれば認証失敗とし、アプリケーションへのリクエストを許可/拒否する。

なお、有料版のngx_http_auth_jwt_moduleモジュールであれば、認可サーバーにJWT取得のための認可リクエストを直接的に送信できる。

http {

    # 認証が必要なパス
    location / {

        # 認可リクエストの宛先とするパスを設定する
        # ここでは、認可リクエストをOAuth2 Proxyに送信する
        auth_request /oauth2/auth;

        ...

    }

    location = /oauth2/auth {
        # 認可サーバーのドメイン名を設定する
        # ここでは、Nginxと認可サーバーの間にOAuth2 Proxyを配置している
        proxy_pass              https://<ドメイン名>:4180;
        proxy_pass_request_body off;
        proxy_set_header        Host $http_host;
    }

}


02. ngx_http_core_module

httpブロック

▼ httpブロック

全てのHTTPプロトコルのインバウンド通信に共通する処理を設定する。

http {
    # Nginxのバージョンを表示するか否か
    server_tokens      off;
    # MIMEタイプを設定
    include            /etc/nginx/mime.types;
    default_type       application/octet-stream;

    # sendfileシステムコールを使用するか否か
    sendfile           on;
    # ヘッダーとファイルをまとめてレスポンスするか否か
    tcp_nopush         on;
    # KeepAliveを維持する時間
    keepalive_timeout  65;
    default_type       application/octet-stream;
    include            /etc/nginx/mime.types;
    include            /etc/nginx/conf.d/*.conf;

    server {
        ...
    }
}

▼ log_format

非構造化ログの場合は、以下の通りとする。

http {

    log_format         main  "$remote_addr - $remote_user [$time_local] "$request_uri" "
    "$status $body_bytes_sent "$http_referer" "
    ""$http_user_agent" "$http_x_forwarded_for"";

    access_log         /dev/stdout  main;
    error_log          /dev/stderr  warn;
}

構造化ログの場合は、以下の通りとする。

ここでは、JSONの設計規則に則って、キー名をローワーキャメルケースにしている。

http {

    log_format         main         escape=json '{'
    '"remoteAddr": "$remote_addr",'
    '"remoteUser": "$remote_user",'
    '"requestUri": "$request_uri",'
    '"status": "$status",'
    '"bodyBytesSent": "$body_bytes_sent",'
    '"httpReferer": "$http_referer",'
    '"httpUserAgent": "$http_user_agent",'
    '"httpXForwarded-for": "$http_x_forwarded_for"'
    '}';

    access_log         /dev/stdout  main;
    error_log          /dev/stderr  warn;
}

▼ map

指定した変数の値を、別の変数の値によって切り替える。

http {

    map $foo $bar {
        foo1 bar1
        foo2 bar2
        foo3 bar3
    }
}

*実行例*

正規表現を使用して、W3C Trace Context仕様のトレースIDをX-Ray仕様に変換する。

http {

    log_format main escape=json '{'
    '"trace-id": "$xray_trace_id"'
    '}';

    map $otel_trace_id $xray_trace_id {
        # W3C Trace Context仕様の場合、前半8文字と9文字目以降の文字を抽出して、X-Ray仕様に変換する
        # @see https://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html#api-segmentdocuments-subsegments
        "~(^.{8})(.*)" 1-$1-$2;
        # それ以外の場合は0とする
        default        0;
    }

}


locationブロック

特定のパスのインバウンド通信に関する処理を設定する。

*実装例*

各設定の優先順位に沿った以下の順番で実装した方が良い。

# 1. ドキュメントルートを指定したインバウンド通信の場合
location = / {

}

# 2. 『/images/』で始まるインバウンド通信の場合
location ^~ /images/ {

}

# 3と4. 末尾が、『gif、jpg、jpegの形式』 のインバウンド通信の場合
# バックスラッシュでドットをエスケープし、任意の文字列ではなく『ドット文字』として識別できるようにする。
location ~* \.(gif|jpg|jpeg)$ {

}

# 5-1. 『/docs/』で始まる全てのインバウンド通信の場合
location /docs/ {

}

# 5-2. 『/』で始まる全てのインバウンド通信の場合
location / {

}

ルートの一致条件は、以下の通りである。

優先順位 prefix ルートの一致条件 ルート例
1 = 指定したルートに一致する場合。 https://example.com/
2 ^~ 指定したルートで始まる場合。 https://example.com/images/foo.gif
3 ~ 正規表現 (大文字・小文字を区別する) 。 https://example.com/images/FOO.jpg
4 ~* 正規表現 (大文字・小文字を区別しない) 。 https://example.com/images/foo.jpg
5 なし 指定したルートで始まる場合。 https://example.com/foo.html
https://example.com/docs/foo.html


serverブロック

特定のルーティング先に関する処理を設定する。

*実装例*

server {
    # 80番ポートで受信
    listen      80;
    # Hostヘッダー値
    server_name example.com;
    root        /var/www/foo;
    index       index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass  unix:/run/php-fpm/www.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}


ディレクティブ

▼ default_type

Content-Typeヘッダー値がmime.typesファイルにないMIME typeであった場合に適用するMIME typeを設定する。

*実装例*

任意のMIME type (指定なし) のインバウンド通信を処理する。

default_type application/octet-stream

▼ listen

インバウンド通信を待ち受けるポート番号を設定する。

*実装例*

インバウンド通信を80番ポートで受信する。

listen 80;

インバウンド通信を443番ポートで受信する。

listen 443 ssl;

▼ sendfile

クライアントへのレスポンス時に、ファイル送信のためにLinuxのsendfileシステムコールを使用するか否かを設定する。

ファイル返信処理をOS内で実行するため、処理が速くなる。

使用しない場合、Nginxがレスポンス時に自身でファイル返信処理を実行する。

*実装例*

sendfile on;

▼ server_name

受信する通信のHostヘッダー値を設定する。

補足としてHostヘッダーには、インバウンド通信のルーティング先のドメイン名が割り当てられている。

server_name example.com;

パブリックIPアドレスを直接的に記述しても良い。

server_name 192.168.0.0;

注意点として、同じIPアドレスからのインバウンド通信のみを受信する場合は、インバウンド通信のHostヘッダー値は常に127.0.0.1 (127.0.0.1) であるため、127.0.0.1を設定できる。

127.0.0.1としても良いが、127.0.0.1のIPアドレスが127.0.0.1でない場合も考慮して、127.0.0.1とした方が良い。

server_name 127.0.0.1;

▼ ssl

HTTPSプロトコルを受信する場合、SSL/TLSプロトコルを有効化する必要がある。

*実装例*

ssl on;

▼ ssl_certificate

HTTPSプロトコルを受信する場合、SSL証明書のパスを設定する。

*実装例*

ssl_certificate /etc/nginx/ssl/server.crt;

▼ ssl_certificate_key

HTTPSプロトコルを受信する場合、SSL証明書と対になる秘密鍵へのパスを設定する。

*実装例*

ssl_certificate_key /etc/nginx/ssl/server.key;

▼ ssl_client_certificate

HTTPSプロトコルを受信する場合、クライアント証明書のパスを設定する。

*実装例*

ssl_client_certificate /etc/nginx/ssl/client.crt;

▼ tcp_nopush

上述のLinuxのsendfileシステムコールを使用する場合に適用できる。

クライアントへのレスポンス時、ヘッダーとファイルを1個のパケットにまとめて返信するか否かを設定する。

*実装例*

tcp_nopush on;

▼ try_files

指定されたパスのファイルを順に探してアクセスする。

また、最後のパラメーターで内部リダイレクトする。

最後のパラメーターでは、異なるパスまたはステータスコードを指定できる。

もし、nginxとアプリケーションを異なる仮想環境で稼働させている場合、try_filesディレクティブがファイル探索の対象とする場所は、あくまでnginxの稼働する仮想環境内になることに注意する。

内部リダイレクトによって、nginx内でリクエストが再処理される。

異なるパスに内部リダイレクトしていた場合は、パスに合ったlocationブロックで改めて処理される。

内部リダイレクトは、URLを書き換えてリダイレクトせずに処理を続行する『リライト』とは異なることに注意する。

location / {
    try_files file ... uri;
}
location / {
    try_files file ... =code;
}

*実装例*

location / {
    # 1. 『/foo.html』のパスで、ファイルをレスポンス
    # 2. 『/foo.html/』のパスで、ファイルをレスポンス
    # 3. 『/index.php?query_string』のパスで内部リダイレクト
    try_files $uri $uri/ /index.php?query_string;
}

# 内部リダイレクト後は、『/index.php?foo=bar』のため、以下で処理される。
location ~ \.php$ {
    # php-fpmにルーティングされる。
    fastcgi_pass  127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include       fastcgi_params;
}


ヘルスチェックの受信

▼ nginxによるレスポンス

webサーバーのみヘルスチェックを受信する。

ヘルスチェック用のserverブロックで、gifファイルを含むレスポンスを返信するようにlocationブロックを定義する。

Nginxでアクセスログを出力する必要はないため、locationブロックではaccess_logを無効化する。

*実装例*

server {
    listen 80      default_server;
    listen [::]:80 default_server;
    root           /var/www/foo;
    index          index.php index.html;

    location /healthcheck {
        empty_gif;
        access_log off;
        break;
    }
}

▼ アプリケーションによるレスポンス

webサーバーとアプリケーションの両方でヘルスチェックを受信する。

アプリケーション側に200ステータスを含むレスポンスを返信するエンドポイントを実装したうえで、ヘルスチェック用のserverブロックでwebサーバーにルーティングするようにlocationブロックを定義する。

Nginxでアクセスログを出力する必要はないため、locationブロックではaccess_logを無効化する。

*実装例*

server {
    listen 80      default_server;
    listen [::]:80 default_server;
    root           /var/www/foo;
    index          index.php index.html;

    location /healthcheck {
        try_files $uri $uri/ /index.php?$query_string;
        access_log off;
    }
}


03. ngx_http_index_module

ディレクティブ

▼ index

リクエストのURLがトレイリングスラッシュで終了する全ての場合、指定されたファイルをURLの末尾に追加する。

*実装例*

index index.php;


04. ngx_http_headers_module

ディレクティブ

▼ add_header

レスポンス時に付与するレスポンスヘッダーを設定する。

*実装例*

# Referrer-Policyヘッダーに値を設定する
add_header Referrer-Policy "no-referrer-when-downgrade";


05. ngx_http_upstream_module

ブロック

▼ upstream

ロードバランシング先を設定する。

デフォルトでは、加重ラウンドロビン方式を基に通信をルーティングする。

注意点としては、このままでは宛先がスケーリングした時にIPアドレスを動的に取得できない。

そのため、resolverディレクティブ、upstreamディレクティブ、UNIXドメインソケット、を使用する必要がある。(ちょっと複雑すぎる...)

*実装例*

upstream foo_servers {
    server 192.168.0.1:80;
    server 192.168.0.2:80;
    server 192.168.0.3:80;
}


06. ngx_http_fast_cgi_module

ディレクティブ

▼ fastcgi_params

FastCGIプロトコルでインバウンド通信をルーティングする場合、ルーティング先で使用する変数とその値を設定する。

*実装例*

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

▼ fastcgi_pass

FastCGIプロトコルでインバウンド通信をルーティングする場合、ルーティング先のアドレスとポートを設定する。

*実装例*

fastcgi_pass 127.0.0.1:9000;


07. ngx_http_rewrite_module

ディレクティブ

▼ if

条件分岐を設定する。

if ($request_uri = /) {
    # foo変数にfooを設定する
    set $foo foo;
}

if ($host ~* teambox.com) {
    # bar変数にbarを設定する
    set $bar bar;
}


08. ngx_http_grpc_module

ディレクティブ

▼ grpc_pass

gRPCによるHTTPリクエストの宛先を設定する。

HTTP/2 (例:gRPCなど) を有効化する必要がある。

*実装例*

grpc_pass 127.0.0.1:80;
server {
    listen 80      default_server;

    # HTTP/2を有効化する
    http2 on;

    location / {
        grpc_pass 127.0.0.1:80;
    }
}


09. ngx_http_proxy_module

ディレクティブ

▼ proxy_pass

HTTPプロトコルでインバウンド通信をルーティングする場合、ルーティング先のアドレスとポートを設定する。

*実装例*

proxy_pass http://127.0.0.1:80;

受信したリクエストの情報をそのまま使ってプロキシする場合、変数を使用する。

proxy_pass $scheme://$host$request_uri;


10. ngx_http_stub_status_module

ディレクティブ

▼ stub_status

特定のパスにリクエストを送信することで、メトリクスを取得できるようにする。

location = /metrics {
    stub_status;
}
$ curl localhost/metrics

Active connections: 1
server accepts handled requests
 93370 93370 74159
Reading: 0 Writing: 1 Waiting: 0


11. ngx_otel_module

ngx_otel_moduleとは

Nginxコミュニティ製のモジュールであり、NginxをOpenTelemetryで計装できるようにする。

執筆時点 (2024/03/10) では、セットアップの簡単さやパフォーマンスでOpenTelemetry製のotel_ngx_moduleに勝っているらしい。


セットアップ

▼ 未ビルドの場合

モジュールをインポートする前に、ビルドする必要がある。

なお、gRPCのビルドは30分ほどかかるため、gRPCを含むビルド済みのモジュールをインストールした方が良い。

$ git clone https://github.com/nginxinc/nginx-otel.git
$ cd nginx-otel
$ mkdir build
$ cd build
$ cmake -DNGX_OTEL_NGINX_BUILD_DIR=/path/to/configured/nginx/objs
$ make -j2
$ make install

ngx_otel_moduleは動的モジュールであるため、nginx.confファイルでモジュールをインポートする必要がある。

load_module modules/ngx_otel_module.so;

その他、alpineはMercurialからインストールすると良い。

▼ ビルド済みの場合

ビルド済みモジュールをインストールする。

Nginx (1.25.3) であればビルトインパッケージになっているため、ビルドが不要である。

# aptリポジトリから
$ apt install -y nginx-module-otel
# yumリポジトリから
$ yum install -y nginx-module-otel

Alpineの場合は、執筆時点 (2024/03/13) でalpineリポジトリにngx_otel_moduleがなく、Nginxのalpineリポジトリにパッケージがある。

apkコマンドはそのまま使用すると常に最新をインストールしてしまう。

そこで、wgetコマンドで一度ファイルを取得し、apkコマンドでそのファイルからモジュールをインストールする。

# nginxのalpineリポジトリから
$ wget -qO nginx-module-otel-<リビジョン>.apk https://nginx.org/packages/mainline/alpine/<バージョン>/main/x86_64/nginx-module-otel-<リビジョン>.apk
$ apk add --allow-untrusted nginx-module-otel-<バージョン>.apk


ディレクティブ

▼ otel_exporter

Exporterを設定する。

執筆時点 (2024/03/14) 時点では、gRPC用のエンドポイントしかありません。

http {

    otel_exporter {
        endpoint foo-opentelemetry-collector.foo-namespace.svc.cluster.local:4317;
    }
}

▼ otel_service_name

http {
    otel_service_name foo-service;
}

▼ otel_trace

分散トレースを有効化するかどうかを設定する。

http {
    otel_trace on;
}

▼ otel_trace_context

http {
    # 受信したCarrierにトレースコンテキストがない場合はInjectし、あればExtractする
    otel_trace_context propagate;
}

▼ otel_span_name

スパン名を設定する。

デフォルトでは、リクエストのLocation値がスパン名になる。

http {

    location / {
        otel_span_name foo;
        proxy_pass $scheme://$host$request_uri;
    }
}

▼ otel_span_attr

スパンの属性を設定する。

http {

    location / {
        otel_span_attr otel.resource.deployment.environment <実行環境名>;
        proxy_pass $scheme://$host$request_uri;
    }
}


変数

$otel_trace_id

トレースIDが割り当てられている。

ログにトレースIDを埋め込む場合に使用できる。

http {
    log_format main escape=json '{'
    '"TraceId": "$otel_trace_id"'
    '}';
}

$otel_span_id

現在のスパンIDが割り当てられている。

$otel_parent_id

親スパンのスパンIDが割り当てられている。

$otel_parent_sampled

受信したリクエストに親スパンが存在する場合、1になる。

Parent Basedなサンプリングを実行できる。

http {
    otel_trace $otel_parent_sampled;
}