ENECHANGE Developer Blog

ENECHANGE開発者ブログ

Wordpress(PHP-FPM)と Nginx との間の FastCGI の設定を見直した話

ENECHANGE のチーフエンジニアという肩書の 川西( a.k.a cuzic )です。

最近、子ども(男の子、6歳)がスーパーマリオメーカー2で作ったステージで遊んでいます。 見たことがない敵がたくさんいますね。びっくりします。 踏んだら倒せるかも分からないし、どう動くかもわかりません。 初見では死んじゃいます。

はじめに

Wordpress のインフラ改善シリーズ第4弾です。

今回は、Wordpress(PHP-FPM)に Nginx から接続する方法を改善した話を書きます。

移行前の設定

enechange.jp はもともと Ruby on Rails と Wordpress が一台の EC2 の中に同居しており、 Nginx でその2つを振分けするように設定していました。 /articles 以下のパスを受け取ったときは Wordpress 側が応答しています。

この Wordpress (PHP-FPM)側の設定のリファクタリングをしたというのが今回の記事の内容です。

もともと FastCGI に通信するときに一度 Unix ドメインソケットを経由するような Nginx の設定になっていました。 下記のようなかんじです。

server {
    listen unix:/var/run/nginx-backend.sock;
    server_name  _;
    # FastCGI に接続するための設定
}

upstream backend {
    server unix:/var/run/nginx-backend.sock;
}

server {
    listen 80;
    server_name  _;
    location /articles {
        # proxy_cache 関連の設定
        proxy_pass http://backend;
    }
}

このように Unix ドメインソケットを経由していることによって、運用上の問題が生じていました。

サーバ再起動のタイミングで、まれに /var/run/nginx-backend.sock が消えず、その結果、Nginx が起動せず、サービスが停止する問題が起きていました。 毎日行っている定期スナップショットを元に AMI を作りサーバを構築する場合は、かならず /var/run/nginx-backend.sock が残っています。 そのため、定期スナップショットを元に台数を増やすときは、① AMIを作る、② インスタンスを作成、起動、③ 残っている sock ファイルを削除、④ サーバを停止、⑤ 再度 AMI を作成、⑥ その AMI を元にサーバ台数を増やすという長い手順が必要になってしまっていた。 また、Nginx 設定のファイル分割が不適切で、どこにどんな設定が書かれているのかが分かりにくく保守性が低い状態となっていました。

既存の設定は歴史的な経緯で上記のような Unix ドメインソケットを経由する設定となっていましたが、実際は Unix ドメインソケットがなくても問題ありません。

下記のようにできます。

server {
    listen 80;
    server_name  _;
    location /articles {
    # FastCGI に接続するための設定
    }
}

この方がずっとシンプルで、保守性も高く、sock ファイルが残ることに起因する問題も発生しません。

そこで、 Unix ドメインソケットを廃止するような設定変更を実施することにしました。

移行時のポイント

もともと Unix ドメインソケットを使って、proxy の設定をすることで、下記を実現していました。

  • proxy_cache を使って、Nginx のレイヤでページキャッシュする
  • /articles という PATH を / にマッピングした上で FastCGI の設定をする

これらは下記の方法に移行しました。

  • fastcgi_cache を使ってキャッシュする
  • fastcgi_param の SCRIPT_FILENAME の設定を適切に行い、 Wordpress のディレクトリを正しく参照できるようにする

fastcgi_cache によるキャッシュ

Wordpress ログイン判定

Wordpress では記事編集などのために管理者としてログインした場合、キャッシュしたページを返すのではなく、常に最新のコンテンツを返す必要があります。 FastCGI でキャッシュする場合もこの点を考慮して実施する必要があります。

管理者としてログイン中かどうかは cookie の内容を見て判定できます。

具体的には下記のような処理を書きました。

set $do_not_cache 0;
if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) {
  set $do_not_cache 1;
}
if ($request_method != GET) {
  set $do_not_cache 1;
}

fastcgi_no_cache $do_not_cache;
fastcgi_cache_bypass $do_not_cache;

fastcgi_no_cache というディレクティブを使うことで、該当の条件を満たしたときは、キャッシュせずに最新のコンテンツを応答するように設定しています。

CloudFront 配下にある場合の Nginx での モバイル、PC の判定

