WEB・APサーバについて改めて整理する

WEB・APサーバについて改めて整理する

January 5, 2021

(曖昧な理解のままやり過ごしてきた自分を反省しつつ、浅はかな知識の上塗りをするための記録です。)

以前は Ruby/Ruby on Rails をやっていた時期もあるのですが、最近は PHP/Laravel のお勉強をしていました。VPS に LAMP 環境を作る手順なんて記事も書いたのですが、Apache を入れて、MySQL を入れて、Laravel をデプロイすれば終わりでとても簡単ですね。

で、今更になって疑問に思ったのが「あれ?そういえば、AP サーバ的な役割をするソフトを入れていないけど、なんでこれで動いてるの?」でした。Rails をデプロイするときには、Unicorn や Puma といった AP サーバをいれていたのですが、PHP では特にそういったものは入れていませんでした。

Apache と PHP の蜜月関係(?)のおかげ

早めに回答を出しておきます。Apache はデフォルトで PHP を実行できるような設定が入っているからです。Apache と PHP、強固な結束です。

Apache で PHP を実行する方法 #

そもそも、Apache から PHP を実行する方式はいくつかあります。

CGI 方式

Apache によって別の PHP プロセスが起動されます。PHP を実行するのは Apache と別のユーザであり、Apache とは別のプロセスで実行されます。

モジュール方式

Apache にはモジュール(拡張機能)というものがあり、mod_php というモジュールを読み込ませると、Apache は PHP スクリプトを実行することができます。このとき、Apache ユーザ(= apache)が PHP を実行していて、PHP は Apache のプロセス上で実行されます。つまり、Apache の内部で PHP が実行される感じです。

なお、CGI とはプロトコルです。下記は Wikipedia からの引用です。(引用しておいて何ですが、「仕組み」とか「取り決め」なんて言葉にせず、プロトコルと書いてくれたほうがわかりやすいと思ってしまいます。)

Common Gateway Interface

Common Gateway Interface(コモン・ゲートウェイ・インタフェース、CGI)は、ウェブサーバ上でユーザプログラムを動作させるための仕組み。現存する多くのウェブサーバプログラムは CGI の機能を利用することができる。

ウェブサーバプログラムの機能の主体は、あらかじめ用意された情報を利用者(クライアント)の要求に応じて送り返すことである。そのためサーバプログラム単体では情報をその場で動的に生成してクライアントに送信するような仕組みを作ることはできなかった。 そこでサーバプログラムから他のプログラムを呼び出し、その処理結果をクライアントに送信する方法が考案された。それを実現するためのサーバプログラムと外部プログラムとの連携法の取り決めが CGI である。

Common Gateway Interface - Wikipedia

そんな CGI 方式とモジュール方式ですが、それぞれ問題点を抱えていたりします。

まずモジュール方式ですが、PHP が apache ユーザによって実行されるため、レンタルサーバのような他人と apache ユーザを共有しているような環境には向きません。PHP ファイルの実行権限制御ができないため、レンタルサーバを共有している別ユーザのファイルを読むこともできてしまうためです。

続いて CGI 方式ですが、これは apache と別のユーザで実行されるため、PHP 実行ユーザによる権限制御が可能であることから、レンタルサーバを共有している別ユーザとの干渉という危険がありません。ただし、性能面ではモジュール方式に劣ります。

CGI は、外部アプリケーションを Web サーバに接続するためのプロトコルである。CGI アプリケーションは個別のプロセスで実行され、各リクエストの開始時に作成され、終了時に破棄される。この「リクエスト毎に 1 つの新しいプロセス」モデルにより、CGI プログラムの実装が非常に簡単になるが、効率とスケーラビリティが制限される。高負荷では、プロセスの作成と破棄のためのオペレーティングシステムのオーバーヘッドが大きくなる。また、CGI プロセスモデルは、データベース接続の再利用、インメモリキャッシング等のリソース再利用方法を制限する。

