この記事は CyberAgent Developers Advent Calendar 2019 25日目の記事です🎉

AI事業本部の黒崎( @kuro_m88 )です。CyberAgentのアドベントカレンダーを書くのは5回目になりました。普段はDynalystという広告配信プラットフォームのバックエンド開発をしています。

今年は所属している事業部のWordPressのリニューアルを手伝ったのでその話をします。

新サイトはこちらです。

https://cyberagent.ai/

8月までは「アドテク本部」という部署だったのですが、9月から組織改編があり「AI事業本部」という組織に変わりました。設立の経緯はこちらの記事をご覧ください。

「AIが全ての産業に影響を与える」 AI新時代 に、取締役 内藤が挑む3つのこと

サイトをリニューアルするにあたって

社内で組織改編の話が出た時にはサイトのデザインやコンテンツのリニューアルのプロジェクトは既に動き始めていました。
インフラ面のリニューアルの予定はなかったのですが、普段AWS上で動くプロダクトを開発していることもあり、その知見が活かせるだろうというのと、面白そうだったのでインフラ面のリニューアルを担当させてもらうことにしました。

今までの構成

初期の構成図

素のEC2にWordPressをインストールして、インターネットに公開していました。シンプルですね。

課題

Nginxでアクセス制限をすることにより最低限のセキュリティは担保していましたが、

  • HTTPサーバとストレージとDBがすべて同一のインスタンスのため、たまにインスタンスが落ちるとサイトがまるごと落ちるので手動で復旧作業が必要(忘れた頃にやってくるのでつらい)
  • バックアップはインスタンスのスナップショットのみ
  • WordPressは攻撃が多いのでセキュリティの面で不安
  • 性能を上げるためにはスケールアップしか手段がない

などなど問題がありました。

考えた構成

WordPressの運用は本業ではないので、できるだけ手離れがしやすくて安定する構成を目指しました。

具体的に意識したのは以下です。

  • セキュリティの強化
  • 表示の高速化
  • インスタンス障害時の自動復旧
  • バックアップの自動化
  • AWSを知らない人でもWordPress自体の改修作業ができる

これらを満たすために Amazon CloudFront + AWS WAF + ALB + Amazon Cognito + Amazon EC2 + Amazon Aurora + Amazon EFS + AWS Backupという構成を考えました。

新しいサイトの構成図

それぞれのコンポーネントの役割について説明します。

Amazon CloudFront

CDNです。記事は静的なコンテンツなのでキャッシュし配信を高速化するのとEC2インスタンスの負荷を減らします。記事を更新したときにキャッシュが残ったままだと実際に反映されるまでに時間がかかってしまうので更新時に自動で該当記事のキャッシュのinvalidationをしてくれるプラグインを入れました。

C3 Cloudfront Cache Controller

記事以外にもカスタマイズして作った記事一覧ページをinvalidationしたかったので少しだけプラグインのコードを書き換えました。汎用性のある書き方になおしてプルリクエストを投げようと思っています。

脆弱性につながる挙動を減らすためにGET系のリクエストのみ通過させています。POSTが必要なもの(フォーム等)はCyberAgentのコーポレートサイトのフォームを使うなどしてこのサイトに動的なコンテンツを持たせないような交渉をして、改修をしました。

副次的なメリットとして、IPv6対応とHTTP/2対応も実現できました!

AWS WAF

ファイアウォールです。11月に新しくリリースされたAWS WAFv2を使っています。
新規リリースだったので、厳しい設定にしてから、正常な操作を行ったにも関わらずブロックされるケースはルールを緩和していくようにして設定を詰めていきました。

WAFを有効にしたら早速怪しいリクエストをブロックしはじめたのが確認できました。

ALB

ロードバランサです。今回はロードバランサの配下にはサーバは1台しかいませんが、サーバをAutoScalingで構成するのと、ALBの認証連携機能を使うために導入しました。
CloudFrontより転送されてきたGET系のリクエストはそのまま通過させ、それ以外は認証を要求するようにしています。
CloudFrontから転送されてきたリクエストかどうかはUserAgentの値で判別できますが、これだと誰でもUserAgentの偽装ができてしまうので、CloudFront側で特定のヘッダ(例: X-WP-Secret-Key)を付加し、そのヘッダのvalueに秘密の値を入れ、ALB側で一致するか確認することでCloudFrontから転送されてきたリクエストなのかどうかを判別させています。

社内の人間がWordPressの管理画面を開いて記事を投稿する場合はCloudFrontにアクセスするのではなく、ALBのエンドポイントに直接アクセスしてもらい、ALBの認証を通すようにします。認証にはCognitoというサービスをつかいました。

