お金をかけずにサーバーの勉強をしよう

WEBサーバー冗長化(後編)

2022年11月27日

メニューへ戻る

WEBサーバーを冗長化して、NGINXのロードバランサー機能で振り分けます。

こんにちは。

WEBサーバー冗長化(前編)」で、2台の WEBサーバーで同じ内容のホームページを提供できるようになりましたので、NGINXでロードバランサーを作って外部からのアクセスを振り分けます。

構成図で言うとオレンジ色のところが前編で作ったところです。
前回作った範囲の図

後編では、NGINXでロードバランサーをつくるのですが、ロードバランサーとは負荷分散装置のことでネットワークハードウェアとして提供されているものが多いと思います。

NGINXにはその動きをソフトウェアで行う機能がありますが、OSS版と有償版の NGINX Plusではこの機能において差がありまして、OSS版の方はおまけくらいな感じですね。


それでは、ロードバランサーになる Ubuntu Server(nginx-load)を作りますが、作り方は前編で作った nginx-web1・nginx-web2と全く同じなので割愛します。

以降、nginx-loadに NGINXがインストールされた後からの作業になります。
IPアドレスはこうなっています。

Win10クライアント192.168.1.14
nginx-load192.168.1.126
nginx-web1192.168.1.127
nginx-web2192.168.1.128
FreeNAS192.168.1.109


NGINXのロードバランス機能に関するドキュメントはこちら。
Using nginx as HTTP load balancer

このドキュメントに記載例がありますので、殆どパクリで NGINXの設定ファイル /etc/nginx/sites-available/default を以下のようにします。

コメントアウト分は除いた生きている行だけですと、デフォルトではこうなっていました。

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /var/www/html;

        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                try_files $uri $uri/ =404;
        }
}

ピンク色が変更箇所です。

upstream webservers {
        server nginx-web1;
        server nginx-web2;
        }

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /var/www/html;

        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                proxy_pass http://webservers;
        }
}

設定ファイルを再読み込みします。

subro@nginx-load:~$ sudo systemctl reload nginx

これでセッティングはできました。

クライアントからWEBブラウザで、ロードバランサーにアクセスしてみます。

ロードバランサーにアクセスした画面

想定通りこの絵になりました。
ロードバランサーの前提機能のリバースプロキシとしては上手く動いているようです。


ここからの検証はどうするか。
いくらWEBブラウザでアクセスしても、同じ画面が出るだけのはずです。

考えた末、Wiresharkでパケットを拾ってみることにしました。
nginx-loadに出入りする http のパケットに限定して拾えば分かりそうです。
フィルタをこのようにしました。

ip.addr == 192.168.1.126 && http

パケットキャプチャーを開始してから以下の作業をしました。

  1. クライアントから nginx-load(192.168.1.126)に WEBアクセス
  2. 結果の画面を確認
  3. ブラウザキャッシュクリア
  4. クライアントから nginx-load(192.168.1.126)に WEBアクセス
  5. 結果の画面を確認

キャプチャ結果はこの通り。

No.     Time                          Source                Destination           Protocol Length Info
   2525 2022-11-28 11:59:52.949737504 192.168.1.14          192.168.1.126         HTTP     536    GET / HTTP/1.1
   2539 2022-11-28 11:59:52.953192724 192.168.1.126         192.168.1.127         HTTP     540    GET / HTTP/1.0
   2552 2022-11-28 11:59:52.957866798 192.168.1.127         192.168.1.126         HTTP     602    HTTP/1.1 200 OK  (text/html)
   2558 2022-11-28 11:59:52.959071019 192.168.1.126         192.168.1.14          HTTP     543    HTTP/1.1 200 OK  (text/html)
   4741 2022-11-28 12:00:27.700659227 192.168.1.14          192.168.1.126         HTTP     536    GET / HTTP/1.1
   4746 2022-11-28 12:00:27.702567288 192.168.1.126         192.168.1.128         HTTP     540    GET / HTTP/1.0
   4761 2022-11-28 12:00:27.709636677 192.168.1.128         192.168.1.126         HTTP     602    HTTP/1.1 200 OK  (text/html)
   4765 2022-11-28 12:00:27.710333560 192.168.1.126         192.168.1.14          HTTP     543    HTTP/1.1 200 OK  (text/html)

辿っていくと、

1回目: 14 → 126 → 127 → 126 → 14
2回目: 14 → 126 → 128 → 126 → 14

となっていて、1回目と2回目で、クライアントからは同じことをしているのに、実際に反応しているWEBサーバーが変わっていることが分かります。

これがロードバランサの機能です。

NGINXのロードバランシングの優先度は何によって決まっているかですが、上のドキュメントの「Load balancing methods」に書いてある以下の3つです。

round-robin順繰り
least-connected最も繋がってないところ
ip-hashクライアントの IPを元にしてハッシュ値で決める

ip-hash はどういう動きになるのかちょっと分かりませんが、上の2つは分かりやすいかと。

デフォルトは round-robin です。
(なので上の実験では round-robinになっています)

