nginx-1.9.11で動的モジュールをサポート

nginxの記事を書くのは久しぶりの滝澤です。

nginxにApache HTTP Serverの動的共有モジュール(Dynamic Shared Object)(DSO)のような機能が欲しいと思っていた人も多いでしょう。筆者もそうです。秘伝のタレのようなビルド用のスクリプト(実際はRPMパッケージのSPECファイル)を保守し続けるのは辛いなと思っていました。

そのような方々に朗報です。2016年2月9日にリリースされたnginx-1.9.11において動的モジュール(Dynamic Modules)がサポートされました。

しかし、Apache HTTP ServerのDSOと比べると、現時点ではまだ制約があります。 本記事ではnginx-1.9.11における動的モジュールの説明と制約について説明します。

動的モジュール

nginxは多数のモジュールから構成されており、nginxをビルドするときにモジュールがnginxバイナリに静的に組み込まれます。 追加したいモジュールがあるときにも、同様にnginxのビルド時にそのモジュールを指定することによりnginxバイナリにそのモジュールが静的に組み込まれます。 このようにnginxのモジュールを追加したり変更したりするたびにnginxバイナリ自体をビルドし直す必要があり、運用が煩雑でした。

しかし、nginx-1.9.11で動的モジュールがサポートされ、動的モジュールを利用時(nginxの起動時やリロード時)に組み込むことができるようになりました。

次のように、設定ファイルにload_moduleディレクティブの値として動的モジュールの共有オブジェクトファイルのパスを指定して、nginxを起動あるいはリロードすると動的モジュールを利用できるようになります。

load_module "modules/foo_module.so";

nginxの標準モジュールすべてが動的モジュールとして利用できるわけではありません。現バージョンでは次のモジュールが動的モジュールとして利用することができます。

  • XSLT (ngx_http_xslt_module)
  • Image Filter (ngx_http_image_filter_module)
  • GeoIP (ngx_http_geoip_module)
  • Mail (ngx_mail_module)
  • Stream (ngx_stream_module)

また、サードパーティモジュールも動的モジュールとして利用可能になりますが、configスクリプトの修正が必要となるため、動的モジュールへの対応をサポートしているモジュール以外はそのままでは利用できません。

設定ファイルへの指定

設定ファイルへの記述方法について説明します。

動的モジュールを読み込ませるためには、設定ファイルにload_moduleディレクティブの値に動的モジュールの共有オブジェクトファイルのパスを指定します。

load_module "modules/foo_module.so";
load_module "modules/ngx_stream_module.so";

load_moduleディレクティブの記述場所はmainコンテキストです。 さらに、次の例のように各ブロック(events, http, stream, mail)より前に記述する必要があります。

user  nginx;
worker_processes  1;

load_module "modules/ngx_stream_module.so";
load_module "modules/ngx_http_geoip_module.so";

events {
    worker_connections  1024;
}