FastCGI - Wikipedia

ということで、両者の弱点を解決するプロトコルが作り出されます。それが「FastCGI」です。(ちなみに、上記の引用も FastCGI のページに書いてある、「従来の CGI の問題点」という項目から引用していました。)

FastCGI 方式

FastCGI

リクエスト毎に新しいプロセスを作成する代わりに、FastCGI は永続的なプロセスを使用して一連のリクエストを処理する。これらのプロセスは、Web サーバではなく FastCGI サーバが所有している。

受信リクエストを処理するために、Web サーバは環境変数情報とページリクエストを、UNIX ドメインソケット、名前付きパイプ、または伝送制御プロトコル(TCP)接続のいずれかを介して FastCGI プロセスに送信する。応答は同じ接続を介してプロセスから Web サーバに返され、Web サーバはその応答をエンドユーザに配信する。応答の最後に接続が閉じられる可能性があるが、Web サーバと FastCGI サービスプロセスの両方が存続する。

FastCGI - Wikipedia

なぜ設定要らずで Apache が PHP を実行できるのか #

(ここでは VPS に自分で Apache や PHP をインストールすることを想定して書いています。レンタルサーバの場合、そもそも自分で Apache や PHP をインストールすることもないですし、Apache の設定は運営会社によって異なります。)

少し前の Apache、例えば CentOS 7(のパッケージマネージャである Yum)にてデフォルトでインストールされる Apache2.4.6 は、デフォルトで mod_php を読み込んでいます。そのため、単純に PHP ファイルをデプロイしておけば、Apache がモジュール方式で実行してくれます。

現在の Apache、例えば CentOS 8(のパッケージマネージャである DNF)の場合、デフォルトの Apache はバージョン 2.4.37 ですが、この場合は FastCGI 方式で PHP が呼び出されるようになっています。FastCGI 方式の呼び出しに応えるため、呼び出される PHP 側では、PHP-FPM というソフトを入れておく必要があります。PHP-FPM とは、PHP 標準のアプリケーションサーバですが、これについての説明はまた後ほど。で、その PHP-FPM ですが、PHP をインストールしたときに合わせて PHP-FPM のパッケージもインストールされます。ですので、やはり単純に PHP ファイルをデプロイしておけば、Apache が FastCGI 方式で実行できてしまいます。

つまりこういうわけで、Apache と PHP をインストールしたら、あとは PHP のファイルであれ Laravel のファイルであれ、デプロイすればそれで動いてしまうわけですね。

なぜ Apache は FastCGI 方式で PHP を呼び出すように変わったか

調べている中でわかりやすい説明を書いてくださっている方がいたので引用します。

Apache には、「受け付けたリクエストをどのように処理するか」を決める MPM (Multi Processing Module) という設定項目があり、マルチスレッドで動作する “worker” や “event“、もしくはマルチプロセスで動作する “prefork” のどれかを指定しなければいけません。 Apache から PHP を連携させるために mod_php モジュールを利用する場合は、このモジュールが スレッドセーフな設計ではないために、”prefork” を選ぶ必要があります。このため、CentOS 7 までの Apache は デフォルトで “prefork” が選択されていました。ただ、マルチプロセスである prefork は時代遅れ感があり、いつまでこの設定を使い続けるんだろうという疑問はありました。

そして CentOS 8 での Apache では、MPM のデフォルト値が “event” になりました。こうなると mod_php は利用できないため、代わりに php-fpm が利用されることになりました。php-fpm は FastCGI という仕様を実装しており、Web サーバーとは別のプロセスとして常時稼働し、HTTP リクエストを Web サーバーから転送して貰って処理しレスポンスを返す、という使われ方をします。

Web サーバー側は「FastCGI として動作するプロセス」にリクエストを転送できればよく、プログラミング言語に依存しません。プログラミング言語側は FastCGI として動作するライブラリを用意しておけば、FastCGI に対応した Web サーバーから利用して貰うことができます。Apache の場合は、FastCGI にリクエストを転送するために、mod_proxy_fcgi というモジュールが用意されています。

