【WordPress】OOM を Apache 最適化で解決!【EC2 / t2.micro】

Blog-Thumbnail_WordPress-01 WordPress

はじめに

AWS の t2.micro の魅力と WordPress 運用における挑戦

こんにちは、KUDs です。

本記事では、AWS EC2 の t2.micro インスタンスを用いた WordPress 運用について記載します。

t2.micro インスタンスは、AWS の12か月の無料利用枠に含まれています。
そのため、低コストでクラウド上に環境構築ができ、多くの人が利用しているかと思います。

コンピューティング
無料利用枠
12 か月間無料
Amazon EC2
750 時間/月
クラウド内でサイズ変更可能なコンピューティング性能。

  • リージョンに応じて、750 時間/月の Linux、RHEL、SLES t2.micro または t3.micro インスタンス
  • リージョンに応じて、750 時間/月の Windows t2.micro または t3.micro インスタンス
https://aws.amazon.com/jp/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=all&awsf.Free%20Tier%20Categories=all

もちろん、EC2 上で WordPress などのウェブアプリケーションを運用できます。
そのため、新規にウェブサイトを始める方にとって、非常に魅力的な選択肢となっています。

しかし、無料枠で利用可能な t2.micro は、1GB のメモリしか提供していません
そのため、WordPress を運用する際にはリソースの管理が非常に重要となります。
特に、Web サーバである Apache の設定は、サイトのパフォーマンスと安定性に大きな影響を与えます。
よって、Apache の適切な設定と最適化が求められます。

以前の記事の振り返りと今回の目的

【EC2 – ELB】ヘルスチェック失敗の原因と対策: OOM と Swap

上記の記事では、t2.micro インスタンスで WordPress を運用する上で遭遇した、メモリ不足による OOM キラーの発生を確認しました。
※OOM: OutOfMemory

また、top コマンドで、メモリの大部分が httpd プロセスによって食いつぶされていることを確認しました。

top - 05:03:06 up  2:18,  1 user,  load average: 0.00, 0.00, 0.00
Tasks: 109 total,   1 running,  65 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.3 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.3 st
KiB Mem :   975596 total,   113940 free,   772996 used,    88660 buff/cache
KiB Swap:        0 total,        0 free,        0 used.    86752 avail Mem
 
  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                                                                                           
 3332 apache    20   0  708148  78772  10428 S  0.0  8.1   0:33.51 httpd
 3084 apache    20   0  707876  78676  10428 S  0.0  8.1   0:33.41 httpd
 3340 apache    20   0  707876  78660  10428 S  0.0  8.1   0:32.90 httpd
 3621 apache    20   0  937276  78640  10584 S  0.0  8.1   0:20.39 httpd
 3614 apache    20   0  707840  78624  10584 S  0.0  8.1   0:19.77 httpd
 3634 apache    20   0  707688  78464  10436 S  0.0  8.0   0:20.78 httpd
 3635 apache    20   0  707828  78460  10436 S  0.0  8.0   0:20.93 httpd
 3323 apache    20   0  707972  78416  10428 S  0.0  8.0   0:33.07 httpd
 3339 apache    20   0  707828  78308  10428 S  0.0  8.0   0:33.66 httpd
 3314 apache    20   0  707852  78296  10428 S  0.0  8.0   0:33.13 httpd
 3167 root      24   4  641736  54932   5532 S  0.3  5.6   0:39.20 aws
 2971 root      20   0  520032  18424  11796 S  0.0  1.9   0:00.43 httpd
 3243 root      20   0  723848  10300   2940 S  0.0  1.1   0:00.28 ssm-agent-worke
 3166 root      20   0  255592   9004   3224 S  0.0  0.9   0:00.39 rsyslogd

また、それに対する一時的な対応策としてのスワップ領域の作成について紹介しました。
しかし、その対策はあくまで一時的なものです。
そもそも、スワッピングを頻繁に行うことはパフォーマンスの低下につながります
スワップ領域を確保しても、慢性的にメモリ使用率が高い場合、その他のアプローチが必要です。