enechange.jp では、サーバサイドで出し分けして、スマートフォンでアクセスした場合はモバイル用のページ、PC でアクセスした場合は PC 用のページを応答するようにしています。また、enechange.jp で Wordpress のページは CDN ( Amazon CloudFront )でもキャッシュを行ってます。 Amazon CloudFront を使う場合は、 サーバでは元の User-Agent を知ることはできません。代わりに、 CloudFront-Is-Mobile-Viewer や CloudFront-Is-Tablet-Viewer という HTTP ヘッダに格納される Amazon CloudFront の判定結果を使う必要があります。

判定結果に応じてキャッシュキーを切り替えるために記述した設定が下記になります。

set $mobile "";
if ( $http_cloudfront_is_mobile_viewer = 'true' ) {
  set $mobile '@smartphone';
}

if ( $http_cloudfront_is_tablet_viewer = 'true' ) {
  set $mobile '@smartphone';
}

fastcgi_cache_key    "$scheme://$host$request_uri$mobile";

SCRIPT_FILENAME の値の編集

Nginx から PHP-FPM に対して SCRIPT_FILENAME というHTTPヘッダを送ると、その内容に応じたファイルで処理されます。

この SCRIPT_FILENAME ヘッダを適切に設定するためには

location /articles/ {
  alias /var/www/html/;

  fastcgi_param  SCRIPT_FILENAME     $request_filename;
}

というかんじに設定しています。 Amazon Elastic Beanstalk の PHP 環境ではアプリケーションのファイルは /var/www/html 以下で参照できます。 そのディレクトリを使うように alias ディレクティブを使っています。

Nginx 側の $request_filename の値の確認

しかしながら、当然かもしれませんが、上記をそのまま実行しても期待どおり動作しないこともありました。 そこで、デバッグが必要です。

ここでは $request_filename の値が何なのか、知りたいところです。 いわゆる printf デバッグです。

Nginx で変数の値を知るためには

add_header "X-Request-Filename" $request_filename always;

などと設定します。 このように設定することで、 Google Chrome の開発者ツールを使って HTTP 応答ヘッダの中の X-Request-Filename の内容を確認できます。

always を指定することで 404 Not Found のステータスで応答している場合でも "X-Request-Filename" の HTTP レスポンスヘッダを確認できます。

Wordpress (PHP-FPM)側の SCRIPT_FILENAME の値の確認

PHP-FPM が受け取っている SCRIPT_FILENAME の値を確認するためには、ログに書き出す方法があります。

私が使っている環境ですと、/usr/local/etc/php-fpm.d/www.conf に下記のような記述があります。

; The access log format.
; The following syntax is allowed
;  省略
;  %e: an environment variable (same as $_ENV or $_SERVER)
;      it must be associated with embraces to specify the name of the env
;      variable. Some exemples:
;      - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e
;      - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e
;  %f: script filename
;  省略

この %e や %f を使います。

もとの access.format に対して、前に書くのであれば

access.format = "%{SCRIPT_FILENAME}e %f %R - %u %t \"%m %r%Q%q\" %s"

とするかんじです。

こうすることで、PHP-FPM 側で受け取っている SCRIPT_FILENAME の値をアクセスログに表示できます。期待と異なる値になっていれば、 Nginx 側の設定を修正することができます。

おわりに

今回は、Nginx から Wordpress に通信するときの設定をリファクタした話について書きました。

このあたり、デバッグする方法について、詳しく解説した記事が少なく、私自身設定を書くときに試行錯誤しました。

Wordpress の設定をしているが、ブラウザでは 404 Not Found のページが表示され、ログを見ると php-fpm got error 'primary script unknown' と表示され、何なのか分からない、と悩んでいる方にお役に立てると嬉しいです。

wp-config.php 内の認証キーを aws secrets manager に移行した話

最近、子ども(男の子、6才)がスーパーマリオメーカー2をやっています。

スーパーマリオブラザーズを全然遊んだことないのに、いきなりスーパーマリオメーカー2を遊ぶ今の子供の感覚には違和感があります。ですが、そういう子も含めてちゃんと遊べるように設計している任天堂はすごいな、と思います。

はじめに

Wordpress のインフラ改善シリーズ第3弾です。

今回は Wordpress の中のシークレット情報を AWS Secrets Manager で管理するようにした話です。

Wordpress の中のシークレット情報