CentOS 8 標準の Apache と PHP の関係について – ラボラジアン

なお、引用記事中に出てきた、Apache の MPM (Multi Processing Module)設定については、Wikipedia の説明もわかりやすいです。

Apache HTTP Server - Wikipedia

Nginx で PHP を動かす場合は FastCGI の一択 #

Web サーバの2大巨塔は、Apache と Nginx です。Nginx での PHP の実行方法は、FastCGI 方式一択になります。

長い歴史を持つ重鎮 Apache と違い、若い Nginx はサッパリしています。まず Nginx は CGI(FastCGI ではなく、普通の CGI)を動作させる機能を持ちません。そして、mod_php にあたるようなモジュールもありません。つまり、Nginx で PHP を動かす場合は FastCGI 一択になります。

そもそも WEB サーバと AP サーバって何? #

上記までで、ひとまず Apache と Nginx の両 Web サーバにおいて、PHP を動かすにはどうしたら良いのかというところはわかりました。

で、ここから話が少しずつ方向転換してくるのですが、そもそも Web サーバと AP サーバって何でしたっけという疑問が湧いてきました。

WEB 2層構造から WEB 3層構造へ #

わかってます。WEB 3層構造ですよね。WEB・AP・DB ですよね。DB サーバは文字通りデータベースが入っているサーバですが、そもそも WEB サーバと AP サーバの違いってなんでしょうか。

いまや WEB 3層構造が常識となっていますが、かつては WEB 2層構造が普通でした。

2層とは「WEB(&AP)サーバ」と「DB サーバ」です。静的なページが中心だった時代はリクエストに応答する WEB サーバと、データを管理する DB サーバがあれば十分でした。多少動的な処理をするとしても、WEB サーバの中でちょっと頑張って貰えば OK でした。

それが3層構造になったのは、動的なページが中心になってきたからです。3層構造では「WEB サーバ」「AP サーバ」「DB サーバ」に分かれますが、このうち「WEB サーバ」は静的なページを担当し、「AP サーバ」は動的なページを担当します。

クライアントからのリクエストは、まず「WEB サーバ」が受け付けます。そのリクエストが静的ページであれば、WEB サーバは自分でリクエストに対応します。リクエストが動的(に生成しなければいけない)ページであれば、WEB サーバは処理を AP サーバに引き継ぎます。AP サーバは動的にページを生成し、結果を WEB サーバに返します。WEB サーバは AP サーバから渡された結果をクライアントに返します。

AP サーバ単独起動もできる #

Rails でいえば「rails s」すれば puma が動きますよね。Laravel なら「php artisan serve」で、Django なら「python manage.py runserver」でサーバが起動します。これでブラウザからアプリ画面が確認できるようになるわけですが、この時はもちろん Apache や Nginx を使っていません。これはつまり AP サーバだけを起動している状態です。

ここで、Web サーバは静的ページのリクエストを効率よく捌くためのものです。キャッシュなども利用して、後ろに控える AP サーバに極力負荷をかけないよう、自分が対応できるものは高速で捌いてくれます。対して、AP サーバは主に動的なページを生成して返すのが役割です。ただし、静的なページのリクエストにだってもちろん対応できます。しかしながら、AP サーバに静的なページを対応させるのは、処理スピード的にも、サーバの負荷的にも非効率な対応になってしまいます。静的なページを AP サーバに対応させるというのは、イメージで言えば、窓口の担当者が十分対応できそうな内容の業務を、わざわざ部長に対応させてしまう、といったところです。

本番運用をするのであれば Apache なり Nginx なりを入れるべきなのですが、開発中にちらっと画面を確認したい時は話が違います。単に、自分が画面を確認したいだけなのに、わざわざ Apahe や Nginx を入れたり、起動したりする方が手間です。ですから、開発中は逆に AP サーバだけを起動すればよいじゃない、という形式になっています。