Amazon Cognito

認証やアクセスコントロールの機能を持ったマネージドサービスです。弊社にはシングルサインオンのシステムがあり、OpenID Connectにも対応しています。今回はシングルサインオンのシステムとCognitoをOpen IDで接続しました。
これにより、社員のみがWordPressの管理画面(ALB)にアクセスできる環境ができました。

Amazon EC2

サーバインスタンスです。NginxとWordPressが動いています。旧ドメインにアクセスがあったときのリダイレクトやURLの書き換えはNginxにルールで書きました。AutoScalingを使って常時1台インスタンスが起動している状態を維持するようにしています。サーバが障害等で落ちると新しいインスタンスが自動で立ち上がります。新しいインスタンスが起動するまでの間はユーザはアクセスできなくなりますが、基本的には頻繁に落ちる物ではないですし、落ちても数分で自動復旧するのと、アクセスの多い記事はCloudFrontのキャッシュに乗っているため全体がダウンとはなりにくいことから、短時間のダウンタイムは許容することにしました。

今回の構成だと、公開用のURLと管理画面用のURLはドメインが異なります。WordPressは設定と異なるドメイン宛にきたリクエストはリダイレクトするようで、管理画面にアクセスすると公開用のドメインにリダイレクトされてしまい、困りました。対策として、wp-config.phpのWP_SITEURLとWP_HOMEの変数を固定せずにリクエストの内容からホスト名を取り出して設定を動的に書き換えるようにしました。

// リダイレクトループ防止
$_SERVER['HTTPS'] = 'on';

// WP_SITEURLとWP_HOMEをHostヘッダの値に応じて動的に書き換える
define('WP_SITEURL', 'https://' . $_SERVER['HTTP_HOST']);
define('WP_HOME', 'https://' . $_SERVER['HTTP_HOST']);

一方で、どんなホスト名でもアクセスしてきたホスト名をそのまま使うとHostヘッダを利用したXSSにつながる可能性があるため、前段のNginxのバーチャルホストの設定で想定していないホスト宛のリクエストはフィルタリングをしています。同様の設定方法を検討される方はご注意ください。

Amazon Aurora

MySQL互換のデータベースです。DBのサーバインスタンスは1台で構成していますが、ストレージレイヤは自動でレプリケーションされているためDBに障害が起きてもデータの損失は発生せず、短時間で自動復旧するのでEC2と同様短時間のダウンタイムは許容してインフラコストを抑えることにしました。
バックアップは毎日自動で取得されます。

Amazon EFS

NFS互換のストレージサービスです。EC2インスタンスにマウントして、WordPressのファイルをホスティングします。WordPress自体のphpファイルとユーザがアップロードする画像等のリソースもすべてEFSに置いたため、EC2インスタンスに障害が発生して新しいEC2インスタンスが起動してもファイルは失われません。静的コンテンツをS3にアップロードするプラグインもあるかとは思いますが、WordPressのデザイン等の改修をする方ができるだけ素のWordPressを使っている感覚で作業できるようにEFSを選択しました。
今回はCloudFrontが前段のキャッシュとして存在しているので問題ありませんが、キャッシュしない場合は小さいファイルを大量に読み出す速度(レイテンシ)の面で問題があるのでお気をつけください。

AWS Backup

EFSのバックアップの自動化に使いました。毎日バックアップを作成しています。

リリース

無事切り替えが完了し、リニューアルは成功しました🎉

記念にGoogleのPageSpeed Insightsでスコアを測定してみました。CloudFrontでキャッシュしているので速度面のパフォーマンスは申し分なさそうです。

ロギングも簡単になりました。CloudFrontのアクセスログを有効化して、S3にログが吐き出されるようにするだけでAthenaを使ってSQLでアクセスログを分析できるようになりました。閲覧数等の把握のためであればGoogle Analyticsを用いればよいのですが、リリース初期でのリンク切れや不具合の検出にはアクセスログが必要で、とても助かりました。
例えば、IPv4のアクセスとIPv6のアクセスの比率を知るためにこんな感じのクエリを書きました。

AthenaでCloudFrontのログをクエリする様子

いまのところは1割くらいがIPv6経由でのアクセスのようですね。

CloudFrontでのアクセスログをAthenaで集計する方法はこちらの記事がわかりやすいです。
Amazon CloudFront ログのクエリ

おわりに

2019年のCyberAgent Developers Advent Calendar 2019もお楽しみいただけたでしょうか?
2020年もたくさん更新していくので、よろしければ公式アカウントのフォローもお願いします!

それでは!