Wordpress では設定情報が wp-config.php の中で管理されています。

その中で、下記のような設定が書かれています。

/**#@+
 * 認証用ユニークキー
 *
 * それぞれを異なるユニーク (一意) な文字列に変更してください。
 * {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org の秘密鍵サービス} で自動生成することもできます。
 * 後でいつでも変更して、既存のすべての cookie を無効にできます。これにより、すべてのユーザーを強制的に再ログインさせることになります。
 *
 * @since 2.6.0
 */
define('AUTH_KEY',         'put your unique phrase here');
define('SECURE_AUTH_KEY',  'put your unique phrase here');
define('LOGGED_IN_KEY',    'put your unique phrase here');
define('NONCE_KEY',        'put your unique phrase here');
define('AUTH_SALT',        'put your unique phrase here');
define('SECURE_AUTH_SALT', 'put your unique phrase here');
define('LOGGED_IN_SALT',   'put your unique phrase here');
define('NONCE_SALT',       'put your unique phrase here');

/**#@-*/

この認証用のユニークキーは環境ごとに異なる値に設定する必要があります。

複数の方法の比較検討

環境ごとに異なる設定値を設定する方法として、下記の3つの案を比較検討しました。

  • 事前に EB 環境の環境プロパティを設定する
  • 動的に .ebextensions 以下にファイルを生成して、デプロイ時に環境プロパティを設定する
  • 別の場所(例: AWS Secrets Manager )に格納したユニークキーの情報をデプロイ時に取得、利用する

事前に EB 環境の環境プロパティを設定する

Elastic Beanstalk では環境プロパティという機能を使って、環境ごとに異なる値を設定することがとてもカンタンにできます。これは標準的な方法で多くの場合で推奨される方法なのですが、今回は採用しませんでした。

理由は、3つあります。

  • 認証キーのような文字列はできる限り、どこにも表示されず漏洩しにくくしたい、と考えました。AWS のマネジメントコンソールの Elastic Beanstalk の設定画面に表示されない、Git のリポジトリにも保存しないようにしたかったのですす。環境プロパティを使う場合は、設定画面に表示されますし、 ENECHANGE では Terraform を使ってインフラの設定を管理している都合上 Git のリポジトリにも保存することになってしまいます。

  • インフラとアプリケーションの責任範囲の問題もあります。Terraform の設定を書くのはインフラエンジニアで、Wordpress の Git repo の内容を編集するのはアプリケーションエンジニアです。今回設定したい認証キーのような設定値はアプリケーションエンジニアの責任範囲であるべきです。Terraform で管理されるべき範囲ではありません。

  • ENECHANGE では Elastic Beanstalk 構築に関する設定などはすべて Terraform で管理しているのですが、その Terraform の環境プロパティに1つでも違いがあれば、ほかのさまざまな設定値も含めて膨大な diff が表示されるというのが Terraform の動作仕様になっています。環境プロパティの数が増えると、些細な差異に対して diff が正しいか目視確認する必要があるため、運用上とてもつらくストレスフルな作業をインフラエンジニアが実施することになってしまいます。

デプロイ時の事前処理で .ebextensions 以下にファイルを生成し、環境プロパティを設定する

Elastic Beanstalk では通常 eb deploy というコマンドを使ってデプロイを実施します。 環境プロパティを設定する config ファイルを生成する事前処理をしてから、デプロイするようにシェルスクリプトや Ruby スクリプトを書く案を考えました。

しかし、実装まではしたもののこの方法は最終的には採用しませんでした。

理由は

  • config ファイルがアプリケーション資源に含まれるため、ステージング環境と本番環境で異なるアプリケーションバージョンとなる
  • ステージング用のアプリケーションバージョンを本番にデプロイしてはいけないという運用上の制約が生じる

からです。

注意していたら、問題ないとも言えますが、インフラ上の理由で運用上の制約があるべきではありません。 仮に作業ミスがあればユーザ影響が生じてしまいます。インフラは最初からそういうことが起きないように設計するのが望ましいです。

別の場所(例: AWS Secrets Manager )に格納したユニークキーの情報をデプロイ時に取得、利用する

今回は、この方式を採用しました。 次節以降で詳しく説明します。

AWS Secret Manager によるユニークキーの情報の管理