例えば、インスタンスタイプをアップグレードし、メモリを増強することも常套手段です。
しかし、もちろん費用は上がりますし、無料利用枠も対象外となります。

本記事では、この問題に対してメモリ使用率を下げるための根本的な解決を図ります。
具体的には、Apache MPM と PHP-FPM の設定を最適化し、メモリ使用率を改善します

これによって、OOM の発生を防ぎ、t2.micro インスタンスで快適に WordPress を運用できる環境を作ることが目標となります。

本記事のゴール

まず、先に結果を見ておきましょう。

CloudWatch Metrics「CWAgent swap_used_percent - CWAgent mem_used_percent - AWS/EC2 CPUUtilization」
CloudWatch Metrics「CWAgent swap_used_percent – CWAgent mem_used_percent – AWS/EC2 CPUUtilization」

上記は、CloudWatch Agent で収集した EC2 のメトリクスです。
オレンジ色の線が メモリ使用率(mem_used_percent)です。
まず、Apache 最適化前(前半)はメモリ使用率が 80% を超えています。
一方、Apache 最適化後(後半)はメモリ使用率が 40% 程度に低下しています

今回の記事では、上記のように メモリ使用率を低下させることを目的としています。
もちろん、設定次第でメモリ使用率を何 % まで下げるかはコントロールできます。
但し、必要以上に下げるとパフォーマンスが低下したり、その他アプリケーションでエラーが発生する可能性がありますので、十分に注意しましょう。

余談ですが、もし EC2 のメモリ使用率を CloudWatch にメトリクス表示したいという方は、以下の記事にて詳しく解説しておりますので、ご参照ください。

【EC2】メモリ使用率・スワップ使用率を CloudWatch で表示する

Apache の役割と設定の重要性

Apache とその重要性の理解

まず、Apache とは何か、改めて概説です。

  • Apache HTTP Server
  • オープンソースの Web サーバーソフトウェアです
  • クライアントからの HTTP リクエスト※を受け取り、 HTML などのウェブページを返す役割を果たします
    ※例えば、ウェブブラウザからのページ要求等
  • CGI スクリプトや PHP などのサーバーサイドのスクリプトを実行できます
  • 豊富なモジュールを通じて、追加の機能を提供します
    • セキュリティ強化
    • URL の書き換え
    • 負荷分散等

Apache 設定の役割と最適化の重要性

Apache は設定ファイル※で制御されます。
※通常は httpd.conf という名前のファイル

ここでは、サーバーの動作に影響を与えるさまざまなパラメータ記述されています。
例えば、以下のようなパラメータがあります。

サーバーがリクエストを受け取るポート番号

Listen 80

ドキュメントルート(サーバーがファイルを探しに行くディレクトリ)

DocumentRoot "/var/www/html"

エラーログの場所

ErrorLog "logs/error_log"

その他、様々なモジュールの設定も含まれます。

特に、今回の記事では、Apache の MPM という設定が重要です。
※MPM: Multi-Processing Module

これは Apache がどのように多数のリクエストを処理するかを決定します。
MPM の設定が不適切であれば、Apache は大量のリクエストを適切に処理できず、サイトのパフォーマンスが低下したり、場合によってはサイトが完全にダウンすることもあります。

また、AWS の t2.micro のようなリソース制約のある環境で Apache を動作させる場合、Apache の設定を最適化することが非常に重要となります。
メモリの制限内で最大限のパフォーマンスを引き出すために、Apache の設定を調整し、リソース使用を効率化する必要があります。

MPM の理解とその重要性

MPM(Multi-Processing Module)の紹介とその役割

前述の通り、今回の記事で、Apache の最適化において非常に重要な要素が MPM です。
これは Apache がどのように並行処理を行うかを制御します。
すなわち、一度に複数の HTTP リクエストをどのように処理するかを決定します。

