サーバーが1台止まると、サイトも問い合わせフォームもファイルの受け口も、まとめて止まります。バックアップは毎日取っている、復元手順も分かっている、それでも「復旧が終わるまでの数時間をどう埋めるか」だけは決めていない。移行や保守の相談で最も多く見つかるのがこの状態です。DNSを別のサーバーへ向け直す方式は、切り替えを決断してからレコードが行き渡るまで待たされ、その間はどうせ止まります。ロードバランサを立てれば確実ですが、常時2台分の費用と設定の手間が常に乗り続けます。その中間として実際に組んだのが、入口のPHPを1枚だけ二重化して、そこで振り分ける構成でした。
二重化するのは入口の1ファイルだけ
対象は、利用者から制作データを受け取る仕組みを持ったサイトでした。表側のサイトと、ファイルを受け取って処理する側が別ドメインに分かれていて、表からクロスドメインでPOSTする作りです。この「受け取る側」を2系統に増やし、表側が叩く入口の index.php で行き先を決めるようにしました。
入口がやることは2つだけです。ホストの一覧を持っておき、通常は mt_rand() でどちらかを選ぶ。POSTに host パラメータが付いていて、それが一覧に載っているホストなら、そちらを優先する。障害時は担当者が明示的に生きている側を指定できますし、指定がなければ勝手に半々で流れます。DNSのレコードにもロードバランサにも触りません。
<code>$hosts = array(
'https://up1.example.jp/',
'https://up2.example.jp/',
);
$target = (isset($_POST['host']) && in_array($_POST['host'], $hosts, true))
? $_POST['host']
: $hosts[mt_rand(0, count($hosts) - 1)];</code>短いコードですが、押さえどころが1つあります。host を外から受け取る以上、必ず配列との照合を挟むことです。受け取った文字列をそのまま転送先に使う実装にすると、任意のURLへPOSTを中継する踏み台がそのまま出来上がります。片方のサーバーを切り離したいときは、配列から1行消すか、コメントアウトするだけで済みます。
なぜDNSの切り替えでは間に合わないのか
止まったときに何を切り替えるかで、待たされる時間と月々の負担が変わります。3つの方式を並べると、選ぶ基準ははっきりします。
| 方式 | 切り替えに必要なもの | 費用のかかり方 | 向いている状況 |
|---|---|---|---|
| DNSを予備サーバーへ向け直す | レコード変更と、TTLに応じた反映待ち | 予備サーバー代のみ | 数時間止まっても業務が回るサイト |
| ロードバランサを前段に置く | 操作なし(自動で外れる) | 常時2台分+前段の費用 | 止まった瞬間が売上に直結する構成 |
| 入口のPHPで振り分ける | ファイル1枚の編集、またはPOSTでの指定 | 予備サーバー代のみ | 受け口や外部連携など、状態を持たない処理 |
3つ目が成立するのは、振り分ける対象が「状態を持たない処理」に限られるからです。ログイン中のセッションや、途中まで書き込んだデータベースの内容は、2台の間で共有されません。ファイルを受け取って保存する、外部へ中継する、といった一発で完結する処理であれば、どちらに落ちても結果は同じになります。逆に、WordPress本体をこの方式で2台に振ると、投稿がどちらに保存されたか分からない状態になります。そちらはデータベースを共有する構成を先に考える話です。
クロスドメインのPOSTが急に通らなくなったら
受け口を別ドメインに置く構成では、CORSの設定が生命線になります。ブラウザは本番のPOSTの前にプリフライトの OPTIONS リクエストを投げますが、これをアプリケーションのルーティングに到達させると、CakePHPやLaravelのようなフレームワークは「そんなルートは無い」とエラーを返します。確実なのは、.htaccess の mod_rewrite で OPTIONS を捕まえ、フレームワークに入る前に 204 を返してしまうやり方です。
<code>SetEnvIfNoCase Origin "https?://(www.)?(site-a.example.jp|site-b.example.jp)$" ORIGIN_OK=$0
Header set Access-Control-Allow-Origin %{ORIGIN_OK}e env=ORIGIN_OK
Header set Access-Control-Allow-Credentials "true" env=ORIGIN_OK
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
RewriteCond %{REQUEST_METHOD} ^OPTIONS$
RewriteRule ^ - [R=204,L]</code>許可するオリジンを * で済ませないでください。Origin ヘッダを正規表現で照合し、通ったものだけをそのまま返す形にします。Basic認証やCookieを併用する構成では、そもそも Access-Control-Allow-Credentials: true と * は同時に使えません。公開前のサイトを認証で隠したまま連携させたい場合は、公開前のサイトをBasic認証で隠す設定と、担当者に渡す時の注意で書いた認証の置き方と合わせて考えることになります。
予備側に何を置けば「予備」になるのか
予備サーバーを借りただけでは予備になりません。実際に、サーバー会社側の障害でファイルが部分的に欠けた5サイトを復旧したことがあります。そのとき何が欠け、何で戻せたかは、そのまま「予備に置いておくべきもの」の一覧になります。
| 欠けたもの | 気づき方 | 戻し方 |
|---|---|---|
| プラグイン本体のファイル | 管理画面でエラー、機能が消える | 同じプラグインを最新版で上書きすれば全ファイルが再配置される。設定はデータベース側に残るので消えない |
WordPress本体のファイル(wp-includes 内のJSなど) | 編集画面のボタンが効かない | 同じバージョンの公式配布物から該当ディレクトリを差し替える。管理画面の「WordPressの再インストール」でも通る |
| 有料プラグインのファイル | 更新も再取得もできない | ライセンスが必要なため、構築時に保管したzipが事実上唯一の復旧手段になる |
languages ディレクトリ | 表示が英語に戻る程度で、気づきにくい | 本体・プラグインと同じ方法で差し替え |
バージョンを上げたくない事情がある場合は、同じバージョンを動かしている別のサイトから、そのディレクトリを丸ごとコピーするのが早いです。ここで効いてくるのが「同じ構成のサイトをもう1つ持っている」という状態そのもので、予備サーバーは待機要員であると同時に、部品取りのできる在庫でもあります。1つのサーバーに複数のWordPressを置く構成の作り方で触れた同居構成にしておくと、この部品取りがさらにやりやすくなります。
設定した直後に、実データで通してみる
入口の振り分けを組んだ案件は、そもそも「こちらは何も触っていないのに、ある日からアップロードだけが失敗する」という相談から始まりました。調べていくと、レンタルサーバー会社がXSS攻撃への対応として仕様を変更しており、それが利用者への事前通知なしに適用されていました。しかも変わったのが1つではなく、PHPのバージョン、CA証明書の検証、クロスドメイン制御の3つが同時でした。1つ直しても症状が残るので、切り分けが長引きます。
証明書の検証が強制されたことで、curl や file_get_contents で検証をスキップしていた古いコードが軒並み動かなくなっていました。古い実装ほど、この手抜きが残っています。載っていたフレームワークはCakePHP 3.5で、サーバー側のPHP強制更新が来た時点で止まる可能性を常に抱えた状態でした。冗長化の前に、この「サーバー会社の都合で明日止まりうる」という前提を共有しておくことが実は本題です。
そのうえで受け口をもう1系統増やし、入口で振り分ける形にしました。同じ理由で片方が止まっても、もう片方で受け続けられます。ただし、設定した直後の疎通確認で不具合が出ました。設定ファイルを書いて表示が出ることを確かめただけでは足りず、実際にファイルを選び、送信し、保存先に入るところまで通して初めて確認になります。切り替え先を用意する作業は、ここまでやって完了です。
入口ファイルに認証情報を書かない
この構成の弱点は、入口の index.php が「いろいろ書き込みたくなるファイル」になることです。実際、その中にBasic認証のユーザー名とパスワードが平文でハードコードされていました。振り分け先のホスト名、認証情報、暫定の分岐——短期の対応がそのまま残りがちな場所なので、認証情報だけは設定ファイルか環境変数へ切り出し、公開ディレクトリの外に置いてください。
もう1つ、予備は放っておくと腐ります。PHPのマイナーバージョン、証明書の更新、プラグインの版、どれも片側だけが進んでいきます。定期的に本番と同じ操作を予備側で通す時間を作るか、本番に変更を入れた回に予備も揃える運用にするかのどちらかです。前者を確実にやるなら、WordPressのテスト環境をレンタルサーバー内に作る手順と同じ考え方で、予備を検証環境も兼ねさせてしまうのが現実的です。手が回らない場合の点検の受け持ちは、保守の範囲として切り出せます。
よくある質問
予備は同じレンタルサーバー会社で借りてよいですか
手間と費用だけを見れば同じ会社が楽です。ただし今回のように、サーバー会社側の仕様変更が引き金になる障害では、同じ会社の同じ系統に置いた予備は同時に同じ影響を受けます。ハードウェア障害だけを想定するのか、事業者側の変更まで想定するのかで答えが変わります。両方に備えるなら会社を分ける以外にありません。
常時2台に振り分ければ、負荷分散にもなりますか
状態を持たない処理に限れば結果的にそうなります。ただし目的が違います。mt_rand() の振り分けは、応答が遅い方や落ちている方を避けてくれるわけではないので、片方が死んでいれば半分のリクエストは失敗します。あくまで「片方が生きていれば、手を入れれば全量を通せる」状態を作る仕組みだと考えてください。
WordPressのサイトでも同じ方式が使えますか
入口1枚での振り分けは、ファイルの受け口や外部連携のように単発で完結する処理向きです。WordPress本体は投稿もオプションもデータベースに書くため、2台に振ると内容が分かれます。WordPressを二重化するなら、データベースを共有するか、片方を読み取り専用の待機系にして手動で昇格させる形になり、設計は別物になります。
どのくらいの頻度で予備を最新にすべきですか
頻度で決めるより、本番に変更を入れたタイミングを唯一の同期点にするほうが守れます。プラグインを更新した、テーマを触った、PHPの版を上げた。その作業の最後に予備側へ同じ変更を入れる手順を付けておけば、少なくとも「気づいたら別物になっていた」という事態は避けられます。