なお、ここら辺の Web サーバと AP サーバの構成については以下の記事が参考になったので紹介しておきます。

ところで AP サーバって何? #

だんだん疑問が深まってきたのが、AP サーバについて。そもそも AP サーバってなんでしょうか。

AP サーバとは、あなたの作ったアプリケーションが動いているサーバのことです。Rails なり、Laravel なり、Django なり、node.js の Express なり、または素の Ruby、PHP、Python ファイルでも良いですが、それらが動いているのが AP サーバです。

要するに、AP サーバの本丸は開発したアプリケーション、つまりはプログラムなのですが、このプログラムたちは、クライアントからの HTTP リクエストを直接解釈することはできません。つまり、HTTP リクエスト受け取って解釈し、プログラムを呼び出してあげる存在が必要になります。これが AP サーバに入れるソフトウェアです。このソフトウェアを指して、単に AP サーバと呼ばれることも多いです。

Web サーバや AP サーバという言葉を使うとき、それはソフトウェアとしての Web サーバ・AP サーバと、ハードウェアとしての(Web 3層構造としての物理的な)Web サーバ・AP サーバのどちらかを指しているのかには注意する必要があります。

で、この AP サーバのソフトって、つまりは Web サーバから HTTP リクエストを受け取って、または開発中の AP サーバ単独起動なのであればクライアントから直接 HTTP リクエストを受け取って解釈して、(その後アプリに処理をさせて)レスポンスを返すのが仕事なわけですよね。

HTTP リクエストを解釈するソフトウェアのことを HTTP サーバ(これはソフトウェアとしてのサーバです)といいますが、誤解を恐れず言ってしまえば、Web サーバとは、HTTP サーバです。

Web サーバとは

「Web サーバ」はハードウェアまたはソフトウェア、あるいは両方が動作しているものを指します。

  1. ハードウェアの観点では、Web サーバとは、Web サーバソフトウェアと Web サイトのコンポーネントファイル (例えば、 HTML 文書、画像、 CSS スタイルシート、 JavaScript ファイル) を格納しているコンピューターのことです。インターネットに接続され、Web に接続された他の端末と物理的なデータ交換に対応しています。

  2. ソフトウェアの観点では、Web サーバとは、ホストにあるファイルに対する、Web ユーザーのアクセスを制御する、いくつかの部品の集まりです。最小限の部品は HTTP サーバです。 HTTP サーバは URL (Web アドレス) および HTTP (ブラウザーが Web ページを閲覧するためのプロトコル) を理解するソフトウェアのことです。格納している Web サイトのドメイン名 (mozilla.org など) を通してアクセスすることができ、コンテンツをエンドユーザーの端末に配信します。

Web サーバとは | MDN

AP サーバも HTTP リクエストを解釈するわけですが、これは結局 Web サーバとも言えませんか?

つまり、AP サーバというのは、動的な処理もできる Web サーバとも言えるわけで。これが理由なのかはわかりませんが、AP サーバを指して、Web サーバと呼んだりしている場合も多いと感じています。

例えば、「php artisan serve」すると“Web”サーバが立ち上がります、と言われることも多いです(というかこのケースがほとんど)。ただし、動的なページだって処理しているわけですから、これって AP サーバなのでは?と思ったり。ということで、AP サーバの定義というのはあまりしっかりしていないのかもしれません。

また、AP サーバのことを「ウェブアプリケーションサーバ」というように書いている記事も見かけますが、単に AP サーバというよりもこの表現が一番正しいのかもしれないなと感じています。

(と言いつつ、以降の文中でも単に AP サーバと書いていきますが、心の中では「ウェブアプリケーションサーバ!」と唱えています。)

AP サーバが WEB サーバの機能を持ったのは最近のこと #