下記の要件を満たす方法を考える必要がありました。

  • ステージング、本番で個別に設定可能とすること
  • ソースコード等はまったく同じで EB 上の同じアプリケーションバージョンでステージングでも本番環境でもデプロイ可能にすること
  • ステージング、本番などの設定値、集中一元管理し、必要であれば容易に更新できること

この要件を満たすために次の方法を採用しました。

  • AWS Secrets Manager で管理する
  • どの環境でどのキーを使うかは環境プロパティTIER_ENV で切り替える
  • Secrets Manager の設定値を環境変数として、 Wordpress から参照可能にする
  • Wordpress から環境変数として利用可能とするため、systemd の設定ファイルをデプロイ時に書き出す

Elastic Beanstalk 環境用の wp-config.php

環境変数から設定情報を取得するため、Elastic Beanstalk 用の wp-config.php をファイルを用意しました。

wp-config-eb.php

<?php
define('DB_NAME', $_SERVER['MYSQL_DATABASE']);
define('DB_USER', $_SERVER['MYSQL_USERNAME']);
define('DB_PASSWORD', $_SERVER['MYSQL_PWD']);
define('DB_HOST', $_SERVER['MYSQL_HOST']);
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');
define('AUTH_KEY',         $_SERVER['AUTH_KEY']);
define('SECURE_AUTH_KEY',  $_SERVER['SECURE_AUTH_KEY']);
define('LOGGED_IN_KEY',    $_SERVER['LOGGED_IN_KEY']);
define('NONCE_KEY',        $_SERVER['NONCE_KEY']);
define('AUTH_SALT',        $_SERVER['AUTH_SALT']);
define('SECURE_AUTH_SALT', $_SERVER['SECURE_AUTH_SALT']);
define('LOGGED_IN_SALT',   $_SERVER['LOGGED_IN_SALT']);
define('NONCE_SALT',       $_SERVER['NONCE_SALT']);
$table_prefix  = 'wp_';
define('WP_DEBUG', false);
if ( !defined('ABSPATH') )
        define('ABSPATH', dirname(__FILE__) . '/');
require_once(ABSPATH . 'wp-settings.php');

define('WP_SITEURL', 'http://' . $_SERVER['HTTP_HOST'] . '/articles');
define('WP_HOME', 'http://' . $_SERVER['HTTP_HOST'] . '/articles');

define('CDN_IMAGE_URL', $_SERVER['CDN_IMAGE_URL']);

wp-config.php ファイルの上書き

Amazon Linux 2 環境で使えるプラットフォームフックという機能を使って、 Elastic Beanstalk にデプロイ時に wp-config-eb.php を wp-config.php に上書きコピーします。

.platform/hooks/prebuild/90_copy_wp-config.sh

#!/bin/bash

cp wp-config-eb.php wp-config.php

デプロイ時に、必要な環境変数を有効化

Amazon Linux 2 の Elastic Beanstalk 環境では Wordpress は PHP-FPM 上で動いており、PHP-FPM は systemd 経由で起動します。 そのため、デプロイ時に必要な環境変数を systemd 経由で有効化するようにしました。

環境変数を systemd 経由で有効化するには

  1. 環境変数ファイルの生成
  2. systemd でその環境変数ファイルを読み込むように設定

の2つを実施します。

環境変数ファイルの生成

環境変数ファイルを生成するとき、

  • EB の環境プロパティ TIER_ENV の値に従って、利用する secretsmanager の ID を切り替える
  • secretsmanager の設定値を読み込んで、 /opt/elasticbeanstalk/deployment/secret に systemd で扱える形式で保存する

必要があります。

これらに留意して、Elastic Beanstalk でデプロイ時に実行される .ebextensions 以下のファイルで下記の通り設定しました。

.ebextensions/02_environment.config

commands:
  01_create_secret_env:
    command: |
      case ${TIER_ENV:-foo} in
        "production")
          secret_id="enechange-jp-wordpress-production"
          ;;
        "staging")
          secret_id="enechange-jp-wordpress-staging"
          ;;
        *)
          exit 1
      esac

      env=/opt/elasticbeanstalk/deployment/env
      mkdir -p $( dirname $env )

      /opt/elasticbeanstalk/bin/get-config environment | \
      ruby -rjson -e 'JSON.load(ARGF).each { |k, v| puts %(#{k}=#{v}) }' > $env

      secret=/opt/elasticbeanstalk/deployment/secret
      aws --region=ap-northeast-1 --output=text secretsmanager \
        get-secret-value \
        --secret-id $secret_id \
        --query "SecretString" | \
        ruby -rjson -e 'JSON.load(ARGF).each { |k,v| puts %(#{k}=#{v}) }' > $secret
    env:
      TIER_ENV:
        "Fn::GetOptionSetting":
          Namespace: "aws:elasticbeanstalk:application:environment"
          OptionName: "TIER_ENV"
      AWS_REGION: ap-northeast-1
      AWS_DEFAULT_REGION: ap-northeast-1