変更するには設定ファイル /etc/nginx/sites-available/default に一行加えるだけなので、変化を試してみるのもお勉強になると思います。


次にロードバランサにぶら下がっている WEBサーバーが障害を起こしてしまった場合の動きを見てみます。

NGINXは WEBサーバーの反応がない場合にそのサーバーが障害を起こしていると認識してアクセス先から除外します。

これについては上のドキュメントの「Health checks」に書いてあり、何回の失敗で通信不能とみなすか、どれだけの時間が経ったら復旧したと想定して通信を再開するかなどのパラメータについて触れています。

WEBサーバーのヘルスチェックにはクライアントからの実際のリクエストを使っているとあり、ここはちょっと注意が必要かと思います。

なお、OSS版ではこのチェック方法しかないようで、クライアントからのリクエストとは非同期で任意のチェックを走らせる機能は、有償版の NGINX Plusで提供されています。

実運用に耐える美味しい機能は有償なんですねぇ、残念。


障害を想定して nginx-web1 の NGINX を停止して、クライアントから WEBアクセスを行ってみるテストです。

パケットはこんな感じになりました。
(分かりやすいように内容を端折っています)

No.     Time                          Source                Destination           Protocol Length Info
     22 2022-11-28 13:33:32.174938819 192.168.1.14          192.168.1.126         HTTP     536    GET / HTTP/1.1
     31 2022-11-28 13:33:32.176908644 192.168.1.126         192.168.1.127         HTTP     540    GET / HTTP/1.0
     44 2022-11-28 13:33:32.197652546 192.168.1.127         192.168.1.126         HTTP     602    HTTP/1.1 200 OK  (text/html)
     48 2022-11-28 13:33:32.198167718 192.168.1.126         192.168.1.14          HTTP     543    HTTP/1.1 200 OK  (text/html)

〜〜〜 ここまで nginx-web1 へのアクセスが正常に終わったところ 〜〜〜

    143 2022-11-28 13:34:01.550230443 192.168.1.14          192.168.1.126         HTTP     536    GET / HTTP/1.1
    147 2022-11-28 13:34:01.553810664 192.168.1.126         192.168.1.128         HTTP     540    GET / HTTP/1.0
    160 2022-11-28 13:34:01.558978120 192.168.1.128         192.168.1.126         HTTP     602    HTTP/1.1 200 OK  (text/html)
    164 2022-11-28 13:34:01.560250794 192.168.1.126         192.168.1.14          HTTP     543    HTTP/1.1 200 OK  (text/html)

〜〜〜 ここまで ラウンドロビンで nginx-web2 へのアクセスが正常に終わったところ 〜〜〜

〜〜〜 ここで nginx-web1 の NGINXを停止 〜〜〜

    257 2022-11-28 13:34:24.470874187 192.168.1.14          192.168.1.126         HTTP     536    GET / HTTP/1.1
    258 2022-11-28 13:34:24.471749389 192.168.1.126         192.168.1.127         TCP      74     42534 → 80 [SYN]
    259 2022-11-28 13:34:24.472206202 192.168.1.127         192.168.1.126         TCP      60     80 → 42534 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
    263 2022-11-28 13:34:24.473922942 192.168.1.126         192.168.1.128         HTTP     540    GET / HTTP/1.0
    270 2022-11-28 13:34:24.477701618 192.168.1.128         192.168.1.126         HTTP     602    HTTP/1.1 200 OK  (text/html)
    273 2022-11-28 13:34:24.478431606 192.168.1.126         192.168.1.14          HTTP     543    HTTP/1.1 200 OK  (text/html)

3回目はラウンドロビンで、再び nginx-web1 にアクセスしにいきますが、NGINXが停止しているため、80/tcpへのセッション確立ができず tcpのリセットがかかりました。

その直後から nginx-web2にアクセスしに行っているのが分かります。

同じタイミングで、ロードバランサの(NGINX)のエラーログ(/var/log/nginx/error.log)にはこのようなメッセージが出力されていました。

2022/11/28 13:34:24 [error] 1293#1293: *82 connect() failed (111: Unknown error) while connecting to upstream, client: 192.168.1.14, server: _, request: "GET / HTTP/1.1", upstream: "http://192.168.1.127:80/", host: "192.168.1.126"

上のドキュメントにある通りの動きをしているようです。


この実験ではログを綺麗に取るため順序よく WEBアクセスしていますから、一見何の問題も無いように見えますが、実際には障害と見做すのは期間であり、リクエスト単位に保全している訳ではありません。

ですので、この状況下にはいくつかのリクエストを取りこぼすことは仕組み上避けられないと思います。

余りシビアな環境ではなければ殆ど問題にならないと思いますが、このまま使うのはこの仕組みを理解した上でないと少々危ない感じです。


これにて NGINXのロードバランサーでの振り分けと WEBサーバーの冗長化のお話は終わりです。

NGINXのOSS版では、ロードバランサとして本番環境に提供するのは注意が必要ではありますが、ロードバランサを使った冗長化というコンセプトは、これが理解の助けにはなるでしょう。


それにしても NGINX は凄いですね。