ここで、AP サーバの例をあげると、Rails でいえば Puma や Unicorn になりますし、Django なら Gunicorn になりますし、Laravel なら PHP-FPM になります。

このなかで HTTP を解釈できない AP サーバのソフトが1つあります。それが PHP-FPM です。

少し前の記述で、Apache や、特に Nginx から PHP を動かす場合は FastCGI 方式です、と記載しました。FastCGI とはプロトコルです。

FastCGI そのものはネットワーク・プロトコルである。 FastCGI アプリケーションはデーモンであるため、必然的に FastCGI アプリケーションとやりとりするためにはネットワークが使われる。 そのため、ネットワーク・プロトコルなのだ。 CGI はプログラムの実行を前提としたインターフェイスなのでネットワーク・プロトコルではない。

FastCGI とは何か | Chienomi

で、FastCGI のプロトコルに即して渡された命令を解釈して PHP プログラムに渡すもの、要するに FastCGI プロトコルで通信するもの、それが PHP-FPM です。つまり、PHP-FPM は HTTP サーバではないことから Web サーバではありません。ちなみに PHP-FPM の FPM は「FastCGI Process Manager」の略です。その名が示す通りです。

対して、Puma や Unicorn、Gunicorn は HTTP サーバであり Web サーバです。これには以下のような歴史があります。

そもそも FastCGI が登場した当時である 1990 年中頃というのはアプリケーションサーバーという概念は一般的ではなかった。

1994 年に RFC で規格化された CGI だが、UNIX の基本的な仕組みを利用しており、シンプルで合理的である一方、パフォーマンス面に問題がある。 だから、プロセスを永続化される手法を求めることになった。

Perl/CGI に慣れ親しんだ世代であれば、mod_perl 名に聞き覚えがあるはずだ。 これは Apache に Perl を組み込み、スクリプトもロードした永続的な Perl プロセスを生成することで生成コストを削減するものだ。 言い換えれば、汎用ウェブサーバーの一部分をアプリケーションサーバーにしてしまうものだと言っていい。

FastCGI はそのような直接的な手法とは違い、汎用性のある手法である。 統一的なインターフェイスによって、より高いスループットが求められる状況で永続的なプロセスを活用できるようになった。

しかし、人類はその後、「別に特別なプロトコルを用意しなくても HTTP プロトコルがあれば良い」ということに気づいてしまった。そのため、HTTP プロトコルに追加ヘッダーを加える方式が採用されるようになると FastCGI は使われなくなっていった。

なぜ FastCGI か | Chienomi

そうなんです。CGI や Apache モジュール(ちなみに、mod_php や mod_perl だけでなく、mod_ruby や mod_python というものもありますよ)を経て生み出された FastCGI ですが、やがて人々は気づきます、FastCGI という独自プロトコルを使わなくたって、HTTP プロトコルでやりとりすればいいじゃないかと。

その結果誕生したのが、Puma や Unicorn、Gunicorn のような http サーバの機能を持った AP サーバです。(要するに、FastCGI 方式が未だに利用されている PHP は若干古いと言ってもいいと思います。)

HTTP リクエストとプログラム実行を繋ぐもの #

さて、HTTP リクエストを受け取って解釈しプログラムを呼び出してあげる存在、これが現代の AP サーバなわけであり、Rails の Puma や Unicorn、Django の Gunicorn がそれです。

これらの AP サーバは大きく2つのパーツに分けることができます。

1つめのパーツが、受け付けた HTTP リクエストに対して、後ろで控えている Rails や Django のプログラムをどのように呼び出せば効率よくリクエストが捌けるかという部分を担当する、いわゆる振り分け役パーツ。

2つめのパーツが、HTTP リクエストを解釈して実際に後方のプログラムを呼んであげる、いわゆる HTTP 解釈役パーツ。

2つめのパーツの具体的な名前ですが、Rails で言えばそれは Rack であり、Django で言えばそれは WSGI です。ちなみに Rack はそのままラックと読み、WSGI はウィスキーと読みます。