.ebextensions 以下のファイルの設定内容に関する詳細は下記を参考にしてください。

docs.aws.amazon.com

生成した環境変数ファイルを systemd で読み込むための設定

.ebextensions で生成した環境変数ファイルを systemd で読み込めるようにするために、 /etc/systemd/system/php-fpm.d ディレクトリ以下に 10-environment.conf というファイルを生成しています。 Elastic Beanstalk の Amazon Linux 2 の PHP 環境では /etc/systemd/system/php-fpm.d ディレクトリ以下の .conf 拡張子のファイルは自動的に systemd で読み込まれます。 systemd で環境変数ファイルを有効化するには、Service セクション以下で EnvironmentFile という設定値を使います。

.platform/hooks/predeploy/02-environment.sh

#!/bin/bash

UNIT='php-fpm.service'
DIR="/etc/systemd/system/${UNIT}.d"
mkdir -p $DIR
CONFFILE=${DIR}/10-environment.conf

echo "[Service]" > $CONFFILE
echo EnvironmentFile=/opt/elasticbeanstalk/deployment/secret >> $CONFFILE

おわりに

今回は Wordpress の wp-config.php 内の設定値を例に、Elastic Beanstalk のアプリケーション内で使うシークレット情報を secrets manager で管理するように移行した例について説明しました。

これらの設定によって、環境ごとに一部の設定値を切り替えたいが、その具体的な値は秘匿したい(AWS のマネジメントコンソール上も表示したくない)という要件を満たすことができました。

Wordpress のメディアファイルを s3fs から efs に移行した話

ENECHANGE の川西( a.k.a. cuzic )です。

最近、いびきの軽減を目的として、のどちんこを切除する手術を受けました。 職場で昼寝していて、いびきが職場に鳴り響いていたのがなくなり、職場環境改善にもつながりました。

続きを読む

Wordpress と Ruby on Rails の CSS の分離

はじめに

最近、急に Nintendo Switch が手に入るようになったので、子ども(6才、男の子)に買い与えました。あつまれどうぶつの森を攻略本とともに買ってみたのですが、1000ページ以上もある攻略本をどこに行くのにも持ち歩いています。 どんなに好きでも大人でもそんな重い本持ち歩く人はいないと思うんだけど、子どもってすごい。

続きを読む

Amazon CloudFront を導入し、enechange.jp の Wordpress の記事配信を高速化した話

ENECHANGE 株式会社チーフエンジニアの川西こと cuzic です。

最近、enechange.jp のインフラ改善に精力的に取り組んでいます。 そこで、やりたいと思っていたことがかなり実現でき、ある程度、目処がついてきました。 せっかくなので、やってきた内容についてまとめてこの tech blog に掲載していきます。

続きを読む

開発背景と現場の話[エネルギープラットフォーム事業編]

こんにちは。ここ数ヶ月、再エネや建設関連銘柄をウォッチしているCTO室のkazです。

ENECHANGEの前身は英ケンブリッジの研究機関で生まれました。くわしくは創業ストーリーをお読みください。 その後、2016年に小売全面自由化される日本市場に参入し、これまでに多数のメディアに紹介いただいています。

今回は求職者に開発現場をイメージしてもらいやすいようにENECHANGEのプロダクトの1つであるエネルギープラットフォーム事業についてご紹介したいと思います。

続きを読む

TerraformでIAM policy変数をプレースホルダーとして使用する

Operations Tips vol7

こんにちは。全社でリモートワークがメインとなり社内SE業務が増加気味のCTO室のkazです。

policyを記述するときにresoruceやconditionがわからない。または、リクエストのコンテキストから取得された値に置き換えたい場合、AWS Identity and Access Managementのpolicy変数をプレースホルダーとして使用することはよくあります。

続きを読む