http {
以下略

動かない例も示します。次のようにeventsブロックより後にload_moduleディレクティブを記述したとします。

user  nginx;
worker_processes  1;

events {
    worker_connections  1024;
}

load_module "modules/ngx_stream_module.so";
load_module "modules/ngx_http_geoip_module.so";

http {
以下略

この場合は、次のようなメッセージが出力され、動きません。

nginx: [emerg] "load_module" directive is specified too late in /usr/local/nginx/conf/nginx.conf:16

動的モジュールのビルド方法

現バージョンでは動的モジュール単体だけをビルドすることはできません。 動的モジュールをビルドするためには、nginxバイナリをビルドする手順とほぼ同じ手順を行う必要があります。

なお、将来のバージョンではモジュール単体をビルドできるようにする計画があるようです。

Introducing Dynamic Modules in NGINX 1.9.11 より

In future releases, we plan to add the ability to compile modules after the NGINX binary has been compiled.

configureスクリプトのオプション

まず、configureスクリプトの実行時にいくつかオプションを指定します。

configureスクリプトの動的モジュール関連のオプションとしては次のようなものがあります。

  --modules-path=PATH                set modules path
  --with-http_xslt_module=dynamic    enable dynamic ngx_http_xslt_module
  --with-http_image_filter_module=dynamic
                                     enable dynamic ngx_http_image_filter_module
  --with-http_geoip_module=dynamic   enable dynamic ngx_http_geoip_module
  --with-mail=dynamic                enable dynamic POP3/IMAP4/SMTP proxy module
  --with-stream=dynamic              enable dynamic TCP proxy module
  --add-dynamic-module=PATH          enable dynamic external module

それぞれ見ていきましょう。

動的モジュールのインストール先の指定

--modules-pathの値に動的モジュールの共有オブジェクトファイルをインストールするパスを指定します。デフォルトでは--prefixで指定したディレクトリ下のmodulesディレクトリになります。

  --modules-path=PATH

/usr/local/nginx/modulesにインストールする場合は次のように指定します。

  --modules-path=/usr/local/nginx/modules

動的モジュールの指定(標準モジュールの場合)

nginxの標準モジュールの場合はオプションの値に"dynamic"を指定します。

  --with-stream=dynamic

動的モジュールの指定(サードパーティモジュールの場合)

サードパーティモジュールの場合は--add-dynamic-moduleの値にそのソースコードのディレクトリを指定します。

  --add-dynamic-module=/path/to/foo_module

configureスクリプトの実行

configureスクリプトのオプションの指定が完了したら、実行します。

$ ./configure \
  --prefix=/usr/local/nginx \
  --modules-path=/usr/local/nginx/modules \
  --user=nginx --group=nginx \
中略
  --with-stream=dynamic \
  --with-stream_ssl_module \
  --add-dynamic-module=/path/to/foo_module

checking for OS
 + Linux 3.10.0-327.4.5.el7.x86_64 x86_64
中略
adding module in /path/to/foo_module
 + foo_module was configured
中略
Configuration summary
  + using threads
以下略

configureスクリプトの実行が成功したら、makeを実行してnginxバイナリと一緒にビルドします。 makeが完了するとobjsディレクトリに動的モジュールが拡張子soの共有オブジェクトのファイルとして生成されます。

$ make
中略
$ ls -1 objs/*.so
objs/foo_module.so
objs/ngx_stream_module.so

さらに、make installを実行すると、nginxバイナリはsbinディレクトリに、動的モジュールはmodulesディレクトリにコピーされます。

$ sudo make install

$ ls -1 /usr/local/nginx/{sbin,modules}
/usr/local/nginx/modules:
foo_module.so
ngx_stream_module.so

/usr/local/nginx/sbin:
nginx

なお、すでに同じ環境でビルドされたnginxバイナリがインストール済みの場合は、objsディレクトリに生成された動的モジュールのファイルをmodulesディレクトリに手動で配置しても利用できます。

$ sudo cp objs/*.so /usr/local/nginx/modules/

ただし、ビルド環境のライブラリとAPIの有無およびビルドオプションが異なる場合は、後述するsignatureの問題でうまく動かないこともあります。

制約事項

上述した内容にも制約がありますが、以下に述べるような制約もあります。

nginxバイナリとモジュールのバージョンが異なると動作しない

nginxバイナリとモジュールのバージョンが異なると動作しません。 これは、nginxバイナリだけバージョンアップして、動的モジュールはそのままのバージョンの場合は、バージョンが異なるため動作しなくなるということになります。

このことについて説明します。

nginxのビルド時に、nginxバイナリおよび各モジュールのバイナリにバージョン番号から生成される整数値が埋め込まれます。 例えば、nginx-1.9.11の場合は"1009011"になります。

nginxはモジュールの追加時にモジュールのバージョン番号の整数値がnginxバイナリが持っているものと同じかを確認します。異なる場合は次のようなメッセージが出力されます。

nginx: [emerg] module "/usr/local/nginx/modules/ngx_stream_module.so" version 1009011 instead of 1009012 in /usr/local/nginx/conf/nginx.conf:8

なお、バージョン番号の整数値はソースコードsrc/core/nginx.h内のnginx_versionに定義されています。

nginxバイナリとモジュールのsignatureが異なると動作しない

nginxバイナリと異なる環境でビルドされた動的モジュールを組み合わせて動かすことはできません。 これにより困ることの例としては、公式サイトやディストリビューションからnginxのRPMパッケージをインストールした環境に、その環境でビルドした動的モジュールを利用することができません。

このことについて説明します。

nginxのビルド時に、ビルド環境のライブラリとAPIの有無およびビルドオプションからsignatureの文字列が生成され、nginxバイナリおよび各モジュールのバイナリに埋め込まれます。

例えば、nginx公式RPMパッケージ(CentOS 7, X86_64)のnginx-1.9.11のnginxバイナリのsignatureの文字列は次のようになっています。

8,4,8,001011111101001111111111110110111

nginxはモジュールの追加時にモジュールのsignatureがnginxバイナリが持っているものと同じかを確認します。signatureが異なる場合は次のようなメッセージが出力されます。

nginx: [emerg] module "/etc/nginx/modules/ngx_stream_module.so" is not binary compatible in /etc/nginx/nginx.conf:8

そのため、nginxバイナリと異なる環境でビルドされたモジュールを組み合わせて動かすことは基本的にはできません。 ただし、ビルド環境のライブラリとAPIの有無およびビルドオプションがすべて同じであればsignatureも同じになるため、この場合は動作します。

開発者向けのメーリングリストでもsignatureの問題は提起されているため、将来、何らかの改善が行われると思われます。

なお、signatureがどのような条件で構成されているかを確認してみたい方は、ソースコードsrc/core/ngx_module.h内のNGX_MODULE_SIGNATUREの定義を見てみてください。

参考資料

株式会社ハートビーツのインフラエンジニアから、ちょっとした情報をお届けします。