Rack とは?WSGI とは?については Wikipedia を読んだ方がわかりやすいので抜粋しつつ長文引用します。以下は、WSGI についてですが、Rack も考え方は全く同じです。そもそも Rack は WSGI を参考にして作られました。

Web Server Gateway Interface

Web Server Gateway Interface (WSGI; ウィスキー) は、プログラミング言語 Python において、Web サーバと Web アプリケーション(もしくは Web アプリケーションフレームワーク)を接続するための、標準化されたインタフェース定義である。また、WSGI から着想を得て、他の言語でも同様のインタフェースが作られた。

基本的な発想

過去において、Python に多種の Web アプリケーションフレームワークが存在することは、Python で Web アプリケーションを開発しようとする者にとって問題になっていた。というのも、Web アプリケーションフレームワークを選択することによって、使用できる Web サーバが制限されてしまったり、その逆の制限が発生したりしたためである。Python で書かれた Web アプリケーションは、FastCGI, mod_python, CGI, さらには Web サーバ独自の API を使ったものなど、様々な方法で実装されていた。

この問題を解決するために WSGI が考案された。WSGI は、Python における、Web アプリケーションと Web サーバを接続する標準仕様を定めるものである。これによって、WSGI に対応した Web アプリケーション(やフレームワーク)は、WSGI に対応した任意の Web サーバ上で運用できるようになる。つまり、アプリケーション側が WSGI に対応していれば、アプリケーションのコードに修正を加えることなく、WSGI 対応サーバを自由に選択することができ、高い可搬性(ポータビリティ)が得られる。

仕様の概要

WSGI には二つの側 — サーバ側とアプリケーション側が存在する。WSGI は、リクエスト情報・レスポンスヘッダ・レスポンス本文を、両者の間でどのようにやりとりするかを Python の API として定義している。

WSGI はミドルウェアの考え方も提供できる。WSGI ミドルウェアは、サーバ側とアプリケーション側の WSGI インタフェースを実装しているため、WSGI サーバと WSGI アプリケーションの “中間に” 挿入できる。ミドルウェアはサーバーの視点からはアプリケーションとして振る舞い、アプリケーションの視点からはサーバーとして振る舞う。

WSGI 対応サーバ

WSGI サーバ(WSGI アプリケーションコンテナ)は、WSGI アプリケーションを常駐させ、HTTP クライアントからリクエストを受け取るごとに、WSGI アプリケーションの callable オブジェクトを呼び出す。これによって、クライアントからのリクエストがアプリケーションに転送される。

WSGI アプリケーションコンテナの例としては、uWSGI, Gunicorn, Apache モジュール (mod_wsgi, mod_python など), Microsoft IIS(isapi-wsgi, PyISAPIe, ASP ゲートウェイを使用)などがある。

Web Server Gateway Interface - Wikipedia

…でした!わかりやすいですね。

最後に…「来るか?Nginx 製 AP サーバ『Nginx Unit』」 #

2018 年に、Nginx 製の AP サーバ「Nginx Unit」がリリースされました。この AP サーバは多言語に対応していて、現在は、Go、PHP、Python、Java、Node.js、Perl、Ruby の AP サーバとして使うことができます。

NGINX Unit | https://unit.nginx.org/

もちろん、Nginx Unit も Rack や WSGI の仕組みに従っています。

Supported App Languages

  • Python: via WSGI and ASGI with WebSocket support

  • Ruby: via the Rack API

Supported App Languages — NGINX Unit

Nginx Unit、今後普及するでしょうか。特に PHP の場合、FastCGI をやめて Nginx Unit を使うのもいいかなと感じています。

以上 #

以上です。話の順番等あまり考えず、思考垂れ流し状態で書いてしまいました。誰かの役にたてばこれ幸いですが、そもそもが自分用のメモなので、わかりづらくても許してくださいね…。