Apache には様々なタイプの MPM が用意されていますが、一般的には次の 3 つのタイプが主に利用されます。

prefork

  • 各リクエストを個別のプロセスで処理します
  • これは非スレッド環境や、スレッドセーフでないモジュール※に適しています
    ※例えば、いくつかの PHP モジュール
  • しかし、大量のリクエストを処理するにはメモリ消費が大きいです

詳細は、以下の公式ドキュメントをご参照ください。

prefork - Apache HTTP Server Version 2.4

worker

  • 各リクエストを個別のスレッドで処理し、これらのスレッドを少数のプロセスで管理します。
  • これによって、プロセスごとのオーバーヘッドを減らしつつ、並行処理を行うことが可能となります。

詳細は、以下の公式ドキュメントをご参照ください。

worker - Apache HTTP Server Version 2.4

event

  • workerと同様にスレッドベースのMPMですが、保持している接続をより効率的に管理できます。
  • これは特に同時に多数の接続を開いている長期間のクライアント※に対して効果的です。
    ※例えば、 Comet や WebSocket

詳細は、以下の公式ドキュメントをご参照ください。

event - Apache HTTP Server Version 2.4

プロセスとスレッドのおさらい

プロセスとは、システム上で実行される独立したプログラムのことを指します。
それぞれのプロセスは、自身のメモリ空間やシステムリソースを持っています。

一方、スレッドとは、プロセス内で実行される個々のタスクのことを指します。
スレッドは同じプロセス内でメモリなどのリソースを共有しながら実行されます。

このため、プロセスに比べてスレッドの生成や切り替えのコスト※は小さいです。
※コンテキストスイッチ

しかし、スレッドが同じプロセス内でリソースを共有するため、スレッド間でデータを共有したり同期を取ったりする際の設計やデバッグは複雑になる可能性があります。

Apache の MPM の設定を調整するときは、プロセスとスレッドのこれらの特性を理解して、システムのリソース(特にメモリ)と性能の最適なバランスを見つけることが重要です。

Worker MPM への変更の決定

前述の通り、各 MPM はそれぞれ異なる特性と特徴を持っています。
本記事の前提は、t2.micro インスタンス上での WordPress 運用です。
この場合、Worker MPM が最適な選択であると判断しました。
以下では、その理由について解説します。

  • マルチスレッドによるリクエストの効率的な処理
    • レイテンシーの低下
    • リソース消費の削減
  • ELB 配置による 5xx エラー発生の回避

補足: ELB 配置による 5xx エラー発生

特に、ELB 配置によるエラーについては注意が必要です。
これは、前段に ELB ( ALB / CLB ) が配置されている EC2 インスタンスにて Event MPM を使用している場合に発生します。
詳細については、以下の repost 記事が参考になるかと思います。

Apache: Apache MPM イベントモジュールは、ロードバランサーからの接続を早期に切断する可能性があります。接続が早期に切断されると、Application Load Balancer では HTTP 502 エラーが、Classic Load Balancer では HTTP 504 エラーが生成されます。この動作を抑制するためには、代わりに MPM ワーカーモジュールを使用するのがベストプラクティスです。

https://repost.aws/ja/knowledge-center/apache-backend-elb

( Event の特徴である Keep Alive Problem の回避 (コネクションの切断) が問題になっているんですかね )

上記の理由から、今回は Worker MPM を選択します。
ただし、Worker MPM はスレッド型モジュールである注意が必要です。
つまり、非スレッドセーフなモジュールとの互換性がありません
具体的には、Apache の mod_php(php_module) は非スレッドセーフなモジュールであり、Worker MPM との互換性がありません。
WordPress は PHP で動作するため、PHP-FPM を用いる方法に変更する必要があります。

次の章では、実際の切り替え手順とその結果について説明していきます。

Apache Worker MPM の設定手順

