【エンジニア向け】忘れると危険!Amazon EC2インスタンスでWebサーバーを構築するときのチューニングの必要性(Apache, PHP-fpm)

【エンジニア向け】忘れると危険!Amazon EC2インスタンスでWebサーバーを構築するときのチューニングの必要性(Apache, PHP-fpm)

最近、某案件で久しぶりにWebサーバーを構築したのだが、その際にWebサイトが見れなくなるトラブルがあった。

今回は、その教訓をもとに、AWSでEC2インスタンス(仮想サーバー)でWebサーバーを構築する際の注意点をお伝えする。

デフォルトの状態だと、メモリリークを起こす可能性大

Webサイトに必要なWebサーバーを調達する場合、1年間の無料枠があるt2.micro(Memory: 1GB, CPU, vCPU×2Core)をベースに構築することが多いのだが、標準構成にApache(Webサーバー機能)でWebサーバーを構成した場合、ある程度のアクセス数が来るとメモリリーク(メモリ不足)を起こしてしまう。

特に、httpdプロセス(Apacheの処理プロセス)が、思うようにメモリを開放してくれず、最悪の場合リモート接続(SSH)すらできなくなってしまう。

今回のトラブルでは、実際にWebサーバーのメモリが枯渇して、Webサイトのアクセスとリモート接続(SSH)ができなくなってしまった。

そこで、WebサーバーのApacheとPHP-fpmをチューニングすることにした。

ApacheとPHP-fpmのチューニング

<前提条件>

  • Amazon EC2のインスタンスがt2.micro
  • OSがLinux(AmazonAMIやCentOS7)
  • Apacheのバージョンが2.4.x
  • PHPのバージョンが5.6。PHP-fpmを利用。

今回は、以下の手順でチューニングを行った。

  1. Apacheの制御(MPM。マルチプロセスモデル)を、preforkからeventに切り替え
  2. Apacheの最大接続数、プロセス数とスレッド処理の調整
  3. PHP-fpmの調整

Apacheの制御(MPM: マルチプロセスモデル)を、preforkからeventに切り替え

AmazonEC2でAmazonAMIやCentOS7のインスタンスを作成すると、Apache2.4のMPMは標準でpreforkになっている。

ところが、いざApacheを起動してみると、preforkではhttp/2形式を処理できないとエラーが出力されてしまう。

The mpm module (prefork.c) is not supported by mod_http2. The mpm determines how things are processed in your server. HTTP/2 has more demands in this regard and the currently selected mpm will just not do. This is an advisory warning. Your server will continue to work, but the HTTP/2 protocol will be inactive.

参考:【図解】HTTP/2って?HTTP/1.1との違いと導入メリット・課題まとめ

http/2形式が利用できないと、サーバー=クライアント間のパフォーマンスが下がるので、MPMをhttp/2が利用できるevent形式に変更する。

/etc/httpd/conf.modules.d/00-mpm.conf

# Select the MPM module which should be used by uncommenting exactly
# one of the following LoadModule lines:

# prefork MPM: Implements a non-threaded, pre-forking web server
# See: http://httpd.apache.org/docs/2.4/mod/prefork.html
#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so ☆コメントアウト

# worker MPM: Multi-Processing Module implementing a hybrid
# multi-threaded multi-process web server
# See: http://httpd.apache.org/docs/2.4/mod/worker.html
#
#LoadModule mpm_worker_module modules/mod_mpm_worker.so

# event MPM: A variant of the worker MPM with the goal of consuming
# threads only for connections with active processing
# See: http://httpd.apache.org/docs/2.4/mod/event.html
#
LoadModule mpm_event_module modules/mod_mpm_event.so ★コメントアウトを外す

設定変更が終了したら、Apacheを再起動する。

Apacheの最大接続数、プロセス数とスレッド処理の調整

次に、Apacheの最大接続数、プロセス数とスレッド処理の設定を変更する。

/etc/httpd/conf.d/performance.conf(/etc/httpd/conf/httpd.confに追記でも可)の設定例

HostnameLookups off

<IfModule mpm_event_module>
# 最初に起動する子プロセスの数
StartServers 4
# アイドル状態のスレッド最小数
MinSpareThreads 25
# アイドル状態のスレッド最大数
MaxSpareThreads 25
# 子プロセスそれぞれに生成されるスレッド数
ThreadsPerChild 25
# 生成する子プロセスの最大数
MaxClients 100
ServerLimit 100

MaxRequestWorkers 100
# それぞれの子プロセスが扱うリクエスト数の制限数
MaxRequestsPerChild 1000
</IfModule>

この設定は、Amazonもプラクティスを紹介しているので参考にしてみるとよいだろう。

参考:Amazon EC2 Linux インスタンスで実行されている Apache ウェブサーバーへのメモリ割り当てを調整する方法を教えてください

PHP-fpmの調整(PHP-fpmを使う場合)

WordPress等PHPアプリケーションを動かす際は、PHP-fpmが有効だ。

◆PHP-fpmとは?
FPM ( FastCGI Process Manager ) は PHP の FastCGI 実装のひとつで、 主に高負荷のサイトで有用な追加機能を用意している。

出典:PHP: FastCGI Process Manager (FPM) – Manual

インストールされていない場合は、以下のサイトを参考にインストールすることを推奨する。

参考:WordPressの高速化に。ApacheのeventMPMとphp-fpmを利用してウェブサイトの速度を向上する

ただし、PHP-fpmも、デフォルトだとプロセス1つあたりのメモリが肥大化してしまうので、以下の様なチューニングが必要となった。

/etc/php-fpm.d/www.conf

; Start a new pool named ‘www’.
; the variable $pool can be used in any directive and will be replaced by the
; pool name (‘www’ here)
[www]

(中略)

