アップストリームの負荷分散と障害隔離
目標
nginxのアップストリームで複数のバックエンドに負荷を分散し、重み・セッション固定・障害の分離・Keep-Aliveを設定して、分散アルゴリズムを状況に合わせて選べるようになります。
なぜ重要なのか
SI現場でWASを2台以上置く理由は、性能よりも、1台が死んでもサービスが維持されるようにするためです。ところが、nginxオープンソース版にはアクティブヘルスチェックがないため、max_fails/fail_timeoutの意味を知らないと、「死んだサーバーにリクエストが行き続ける」状況を作ります。また、アップストリームのKeep-Aliveは、3つの設定がすべてあって初めて動作するのに、proxy_set_header Connection ""を書き忘れて、リクエストごとに新しいコネクションを作るシステムが、本当に多くあります。そうすると、TIME_WAITソケットが溜まり、ある瞬間にポートが枯渇します。
ステップ
- 準備: Tomcat(ポート8080)に
labhub.warをデプロイし、/opt/lab/samples/labhub-boot.jarをポート8082で起動しておきます。nginxは/root/ngをprefixとして使います。 /root/ng/nginx.confにupstream appブロックを作って、127.0.0.1:8080と127.0.0.1:8082を入れ、ポート8088の/がこのグループにプロキシされるようにします。log_formatに$upstream_addrを必ず含め、アクセスログを/root/ng/logs/access.logに出力します。http://127.0.0.1:8088/versionが200である必要があります。- リクエストを20回送ったあと、アクセスログを
/root/ng/rr.logにコピーします。異なるアップストリームのアドレスが、2個以上記録される必要があります。 127.0.0.1:8080にweight=3を指定してreloadしたあと、アクセスログを空にしてリクエストを40回送り、/root/ng/weight.logにコピーします。ポート8080に行ったリクエストが、28–32回の間である必要があります。- 分散方式を
ip_hashに変更してreloadしたあと、ログを空にしてリクエストを20回送り、/root/ng/iphash.logにコピーします。アップストリームのアドレスが1種類だけ記録される必要があります。 ip_hashを削除して(設定ファイルに残っていてはいけません)、両方のサーバーにmax_fails=2 fail_timeout=10sを設定します。そのあとポート8082のプロセスを停止して、リクエストを10回送ります。10回とも200である必要があります。- アップストリームのKeep-Aliveを設定します。
upstreamブロックにkeepalive 32;を、locationにproxy_http_version 1.1;とproxy_set_header Connection "";を設定します。3つすべてがある必要があります。 upstreamブロックにleast_conn;を追加して、文法チェックを通します。/root/ng/lb.mdを作成します。라운드로빈、가중치、ip_hash、least_connの4つの方式(韓国語の2つの語は、順に「ラウンドロビン」「重み」を意味します)について、それぞれいつ使うか、いつ使ってはいけないかを書きます。세션という単語(韓国語の語は「セッション」を意味します)が必ず登場する必要があり、全体で400文字以上である必要があります。
参考
- ログを空にする:
> /root/ng/logs/access.log(reloadなしでできます) - ステップごとのスナップショット:
cp /root/ng/logs/access.log /root/ng/rr.log - アップストリームごとの件数:
awk '{...}' access.log | sort | uniq -c - プロセスの停止:
pkill -f labhub-boot.jar - よくあるミス1:
ip_hashとweightを一緒に使いながら、重み付けの分散を期待するミスです。 - よくあるミス2: Keep-Alive 3点セットのうち、
proxy_set_header Connection "";を書き忘れるミスです。これを忘れると、HTTP/1.1に上げても、リクエストごとにコネクションが切れます。 - よくあるミス3:
least_conn;をlocationに書くミスです。upstreamブロックの中に入れる必要があります。
アップストリームグループの構成
/root/ng/nginx.confにupstream appブロックを作って、127.0.0.1:8080と127.0.0.1:8082を入れ、ポート8088の/がこのグループにプロキシされるようにします。log_formatに$upstream_addrを必ず含め、アクセスログを/root/ng/logs/access.logに出力します。http://127.0.0.1:8088/versionが200である必要があります。
upstreamブロックはhttpコンテキストに置きます。proxy_passで、その名前をURLのように使います。2つのバックエンドを両方とも起動しておくことを、忘れないでください。
ラウンドロビンの分散の確認
リクエストを20回送ったあと、アクセスログを/root/ng/rr.logにコピーします。異なるアップストリームのアドレスが、2個以上記録される必要があります。
どのバックエンドに行ったかは、アクセスログのアップストリームアドレスの変数でわかります。リクエストを十分に送って、ログから固有のアドレス数を数えてみてください。
重み付けの分散
127.0.0.1:8080にweight=3を指定してreloadしたあと、アクセスログを空にしてリクエストを40回送り、/root/ng/weight.logにコピーします。ポート8080に行ったリクエストが、28–32回の間である必要があります。
nginxの重み付きラウンドロビンは、ランダムではなく決定的です。重みの比率が、そのまま分散の比率になります。
セッション固定
分散方式をip_hashに変更してreloadしたあと、ログを空にしてリクエストを20回送り、/root/ng/iphash.logにコピーします。アップストリームのアドレスが1種類だけ記録される必要があります。
同じクライアントを同じバックエンドに送る方式です。セッションをWASのメモリに置くレガシーシステムで、よく使われます。サーバーが追加・削除されると、再分配が起きるという欠点も、覚えておいてください。
障害サーバーの自動除外
ip_hashを削除して(設定ファイルに残っていてはいけません)、両方のサーバーにmax_fails=2 fail_timeout=10sを設定します。そのあとポート8082のプロセスを停止して、リクエストを10回送ります。10回とも200である必要があります。
nginxオープンソース版には、アクティブヘルスチェックがなく、失敗回数に基づくパッシブな方式だけがあります。バックエンドを1台実際に停止してみて、サービスが維持されるかを確認してください。
アップストリームのKeep-Alive
アップストリームのKeep-Aliveを設定します。upstreamブロックにkeepalive 32;を、locationにproxy_http_version 1.1;とproxy_set_header Connection "";を設定します。3つすべてがある必要があります。
3つを一緒に設定して初めて動作します。1つだけ欠けても、リクエストごとに新しいコネクションができます。特に、Connectionヘッダーをどう処理すべきかが、落とし穴です。
最小接続方式
upstreamブロックにleast_conn;を追加して、文法チェックを通します。
リクエストの処理時間のばらつきが大きいサービスで、ラウンドロビンよりも優れている理由を、考えてみてください。
分散アルゴリズムの選択基準の整理
/root/ng/lb.mdを作成します。라운드로빈、가중치、ip_hash、least_connの4つの方式(韓国語の2つの語は、順に「ラウンドロビン」「重み」を意味します)について、それぞれいつ使うか、いつ使ってはいけないかを書きます。세션という単語(韓国語の語は「セッション」を意味します)が必ず登場する必要があり、全体で400文字以上である必要があります。
各方式の長所だけを書いても、ドキュメントとは言えません。いつ使ってはいけないかも一緒に書いておけば、あとで判断に使えます。