2021年5月23日日曜日

Apacheの前にNGINXを置くと負荷が下がる理由

以前、さくらのナレッジにいまさら聞けないNode.jsという記事を投稿しました。そこの「C10K問題の解決方法」という項目に

構成によっては、同じウェブサーバー内でApacheの前段にNGINXを配置するだけで負荷が下がることもあります。

と書いたところ「なんで?」という問い合わせがあったので、ちょっと解説します。

単純に考えるとNGINXのプロセスが走る分負荷が上がりそうな気がしますが、どういうからくりがあるのでしょうか。

前提

まず前提として、NGINXを設置する前のサーバーは一般的なLAMP構成とします。つまり、

  • mod_php等のモジュールを組み込んで動作している
  • prefork(マルチプロセス)方式で、クライアントからのリクエストに応じてプロセスを消費する

というものです。

何が問題か?

上記のような構成では、1つのプロセスにウェブサーバーと言語モジュールが組み込まれています。つまり1つ1つのプロセスが重いのです。さらに、

  • クライアントにレスポンスを送り終わるまでプロセスを終了できないため、クライアントの回線が遅い場合、Apache側はコンテンツを生成し終わってもプロセスを維持しなければいけない
  • SSL対応する場合はmod_sslが必要で、これも各プロセスに組み込まなければならず、さらにプロセスが重くなる
  • コンテンツ(特にHTMLなどのテキストデータ)を圧縮して送る場合、mod_deflateも各プロセスに組み込む必要がある

という問題があります。1つのプロセスでリクエストを捌くので仕組みは簡単ですが、必要な機能を全部詰め込むのでプロセスが使うリソースが肥大化してしまいます。

NGINXを置くとどうなる?

一言で言うと、Apacheの前段に置いたNGINXはリバースプロキシの役割を果たします。Wikipediaにはリバースプロキシの用途がたくさん書いてありますが、Apacheの前段にNGINXを置くことで

  • 速度の調整: Webサーバ上のプログラムがコンテンツを生成している場合、直接クライアントと通信を行うと、クライアント側がダウンロードするのを待たないとプログラムを終了できない。リバースプロキシはWebサーバが生成したコンテンツをまとめてキャッシュし、プログラムはその時点で終了可能となり、クライアントはクライアント側の速度でダウンロードが可能となる。
  • 暗号化/SSL高速化: SSL による暗号化でセキュアなWebサイトを作るとき、暗号化をWebサーバ自体が行うのではなく、SSL高速化のためのハードウェアを備えたリバースプロキシサーバで行う。この用途で用いる場合、SSLオフローダとも呼ばれる。  
  • 圧縮: リバースプロキシはコンテンツを圧縮して最適化し、ロード時間を短縮できる。

このあたりの効果が期待できます。SSLに関しては専用ハードウェアがなくとも、1つ1つのApacheプロセスにmod_sslを組み込んでさらに重くせずに、軽いNGINXプロセスでSSLを処理できるのがメリットです。

リバースプロキシは複数のマシンに処理を分散させるような場合に真価を発揮しますが、このように単に同じマシン上に置くだけでもずいぶんと役に立ちます。特に回線速度が遅いクライアントが多数あるような場合、Apacheプロセス数が全然違うのがtopコマンドなどで確認すると目に見えてわかります。

バックエンドの構成に大きく手を加えずに大幅な効果が見込めるので、コストパフォーマンスの高い改善方法です。Apacheプロセスの増大に悩まされている場合は、まずこの方法を検討してみてください。

導入手順・注意事項

導入手順は簡単です。

  1. ApacheがListenするポートを変更(80,443→8080のみ)
  2. 80,443番ポートはNGINXでListen
    • SSL証明書・秘密鍵をNGINXから参照
  3. NGINXのupstreamlocalhost:8080に指定

これだけです。ポートをListenするプロセスを変更する必要があるので、無停止の導入はムリですね。一瞬だけサービスを落とす必要があります。事前準備をきちんとしていれば、停止時間はほんの数秒で済みます。

注意点として、NGINXはSSLの中間CA証明書を指定する場所がないため、サーバー証明書と中間CA証明書を連結してssl_certificateに指定する必要があります。

もう1つの注意点は、NGINXを前段に置くことでプログラムから見えるアクセス元IPアドレスは常にNGINXのIPアドレスになってしまうということです。つまり、プログラム側でアクセス元のIPアドレスを取得している場合、例えばPHPで$_SERVER["REMOTE_ADDR"]のようなコードでアクセス元IPアドレスを取得している場合、常に127.0.0.1が取得されてしまいます。

対策として、NGINX側で「本当のIPアドレス」をカスタムヘッダーにセットしてやり、プログラム側でそのカスタムヘッダーを参照しましょう。この用途にはX-Forwarded-ForX-Real-IPなどがよく使われます。これらのカスタムヘッダーはクライアント側で偽装できるので取り扱いには注意しましょう。

おまけ

「ウェブサーバー」は本来ApacheとかNGINXのようなソフトウェアを指す言葉なので、「同じウェブサーバー内でApacheの前段に〜」というのは正確ではありませんね。この場合は「同じホストマシン内で〜」と書いたほうがいいですね。

0 件のコメント:

コメントを投稿