nginxとluaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
TRANSCRIPT
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
サイボウズ株式会社
深谷敏邦
#devsumi [19-G-6]
1
自己紹介
•深谷敏邦
• 2012年サイボウズ株式会社入社
•インフラチーム Hazama所属 (2012/09~)
•お仕事• デプロイツールの作成
• MySQL HA環境の構築
• Apacheのデバッグ・パッチの作成
• Nginxを使ったリバースプロキシの構築
2
1. イントロダクション
2. Apache から Nginx へ
3. 実装について
4. 結果とまとめ
3
cybozu.comとは
•サービスとして• サイボウズが運営するクラウドサービス
• 2011 年 11 月サービスイン
• 開始から 4 年で導入社数 9000 社以上
4
クラウド基盤としての cybozu.com
• 1000 台規模の物理マシン、数倍の VM• 物理機材から全て自社構築
•1億リクエスト/日
•契約ユーザーライセンス数29万人以上
5
サービス概要
•お客様ごとにサブドメインを発行
•契約内容に従って複数のサービスが同ドメインで利用可能
6
fukaya-coop.cybozu.com
サブドメイン毎の処理
•アプリケーション振り分け
• IP制限
• Basic認証
•クライアント証明書
7
LB
APAP
fukaya-coop.cybozu.com
/o//k/12.34.5.6
サブドメイン毎の処理の裏側
• 1 サブドメインにつき 1 設定ファイルを用意
•読み込みのたびに全ファイルのロードが必要
•サブドメインが増えるとロード処理にも時間がかかり….
8
サブドメイン数×設定変更にかかる時間
9
150
170
190
210
230
250
270
290
2/17/2014 3/17/2014 4/17/2014 5/17/2014 6/17/2014
Apache再起動時間
設定変更に時間かかり過ぎ
秒
日付~サブドメイン数
2.5分
5分
ミッション
•サブドメインの設定変更を1秒で完了させる
10
1. イントロダクション
2. Apache から Nginx へ
3. 実装について
4. 結果とまとめ
11
これまで
• LB として Apache 2.2を利用
• Apache を普通に使うと• サブドメイン毎に設定ファイルが必要
• 設定ファイルを安全にリロードするには再起動が必要
12
仕組みの転換
•1サブドメインの変更のためにすべての設定を読み込む
13
•アクセス毎に対応するサブドメインの設定を動的に読み込む
実際どうやるか?
•さすがに自力で全部作るのは厳しい
•候補に挙がったのは以下の 2 つ• Apache 2.4
• nginx
•今の機能を実現できるのは最低ライン
•パッチを当てすぎると本家の変更に追随できなくなる
•そうだLuaがあるじゃないか!
14
ということで比較してみる
機能 ◎(当然)
○(何とかいけそう)
Lua△
(experimental)◎
(実績十分)
性能○
(event mpm)◎
(完全イベント駆動)
15決定
nginxと lua-nginx-module
• nginxはイベント駆動型の HTTPサーバ• 2015年1月現在シェア 14%
• 大量のリクエストの処理が得意
• lua-nginx-module は HTTPリクエスト処理を luaで書ける拡張モジュール• cybozu.com で必要な設定項目がおおよそ実現できる
• 出来ないものは Cで書く☺
16
1. イントロダクション
2. Apache から Nginx へ
3. 実装について
4. 結果とまとめ
17
nginxの設定の基本
• location• リクエスト URIに応じて設定を変えるためのディレクティブ
18
location / {root /www/default;
}
location /hoge/ {root /www/hoge;}
• / にアクセスした場合は、/www/default からコンテンツを探す
• /hoge/ にアクセスした場合は、/www/hogeから探す
internal redirect
• location 間を移動する仕組み• 処理が異なる複数の location を組み合わせる事ができる
19
location /
location /login/
location /proxy/
nginx
location /maintenance/
メンテナンス中 初回アクセス
アプリケーションサーバへ
ログイン後
lua-nginx-module
• HTTP 処理の様々なタイミングで luaで記述した任意のコードを実行出来る• https://github.com/openresty/lua-nginx-module
•可能なこと• HTTPヘッダの読み書き
• location に対するリクエストの発行
• internal redirect の実行
20
データベースファイルの読み込み
•リクエスト毎にサブドメインに対応する設定をデータベースファイルから読み込む• key: value の簡単なテキスト形式
• luaを使って Hostヘッダを元にローカルファイルを読む
21
nginx
hagi
hagi.cybozu.com
fukaya-coop.cybozu.com
sato-shoji.cybozu.com
fukaya
-coop
sato-
shoji
データベースファイルの読み込み実装例
22
location / {rewrite_by_lua ‘path = (“/settings/” .. ngx.var.host)ret = ngx.location.capture(path)ret.body...
‘;}
location /settings/ {internal;root /var/settings;
}
luaによる設定読み込み用location
DBファイル配信用location
データベースファイルの読み込み実装例
23
location / {rewrite_by_lua ‘path = (“/settings/” .. ngx.var.host)ret = ngx.location.capture(path)ret.body...
‘;}
location /settings/ {internal;root /var/settings;
}
• Host ヘッダを元にDBファイルパスを作成
データベースファイルの読み込み実装例
24
location / {rewrite_by_lua ‘path = (“/settings/” .. ngx.var.host)ret = ngx.location.capture(path)ret.body...
‘;}
location /settings/ {internal;root /var/settings;
}
• Host ヘッダを元にDBファイルパスを作成
• DBファイルを取得するリクエストを投げる
データベースファイルの読み込み実装例
25
location / {rewrite_by_lua ‘path = (“/settings/” .. ngx.var.host)ret = ngx.location.capture(path)ret.body...
‘;}
location /settings/ {internal;root /var/settings;
}
• Host ヘッダを元にDBファイルパスを作成
• DBファイルを取得するリクエストを投げる
• レスポンスボディから設定値を取得
アクセス毎に変更すべき設定
1. 利用アプリケーション
2. IP アドレス制限
3. Basic認証
4. クライアント証明書による認証
26
1. 利用アプリケーション
•各サブドメイン毎に利用可能なアプリケーションが異なる• 申込時に選択
• 後に追加・削除が可能
• luaから以下の制御を行う• 利用不能なアプリケーションへのアクセスは拒否する
• 利用可能なアプリケーションへのアクセスは適切なアプリケーションサーバにリバースプロキシする
27
nginx
hagi.cybozu.com
fukaya-coop.cybozu.com
garoon
AP
kinton
eAP
g
k
利用アプリケーション動的化実装例
28
location / {rewrite_by_lua ‘if use_garoon thenngx.exec(“/proxy/garoon”)
endif use_kintone thengx.exec(“/proxy/kintone”)
endngx.exit(ngx.HTTP_NOT_FOUND)
‘;}
location /proxy/garoon {proxy_pass http://garoon-ap/;
}
location /proxy/kintone {proxy_pass http://kintone-ap/;
}
luaによるアプリケーション利用可否と振り分けロジック
リバースプロキシ用の
location
利用アプリケーション動的化実装例
29
location / {rewrite_by_lua ‘if use_garoon thenngx.exec(“/proxy/garoon”)
endif use_kintone thengx.exec(“/proxy/kintone”)
endngx.exit(ngx.HTTP_NOT_FOUND)
‘;}
location /proxy/garoon {proxy_pass http://garoon-ap/;
}
location /proxy/kintone {proxy_pass http://kintone-ap/;
}
• 予め DBファイルの内容を lua
の変数に読み込んでおく• アプリケーションが利用可能ならリバースプロキシ用のlocation に internal redirectする
利用アプリケーション動的化実装例
30
location / {rewrite_by_lua ‘if use_garoon thenngx.exec(“/proxy/garoon”)
endif use_kintone thengx.exec(“/proxy/kintone”)
endngx.exit(ngx.HTTP_NOT_FOUND)
‘;}
location /proxy/garoon {proxy_pass http://garoon-ap/;
}
location /proxy/kintone {proxy_pass http://kintone-ap/;
}
• 予め DBファイルの内容を lua
の変数に格納しておく• アプリケーションが利用可能ならリバースプロキシ用のlocation に internal redirectする
• 利用可能なアプリケーションがなければ 404 エラーをクライアントに返す
2. IP アドレス制限
•標準モジュールではアクセス毎動的に設定を変更することが出来ない
• luaで IPアドレス制限を実装した
31
• 192.168.0.1
• 192.168.0.2
• 172.16.0.1
fukaya-coop
192.168.0.1
192.168.0.3
IP アドレス制限の実装
32
function authenticate_remote_addr(allows)local bit = require("bit")
local remote_addr = ngx.var.binary_remote_addrlocal x0, x1, x2, x3 = string.byte(remote_addr, 1, 4)local ipip = x0 * 16777216ip = x1 * 65536 + ipip = x2 * 256 + ipip = x3 + ip
for i, allow in ipairs(allows) doif bit.band(ip, allow[2]) == bit.tobit(allow[1]) then
return trueend
endreturn false
end
IP アドレス制限の実装
33
function authenticate_remote_addr(allows)local bit = require("bit")
local remote_addr = ngx.var.binary_remote_addrlocal x0, x1, x2, x3 = string.byte(remote_addr, 1, 4)local ipip = x0 * 16777216ip = x1 * 65536 + ipip = x2 * 256 + ipip = x3 + ip
for i, allow in ipairs(allows) doif bit.band(ip, allow[2]) == bit.tobit(allow[1]) then
return trueend
endreturn false
end
nginxの内部変数からクライアント IPアドレスを取得する
IP アドレス制限の実装
34
function authenticate_remote_addr(allows)local bit = require("bit")
local remote_addr = ngx.var.binary_remote_addrlocal x0, x1, x2, x3 = string.byte(remote_addr, 1, 4)local ipip = x0 * 16777216ip = x1 * 65536 + ipip = x2 * 256 + ipip = x3 + ip
for i, allow in ipairs(allows) doif bit.band(ip, allow[2]) == bit.tobit(allow[1]) then
return trueend
endreturn false
end
データベースファイルから読み込んだ許可リストと比較してアクセス可否を
判定
3. Basic認証
•標準モジュールでリクエストごとにパスワードファイルを変更することが出来る
•ただし認証自体は全ドメインにかかってしまう
•一方認証自体を利用するか否かはサブドメイン毎に設定できる
•認証を利用するかどうかを luaで制御するようにした
35
nginxのリクエスト処理
• nginxではリクエストはいくつかのフェイズを通って処理される
• Basic 認証は Access フェイズで実施される
36
rewrite フェイズ
access フェイズ
content フェイズ
Basic 認証
Basic 認証のスキップ
• Basic 認証を利用しない場合 luaを使って Access フェイズより前に internal redirect する
•後のフェイズが実行されないので Basic認証が行われなくなる
37
rewrite フェイズ
access フェイズ
content フェイズ
luaでスキップ
Basic認証の実装例
38
location / {rewrite_by_lua ‘if not use_basic_auth thenngx.exec(“/contents/”)
end‘;
auth_basic “closed site”;auth_basic_user_file /var/passwd/$host;
content_by_lua ‘ngx.exec(“/contents/”)
‘;}
location /contents/ {internal;root /var/www;
}
basic 認証用 location
実コンテンツ用
location
Basic認証の実装例
39
location / {rewrite_by_lua ‘if not use_basic_auth thenngx.exec(“/contents/”)
end‘;
auth_basic “closed site”;auth_basic_user_file /var/passwd/$host;
content_by_lua ‘ngx.exec(“/contents/”)
‘;}
location /contents/ {internal;root /var/www;
}
rewrite フェイズ
access フェイズ
content フェイズ
Basic認証の実装例
40
location / {rewrite_by_lua ‘if not use_basic_auth thenngx.exec(“/contents/”)
end‘;
auth_basic “closed site”;auth_basic_user_file /var/passwd/$host;
content_by_lua ‘ngx.exec(“/contents/”)
‘;}
location /contents/ {internal;root /var/www;
}
• basic 認証を利用しない場合は /content/に直接飛ばす
Basic認証の実装例
41
location / {rewrite_by_lua ‘if not use_basic_auth thenngx.exec(“/contents/”)
end‘;
auth_basic “closed site”;auth_basic_user_file /var/passwd/$host;
content_by_lua ‘ngx.exec(“/contents/”)
‘;}
location /contents/ {internal;root /var/www;
}
• basic 認証を利用する場合は rewrite フェイズでは何もしない
• access フェイズではサブドメイン毎にパスワードファイルを切り替える
Basic認証の実装例
42
location / {rewrite_by_lua ‘if not use_basic_auth thenngx.exec(“/contents/”)
end‘;
auth_basic “closed site”;auth_basic_user_file /var/passwd/$host;
content_by_lua ‘ngx.exec(“/contents/”)
‘;}
location /contents/ {internal;root /var/www;
}
• basic 認証を利用する場合は rewrite フェイズでは何もしない
• access フェイズではサブドメイン毎にパスワードファイルを切り替える
• content フェイズで実コンテンツ用 location に移動する
4. クライアント証明書による認証
•クライアント証明書は SSL/TLS接続で利用できる認証方法
•サーバ証明書とは逆に HTTPサーバがクライアントに証明書を要求する
•クライアントはサーバが持つ CA が発行した証明書を送信する必要がある
• cybozu.com ではサブドメイン毎に CAを作成
43
LB
クライアント証明書を要求
証明書を送信
証明書を確認
クライアント証明書認証の実装上の課題
•標準モジュールではアクセス毎に CAを切り替えるという器用なことは出来ない
• SSL/TLSプロトコルでの認証なので luaでは実装できない• lua-nginx-module は HTTPレベルの処理しか書けない
• nginxに直接パッチを行った
44
クライアント証明書認証の実装の概要
•サーバはクライアント証明書の発行者を動的に呼び出して証明書を検証
•ただし他のサブドメインの証明書でも認証が通るためその証明書がアクセス先のサブドメインのものかどうかチェックが必要
45
LB
クライアント証明書を要求
証明書を送信
証明書を確認
CA
クライアント証明書に対応したCAを取得
SNIについて
• SNIは TLS拡張の一つで TLSハンドシェイク時にアクセスしたい FQDNをサーバに渡すこと• 名前ベースバーチャルホストでも FQDN毎に証明書を変えることが出来る
•クライアント証明書認証ではサーバは受け付けるクライアント証明書の CAの DNを送信することが出来る• 複数のクライアント証明書を持っている場合ユーザーの証明書選択が楽になる
46
LBこの CAが発行した証明書を送ってね
証明書を送信
fukaya-coop.s.cybozu.com にアクセスしたいよ
SNI対応
•パッチでは SNIで送信された FQDNに従って CAをルックアップするようにした• CA は FQDNをファイル名とした通常の PEMファイルとして保存
47
LBこの CAが発行した証明書を送ってね
fukaya-coop.s.cybozu.com にアクセスしたいよ
fukaya-coop.s.cybozu.com
その他おこなったこと
• LBを複数サーバにした場合の問題• 各 LB毎に SSLハンドシェイクを行うので、iOS だと証明書選択ダイアログ複数回でてしまう
• LB全体で状態を共有できない
1. 複数サーバー間での SSL セッションキャッシュ共有パッチ
2. DoS対策用同時リクエスト制限モジュールの開発• yrmcdsのセマフォを利用
•いずれも OSSとして公開予定
48
1. イントロダクション
2. Apache から Nginx へ
3. 実装について
4. 結果とまとめ
49
適用結果
•5分かかっていた設定の反映が1~2秒で終わるようになった
50
0
50
100
150
200
250
300
設定リロード時間
適用前 適用後
秒
100倍以上高速化
パフォーマンスについて
•リクエストごとに luaを実行するためパフォーマンスの劣化が懸念されたが問題なかった
51
count count
log10(response time[μs]) log10(response time[μs])
適用前のレスポンスタイムの分布 適用後のレスポンスタイムの分布
nginx化のメリット
•大量のコネクションを扱えるので keepalive秒数を伸ばした• 15秒から75秒に
•送信トラフィックが顕著に落ちた• 処理したリクエスト数は減っていない
•サーバ証明書の送信量が減ったことが理由• 中間証明書を合わせると3~4KB
52適用前トラフィック 適用後トラフィック
まとめ
•従来は LBへの設定の反映に5分かかっていた• nginx + luaを使いサブドメイン毎に設定を動的に読み込んだ
• アプリケーションの利用可否
• IP制限
• Basic認証
• クライアント証明書認証
•新しい LBの実装は設定の反映が1秒になった
53