nghttpx - HTTP/2 リバースプロキシー / L7 ロードバランサー
nghttpx は nghttp2 プロジェクトで開発している HTTP/2 対応のリバースプロキシー / L7 ロードバランサーです.
この文書は v1.11.0 開発バージョンを元に執筆しました.
リバースプロキシーとして使ってみよう
nghttpx のもっとも簡単な使い方はサーバー証明書を用意して:
$ nghttpx server.key server.crt
を実行することです. server.key はサーバーの秘密鍵ファイル, server.crt は証明書のファイルです. どちらも PEM 形式です. この構成では nghttpx は全インターフェースのポート 3000 番でTLS 接続を受け付け, バックエンドサーバー 127.0.0.1:80 にフォワードします. バックエンドの接続は TLS なしになります. またはフォワードは HTTP リクエスト毎に行います.
nghttpx はすべての設定をコマンドラインオプションで指定できますが, 設定ファイルに書くこともできます. シェルで特別に意味を持つ文字を入力することもあるので以下では設定ファイルを使うことにします. 以下のような設定ファイル nghttpx.conf を用意し:
private-key-file=server.key
certificate-file=server.crt
nghttpx を以下のように実行します:
$ nghttpx --conf nghttpx.conf
デフォルトでは /etc/nghttpx/nghttpx.conf を見に行くようになっています. 設定ファイルの内容はコマンドラインオプションで上書き(一部のオプションは上書きではなく追加)できます.
対応プロトコルはどうなっているのでしょうか. 上記の場合, HTTP/2, HTTP/1.1 をフロントエンドでサポート1しています. ALPN または NPN でプロトコルのネゴシエーションをします. バックエンドは HTTP/1.1 になります. バックエンドで HTTP/2 を使う方法はすぐ後で書きます.
TLS に関しては nghttpx は充実しています2. OCSPステープリング3, TLS セッションチケット秘密鍵の自動ローテーション, 複数 nghttpx インスタンス間の TLS セッションチケット秘密鍵および TLS セッションキャッシュの共有4もサポートしています.
バックエンドの接続先を変更してみましょう. この場合 backend
オプションを使います. 127.0.0.1:8080 にフォワードするには以下のように設定ファイルに書きます:
backend=127.0.0.1,8080
アドレスとポートの間は ":" ではなくて "," なので注意してください.
バックエンドで使うアプリケーションプロトコルはデフォルトでは HTTP/1.1 でした. HTTP/2 を使う場合は backend
オプションで:
backend=127.0.0.1,8080;;proto=h2
とします. ";" が二個ありますが誤植ではありません. これについてはすぐ後で説明します. この文書の執筆時点ではバックエンドのアプリケーションプロトコルは明示的に指定する必要があります.
nghttpx では proto=h2
など backend
オプション等で使用する追加設定をパラメーターと称しています. backend
オプションでパラメーターを使ってバックエンド接続の設定を変更することができます. パラメーターは ";" で区切ります.
バックエンド接続を TLS で暗号化するには tls
パラメーターを使います:
backend=127.0.0.1,8080;;proto=h2;tls
UNIX ドメインソケットにも対応しています. "unix:" に続けて接続先の UNIX ドメインソケットのパスを書きます:
backend=unix:/tmp/h2o.sock;;proto=h2
慣れてきましたか? では ";" が二個あった件についてお話しましょう.
nghttpx ではリクエストパスとホストでフォワード先を切り替えることができます. backend
オプションを複数回つかってフォワード先を指定します. 例えば以下の例をみてください:
backend=127.0.0.1,6000;ws.example.com/ws/
backend=127.0.0.1,8080;/;proto=h2
上記の例では, ホストが ws.example.com でリクエストパスが /ws/ で始まる場合, 127.0.0.1:6000 へ HTTP/1.1 でフォワードする, それ以外は 127.0.0.1:8080 へ HTTP/2 でフォワードする, という設定になります.
フォワード先を切り替えるためのリクエストパスとホストの組をルーティングパターンと称しています. 書式について説明しましょう. リクエストパスは必ず "/" で始まります. "/" で始まらない場合, それはホスト名であると認識されます. ホスト名だけ指定されていて, パスが指定されていない場合は, パスが "/" であるとみなされます. ポート番号とキーワードの間にルーティングパターンを書き, ";" で区切るという書式になっています. ルーティングパターンを省略すると "/" が指定されたことになり, すべてのリクエストがマッチするようになります. nghttpx ではこのようにすべてのリクエストがマッチするルーティングパターンを含めなければなりません.
ルーティングパターンのマッチングルールは Go の net/http パッケージの ServeMux によく似たものです5. パスのマッチについては, 長いものが優先されます. ホスト名がマッチするとパスのマッチよりも優先され, その中でパスのマッチで最も長いものが選ばれます. "/" で終わる場合, 前方一致となります. 一つ例外があってリクエストパスに "/" を末尾に追加した時にマッチする場合も含むということです (e.g., ルーティングパターン "/ws/" は, リクエストパス "/ws" にもマッチする). "/" で終わらないルーティングパターンは完全一致になります.
同じパターンを持つ backend
を複数回つかってロードバランス先のバックエンドサーバーを追加できます:
backend=127.0.0.1,8080;/;
backend=192.168.0.1,50051;/greeter/;proto=h2
backend=192.168.0.2,50051;/greeter/;proto=h2
backend=192.168.0.3,50051;/greeter/;proto=h2
上記の例では, ルーティングパターン "/greeter/" で 3 個のバックエンドサーバー 192.168.0.1, 192.168.0.2, 192.168.0.3 を追加しました6.
バックエンドの接続数は nghttpx が自動で調整します. バックエンドの HTTP/2 接続を複数のクライアントで共有する場合もあります.
フロントエンド, バックエンドで共に HTTP/2 をサポートしているので例えば gRPC のような HTTP/2 ベースの RPC のリクエストを L7 でロードバランスすることができます.
backend
オプションについて詳しく知りたい方はマニュアルを参照してください.
ではフロントエンドの話をしましょう. デフォルトでは全インターフェースでポート 3000 番を listen し TLS が必須なのでした. listen するアドレスを変更するには frontend
オプションを使います:
frontend=*,80;no-tls
上記の例では全インターフェースでポート 80 番を listen し, TLS 無しの平文という設定内容になります. "*" は全インターフェースを意味する記号です. 0.0.0.0
のように IP アドレスを書くことももちろんできます. no-tls
はキーワードで, TLS 無しを意味します.
frontend
オプションを複数書いて listen するアドレスを増やすことができます:
frontend=*,80;no-tls
frontend=*,443
上記の例では, 80 番ポートで平文通信, 443 番ポートで TLS 暗号化通信という設定になります.
UNIX ドメインソケットにも対応しています. "unix:" に続けて UNIX ドメインソケットのパスを書きます:
frontend=unix:/tmp/nghttpx.sock
frontend
オプションについて詳しく知りたい方はマニュアルを参照してください.
ほとんどの Web サーバーによるリバースプロキシーではフロントエンドとバックエンドが関連付いて設定できる (あるいはそう強制される) ようになっています. nghttpx はフロントエンドとバックエンドは分離されていてどのフロントエンドアドレスから入ってきても同じようにバックエンドにルーティングします. これについては賛否両論あるでしょう. 後述する mruby スクリプトを使ってルーティングを制御することができます.
HTTP/2 セキュアプロキシーとして使ってみよう
HTTP/2 セキュアプロキシーは, ブラウザーとフォワードプロキシーの間は原則的に 1 本の HTTP/2 TLS 接続になり, すべてのリクエストはこの接続を通してフォワードされます. フォワードプロキシーはクライアントの要求に従い代理としてオリジンサーバーへリクエストをフォワードします. https URI の場合は同じ接続でトンネリングされます.
バックエンドには Squid やフォワードプロキシーとして設定した Apache Traffic Server が必要です. nghttpx は HTTP/2 と TLS の終端だけ行い, キャッシングなど残りの処理はすべてバックエンドに委譲します.
nghttpx を HTTP/2 セキュアプロキシーとして使うためには http2-proxy
オプションを使います:
http2-proxy=yes
http2-proxy=yes
の場合, backend
オプションのルーティングパターンはすべて無視されます.
Chromium と Mozilla Firefox が HTTP/2 セキュアプロキシーに対応しています. 両者ともに TLS 接続が必須になっています.
対応ブラウザで HTTP/2 セキュアプロキシーを使うには, 以下のような proxy.pac スクリプトを作成します:
function FindProxyForURL(url, host) {
return "HTTPS <SERVERADDR>:<PORT>";
}
<SERVERADDR>
と <PORT>
は nghttpx が listen しているアドレスとポート番号になります. ブラウザは正しい SSL/TLS 証明書を要求します. self-signed な証明書を使う場合はインポートするなりします.
Mozilla Firefox では Preference を開いて, Advanced, Network タブをクリックします. Connection Settings ボタンをクリックして表示されるダイアログで "Automatic proxy configuration URL" を選択して proxy.pac へのパスを以下のように指定します:
file:///path/to/proxy.pac
Chromium では以下のようなコマンドラインを使います:
$ chromium --proxy-pac-url=file:///path/to/proxy.pac --use-npn
mruby スクリプトによる拡張
nghttpx は mruby スクリプトで機能を拡張することができます7. この文書の執筆時点では以下のようなことができます:
- リクエストヘッダー, レスポンスヘッダーの書き換え
- リクエストヘッダーを書き換えることによりバックエンドへのルーティングを制御
- バックエンドを使わずにレスポンスを返す
mruby スクリプトでは, 以下の 2 つのメソッドを持つオブジェクトを返すようにします:
-
on_req(ctx)
: クライアントからリクエストヘッダーを受信した時に実行される -
on_resp(ctx)
: バックエンドからレスポンスヘッダーを受信した時に実行される
ctx
はリクエストの情報が入ったオブジェクトです. nghttpx は返されたオブジェクトのメソッドを上記に書いたイベントが発生すると呼び出します.
例として, すべてのリクエストパスの前に "/apps" というパスコンポーネントを挿入する mruby スクリプトは以下のようになります:
class App
def on_req(env)
env.req.path = "/apps#{env.req.path}"
end
end
App.new
mod.rb を mruby-file
オプションで指定します.
mruby スクリプトの詳しい使い方についてはマニュアルを参照してください.
参考文献
nghttpx について詳しく知りたい方には以下の文書をおすすめします.
- nghttpx manual
- [nghttpx - HTTP/2 proxy - HOW-TO] (https://nghttp2.org/documentation/nghttpx-howto.html)
脚注
-
https://nghttp2.org/documentation/nghttpx.1.html#ocsp-stapling ↩
-
https://nghttp2.org/documentation/nghttpx.1.html#tls-session-resumption ↩
-
この文書執筆時点では同じルーティングパターンを共有する場合,
proto
とtls
キーワードの設定がバックエンドですべて同じである必要があります. ルーティングパターンが違う場合はこの限りではありません. ↩ -
mruby を利用するためには,
--with-mruby
オプションを configure スクリプトに与えて nghttp2 をビルドします. デフォルトでは有効になっていません. ↩