では、いよいよ設定手順の説明に入ります。
なお、環境次第では重大なエラーにつながる可能性もあるため、事前にバックアップを取得しておくことを強くお勧めします。

Prefork の無効化と Worker の有効化

まず、モジュールそのものの無効化・有効化を設定しましょう。
設定には、モジュールの設定ファイルを修正する必要があります。

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

mpm_prefork_module は無効化するため、行をコメントアウトします。
mpm_worker_module は有効化するため、行のコメントを外します。

#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
…
LoadModule mpm_worker_module modules/mod_mpm_worker.so

これで、Apache Worker MPM の設定は完了です。

設定の確認は、以下のいずれかのコマンドで確認可能です。

$ httpd -V | grep MPM
Server MPM:     worker
$ httpd -M | grep mpm
 mpm_worker_module (shared)

PHP-FPM の設定手順

概説とメリット

まず、PHP-FPM とは、ウェブサーバーと PHP の間の高速なインターフェイスを提供する PHP の代替バージョンです。
※FPM: FastCGI Process Manager

このツールは、FastCGI と互換性があり、リクエストごとに新しい PHP プロセスを起動する代わりに、事前に起動しておいたプール内のプロセスを再利用します。
これによって、PHP-FPM はパフォーマンスを向上させ、リソースの使用を最小化します。
また、非スレッドセーフな mod_php に代わるスレッドセーフな解決策として、Apache の Worker MPM と組み合わせて使用することができます。

インストールと設定

PHP-FPM のインストールは、パッケージマネージャーを用いて簡単に行うことができます。
例えば、Amazon Linux 2 では次のコマンドで PHP-FPM をインストールできます。

sudo yum install php-fpm

次に、インストールが完了したら、PHP-FPM を Apache と統合する必要があります。

vi /etc/httpd/conf.d/php.conf

ここでは、以下の行を追加または更新します。

<FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost/"
</FilesMatch>

これによって、PHP リクエストを PHP-FPM にリダイレクトされます。
要するに、.php 拡張子を持つすべてのファイルは PHP-FPM によって処理されます。
※ファイルパスは環境により異なる可能性があります

mod_php の無効化

最後に、mod_php(php_module)を無効にします。
これによって、Apache が Worker MPM とともに正しく動作し、同時にメモリ使用量を抑えることが可能となります。
具体的には、Apacheのモジュール設定ファイルを編集します。

vi /etc/httpd/conf.modules.d/15-php.conf

mod_php に関連する Import 行をコメントアウトします:

#      LoadModule php_module modules/libphp.so
…
#      LoadModule php_module modules/libphp-zts.so

※ファイルパスとモジュール名は環境により異なる可能性があります

プロセス・スレッド数の調整

ここまでの設定で、httpd と php-fpm プロセスがそれぞれ独立して動作するようになります。
試しに、この段階で各種サービスを再起動して、違いを確認いただいても構いません。
これだけでも、パフォーマンスは向上していることが確認できるかと思います。

しかし、t2.micro インスタンスでは、php-fpm プロセスが多数同時起動することがボトルネックになりかねません。
以下では、適切な処理が行えるよう、設定ファイルで指定するパラメータの例を確認しましょう。

各種パラメータ設定例

まず、以下の設定ファイルでパラメータを指定可能です。

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

例えば、以下はアクセス数が非常に少ない場合の、各種パラメータの設定例です。

pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

あくまで設定例なので、リソース状況をモニタリングしつつ調整してみてください。