; Choose how the process manager will control the number of child processes.
; Possible Values:
; static – a fixed number (pm.max_children) of child processes;
; dynamic – the number of child processes are set dynamically based on the
; following directives. With this process management, there will be
; always at least 1 children.
; pm.max_children – the maximum number of children that can
; be alive at the same time.
; pm.start_servers – the number of children created on startup.
; pm.min_spare_servers – the minimum number of children in ‘idle’
; state (waiting to process). If the number
; of ‘idle’ processes is less than this
; number then some children will be created.
; pm.max_spare_servers – the maximum number of children in ‘idle’
; state (waiting to process). If the number
; of ‘idle’ processes is greater than this
; number then some children will be killed.
; ondemand – no children are created at startup. Children will be forked when
; new requests will connect. The following parameter are used:
; pm.max_children – the maximum number of children that
; can be alive at the same time.
; pm.process_idle_timeout – The number of seconds after which
; an idle process will be killed.
; Note: This value is mandatory.
pm = dynamic

; The number of child processes to be created when pm is set to ‘static’ and the
; maximum number of child processes when pm is set to ‘dynamic’ or ‘ondemand’.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI. The below defaults are based on a server without much resources. Don’t
; forget to tweak pm.* to fit your needs.
; Note: Used when pm is set to ‘static’, ‘dynamic’ or ‘ondemand’
; Note: This value is mandatory.
;pm.max_children = 50
pm.max_children = 5 ★最大プロセス数。50は多すぎるので、1プロセス50MB(適当)×nと仮定して5に変更

; The number of child processes created on startup.
; Note: Used only when pm is set to ‘dynamic’
; Default Value: min_spare_servers + (max_spare_servers – min_spare_servers) / 2
;pm.start_servers = 5
pm.start_servers = 1 ★起動時は1プロセスのみ

; The desired minimum number of idle server processes.
; Note: Used only when pm is set to ‘dynamic’
; Note: Mandatory when pm is set to ‘dynamic’
;pm.min_spare_servers = 5
pm.min_spare_servers = 1 ★待機プロセス最小数は1プロセスのみ

; The desired maximum number of idle server processes.
; Note: Used only when pm is set to ‘dynamic’
; Note: Mandatory when pm is set to ‘dynamic’
;pm.max_spare_servers = 35
pm.max_spare_servers = 5 ★待機プロセス最大数は5プロセスのみ

; The number of seconds after which an idle process will be killed.
; Note: Used only when pm is set to ‘ondemand’
; Default Value: 10s
;pm.process_idle_timeout = 10s;

; The number of requests each child process should execute before respawning.
; This can be useful to work around memory leaks in 3rd party libraries. For
; endless request processing specify ‘0’. Equivalent to PHP_FCGI_MAX_REQUESTS.
; Default Value: 0
;pm.max_requests = 500
pm.max_requests = 500 ★1プロセスあたりの最大数のコメントアウトを外す

(省略)

設定が終了したら、ApacheとPHP-fpmをそれぞれ再起動する。

この設定を行って1週間ほど経過観察したところ、メモリリークが発生しなくなった。
※ここで取り上げた値はあくまで参考値であり、動作保証できるものではないので注意。

参考:MPMとは?

MPMとは、マルチプロセスモデルといってApacheの処理プロセスの制御を定義したものだ。preforkとworker、eventの3種類ある。

◆prefork
スレッドを使わず、先行して fork を行なう制御形式
Apacheは伝統的に親プロセスを1つ持ち、クライアントからリクエストが来ると自分自身をコピーして子プロセスを起動する(forkという)
予め(pre)いくつか子プロセスを立ち上げておくことで、クライアントからのリクエストに対応できるようになっている。

クライアントが多くなればなるほど子プロセスの数も増えるため、使用メモリ量やCPU負荷が比例的に増大していく。
preforkで多数のクライアントを制御するには、それに応じた大量のメモリと高速なCPUが必要。

◆worker
workerは、マルチスレッドとマルチプロセスを組み合わせた制御形式である。Apacheの子プロセス1つ1つがマルチスレッドで動作し、スレッド1つが1つのクライアントを受け持つ方式である。従来のpreforkと異なり、1つのプロセスで複数のクライアントを制御できる。また多くの子プロセスを起動せずに済むため、メモリの使用量も減らすことが出来る。

◆event
eventは、workerの発展版だ。「プロセス数不足」というイベントをトリガーとして新規プロセスを起動し、以降に来たリクエストは新規プロセスのみで受け付ける。
既存プロセスはリクエストの受け付けを止め、速やかにプロセス停止する。リクエスト処理中のプロセスは処理が終わったらプロセスを停止する。

従来のworkerやPrefork形式では、KeepAlive(クライアントとの接続維持)をOnにすることで、無駄に接続を維持してしまいパフォーマンスの低下を招いていた。
それを解決するために生まれたのがevent形式で、KeepAliveの制御を別スレッドで行うことで、従来のworkerやpreforkと比べてサーバーリソースの効率化を実現している。

参考:【図解/apache】MPM prefork/worker /event (イベント駆動)の違い~CPUコアの使い方~

参考:Apacheパフォーマンス・チューニングの実践

そもそもt2microは、標準でスワップ領域がないので注意!

さらに、t2microは、初期構築時にスワップ領域(≒仮想メモリ)を持っていないため、物理メモリが枯渇するとプロセスを新規で立ち上げることができない。さらに、SSH接続のためのメモリも使ってしまうので、AWSコンソールから再起動する以外に選択肢がなくなってしまう。

物理メモリが少ないECインスタンスを構築する際は、予めスワップ領域を作っておくことを強く推奨する。

参考:スワップファイルを使用して、Amazon EC2 インスタンスのスワップ領域として動作するようにメモリを割り当てる