各種パラメータ概要

  • pm:
    • これはプロセスマネージャーの設定です
      これによって、PHP-FPMがどのように子プロセスを管理するかが決まります
    • 有効な値はstatic、dynamic、ondemandのいずれかです
      dynamic を設定した場合、子プロセスの数は必要に応じて増減します
  • pm.max_children:
    • 子プロセスの最大数を設定します
      これは同時に処理できるリクエストの最大数に相当します
  • pm.start_servers:
    • サーバーの起動時に生成される子プロセスの数を設定します
      これは pm が dynamic か ondemand のときに有効です
  • pm.min_spare_servers:
    • アイドル状態の子プロセスの最小数を設定します
      これは pm が dynamic のときに有効です
      なお、アイドル状態の子プロセスの数がこの値よりも少なくなると、新たな子プロセスが生成されます。
  • pm.max_spare_servers:
    • アイドル状態の子プロセスの最大数を設定します
      これは pm が dynamic のときに有効です
      なお、アイドル状態の子プロセスの数がこの値よりも多くなると、余分な子プロセスが終了します。

その他のパラメータや、詳細については公式ドキュメントをご参照ください。

PHP: 設定 - Manual
PHP is a popular general-purpose scripting language that powers everything from your blog to the most popular websites i...

これで、PHP-FPM の設定は完了です。

設定反映と結果確認

ここまでで、各設定が完了しました。

設定反映

まず、設定内容を反映するために、各種サービスを再起動しましょう

sudo systemctl restart httpd
sudo systemctl restart php-fpm

設定に問題がなければ、正常に起動するかと思います。
一例ですが、php_mod の無効化を忘れていると以下のようなエラーが発生します。(体験談)

httpd: [php:crit] [pid 4177:tid 140319625552448] Apache is running a threaded MPM, but your PHP Module is not compiled to be threadsafe.  You need to recompile PHP.

自動起動設定

また、必要に応じて自動起動設定もしておきましょう。

sudo systemctl enable httpd
sudo systemctl enable php-fpm

結果確認

最後に、top コマンドや CloudWatch で、メモリ使用率が下がっていることを確認しましょう。

top - 09:44:44 up  8:56,  1 user,  load average: 0.02, 0.05, 0.05
Tasks: 105 total,   1 running,  62 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :   975596 total,   424116 free,   372996 used,   178484 buff/cache
KiB Swap:  1048572 total,  1017720 free,    30852 used.   462260 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                                                      
 8216 apache    20   0  476844  91216  13088 S  0.0  9.3   0:09.29 php-fpm
 8219 apache    20   0  474140  88740  13092 S  0.0  9.1   0:06.51 php-fpm
 8215 apache    20   0  470704  85952  13140 S  0.0  8.8   0:09.99 php-fpm
 7712 root      24   4  792268  56724   9248 S  0.3  5.8   0:28.81 aws
 7129 root      20   0  789336  29688  15304 S  0.3  3.0   0:26.12 amazon-cloudwat
 7474 root      20   0  326744  19448  14528 S  0.0  2.0   0:00.29 php-fpm
CloudWatch Metrics「CWAgent swap_used_percent - CWAgent mem_used_percent - AWS/EC2 CPUUtilization」
CloudWatch Metrics「CWAgent swap_used_percent – CWAgent mem_used_percent – AWS/EC2 CPUUtilization」

無事、メモリ使用率が大幅に低下させることができました。

ここで、死活監視等を実装していない人は、きちんとサービス (WordPress等) が正常稼働しているか確認しておきましょう。
本記事の設定では、php-fpm プロセス数・スレッド数を抑制しているため、パフォーマンスへの影響が出ていないかも確認することをお勧めします。

さいごに

本記事では、EC2 t2.micro で WordPress を運用するにあたって陥りがちなメモリ不足問題(OOM)の根本的な対策として、Apache Worker MPM と PHP-FPM を用いた最適化について解説しました。

途中、割愛しましたが、php-fpm ももちろんエラーログを出力してくれます。

# grep "error_log" /etc/php-fpm.conf
error_log = /var/log/php-fpm/error.log

今後、アプリケーションエラーが発生した際にきちんと調査ができるよう、CloudWatch Logs へ出力設定することをお勧めします。

具体的な設定方法については、以下の記事で詳細に解説しておりますので、ご参照ください。

EC2 のシステム/アプリログを CloudWatch Logs に出力する方法

コメント