😺

個人WordPressサイトをCloudflare経由で爆速にしてみた

2024/12/26に公開

概要

本記事は Cloudflare Advent Calendar 2024 19日目の記事です。

WordPressのサイトをCloudflareのCDNを使って高速に表示できるようにした事例を紹介します。
今回移行してみたサイトの1つはこちら。瞬間的に表示されると思います。

https://blog.teraren.com/

3行まとめ

  • Cloudflare CDNを利用して、パフォーマンスが15req/sec => 512req/sec。(約34倍改善) レスポンス速度が平均611.64ms/req => 28.82ms/req。(約21倍改善)。
  • WordPressにCloudflareプラグインを導入してCDN連携を自動化
  • E2EE(End to End Encryption)を導入して経路の安全性を向上

背景

自宅サーバでWordPressを使ったサイトを10個ほど運用しています。

問題

WordPressは動的にページを生成するため、突発的なトラフィックや悪意のあるプログラムによる攻撃を受けたときにサーバの負荷が上がってしまう問題がありました。

WordPressで作られたユーザ向けのページはほとんどのトラフィックがReadなので、CDN(Contents Delivery Network)に載せられれば効率的に配信ができます。
しかし、記事を更新したり、コメントが追加された際にはCDN上のキャッシュの削除といった操作する必要があり、Originが更新された際にCDNへの通知が必要となります。

アプローチ

WordPressにはCloudflareのCDNを使った運用を効率的に行うプラグインが用意されているので、これを使えばWordPressの記事更新に応じたCDNの運用を行えます。
しかもCloudflareのCDNは無料で利用できます。

Cloudflare CDNの主な特徴は以下の通りです:

  • 世界中に330以上のデータセンターを展開し、Anycastネットワークを活用
  • インターネット人口の95%から約50ミリ秒以内でアクセス可能
  • DDoS攻撃の緩和やWAFなどのクラウドセキュリティ機能
  • SSL/TLS暗号化によるセキュアな通信

Cloudflare CDNには上記以外にも様々な機能があります。

概念的には以下のようになります:

Cloudflare CDN概念図

CDNを完全にユーザ向けサービスのフロントエンドに配置します。
画像や動画の静的コンテンツだけでなく、ページ自体もほとんど更新が入らないのでCDNに載せます。このようにCDN、WAF、ロードバランサの機能を統合するアーキテクチャは昨今のスタンダードになってきていると思います。

Cloudflareでの設定

フェーズ1

Cloudflareを利用するためにはドメインが必要であり、DNSゾーンをCloudflareでホスティングする必要があります。
Cloudflareのゾーンホスティング機能は無料で利用できます。

新規構築ではなく、別のDNSゾーンサービスを使っている場合は、まずCloudflareにDNSゾーンを管理させる設定をします。

Cloudflare概念図

「Cloudflare as CDN」と書かれている部分が普通の概念とは異なりますが、CDNとざっくり書いてしまいましたがこの部分がCloudflareの超重要な部分です。

CloudflareのCDNはただのCDNではなく、TLS terminationを行い、リバースプロキシとして動作しています。
それにより、外部からの攻撃、リクエスト内容の検証、オリジンへのトラフィックの制御を行えるようになっています。

次に、設定の手順の概略を記します。

DNSゾーンの登録

まず、Cloudflareを使うための第1ステップはドメインのゾーンを登録するところからです。

ゾーン登録画面

ゾーンを登録したら、各レコードを追加していきます。

レコードを追加する際に普通とは違うのが、"Proxy status" というスイッチがあることです。このスイッチをオンにするとDNSゾーンサーバのレスポンスは登録したIPアドレスなどではなく、Cloudflareのリバースプロキシサーバのアドレスに置き換わります。

DNSの設定なのに、DNSとは違うリバースプロキシの設定も混ざっているので分かりづらいですが、ここは一旦オフにして単純にDNSゾーンの設定にフォーカスします。

DNSレコード設定画面

リバースプロキシで受けられるようにWordPress側のWebサーバを設定

既存のWordPressはインターネット経由でエンドユーザからリクエストを受ける設定になっています。それに追加して、Cloudflare CDNのリバースプロキシからアクセスを受けられるように設定します。

運用中のサーバの場合は、並行運用できるように追加で設定します。

設定例: Configure Nginx Reverse Proxy behind Cloudflare

キャッシュルールの設定

リバースプロキシ自体がCDN機能を持っているので、どのコンテンツをキャッシュするかを設定します。

Webから分かりやすいUIで設定できるので、自分のホスティングするサービスに応じてルールを書いていきます。
無料プランでは10個までしかルールセットを作れません。私はキャッシュの期間ごとにルールセットを作っています。
1ヶ月キャッシュ、7日キャッシュ、1日キャッシュ、キャッシュ無しという分類です。

キャッシュルール設定画面

私の環境における「キャッシュ無し」の設定例を示します。
WordPressの管理画面、ヘルスチェック用エンドポイント、動的なスクリプト、RSSフィードなどを記載しています。

キャッシュ無し設定例

逆に1ヶ月の長期間を設定している設定はこちらです。主に画像などの静的アセットが対象です。昨今はCDNを考慮したアセットの構築が行われているので、内容が変わればURLも変わるという前提のためです。

1ヶ月キャッシュ設定例

CDN周りだけでも以下のような設定が簡単に行えます:

  • クエリストリングごとにキャッシュするかどうか
  • ブラウザキャッシュのヘッダを付加するか
  • オリジンが落ちたときにキャッシュをサーブするか
  • CDNの一時的な一括無効化

CDN設定オプション

WordPressの設定

WordPress用にCloudflareのプラグインが用意されています。

インストールしなくても使えますが、記事を更新すると自動でCDNの関連ページをパージしてくれます。
また、CDNでWordPressを運用するにあたってWordPressの挙動を裏で自動的に変更しているようです。

Cloudflareプラグイン設定画面

例えば、WordPressは動的にページを生成しているので、管理者が記事を閲覧したときと、そうでないときはコンテンツの内容が違います。
その点を考慮して管理者が閲覧したときの内容はキャッシュされないような仕組みを提供しています。(おそらく)

Cloudflare CDNを使う場合はCloudflareプラグインは必須です。物理的にはインストールしなくても運用できますが、様々な運用する際のユースケースに対応する機能が入っているので、入れておかないと予想外のコンテンツがキャッシュされてしまうリスクがあります。

CloudflareのDNS設定でリバースプロキシを有効にする

最後に、DNSの設定を書き換えてCloudflareのリバースプロキシ経由でアクセスできるように設定を変更します。
こうすることでCDNによってWordPressサイトを配信できます。

フェーズ2

フェーズ1だけの設定では問題ないのですが、オリジナルのWordPressサイトはグローバルに公開された状態なので攻撃の危険性があります。

フェーズ2では、オリジナルのWordPressをインターネット上に公開しない設定をし、オリジン側のポート開放が不要になってよりセキュアにサービス運用を行えます。

目指すトポロジは以下です:

フェーズ2トポロジ

トンネルのセットアップ

オリジン側にCloudflareのデーモンを動かして、トンネルを設定できます。Cloudflareとオリジンとのトラフィックをこのトンネルを通すことによってオリジンサーバ側でポート開放をする必要がなくなります

トンネルを使うためにはCloudflare上のZero Trustの管理画面で設定します。
1ホストに対して1本のトンネルを作ってある設定例です。

トンネル設定例

デーモンのインストールも非常に簡単で、管理画面上に表示されている例にあるコマンドを実行するだけです。

デーモンインストール手順

以下のようにパブリックのホスト名と、オリジンのサービス名を登録していきます。ここで登録するとCloudflareのDNSの設定にレコードが追加されていきます。既存のレコードと重複する場合はエラーが出ます。

トンネル設定画面

トンネル経由でオリジンへアクセス

トンネルの設定をする際に、Cloudflareとオリジンの通信を安全にしておくことをおすすめします。ブラウザとCloudflare間の設定はグローバルなのでLet's Encryptといった枠組みでTLS証明書の発行と運用が簡単に設定できますが、オリジンへのアクセスはプライベートネットワークなのでそう簡単にはできません。

そこで、Cloudflareが発行する証明書をオリジンサーバにインストールすることで、Cloudflareから発行された証明書がインストールされたオリジンサーバということを担保する証明書を利用します。

E2EE概念図

その設定が完了すれば、上記のようにCloudflareを挟んでも安全にオリジンの信頼性を保証できることになります。

Cloudflare上ではこのことをE2EEと呼んでいますが、TLS terminationをCloudflare上で行っているのでE2Eでの秘匿性と完全性は保証できません。紛らわしい表現ですね。

また、ここでは触れませんがSSHなどのサービスもZero Trustのサービスによってトンネル経由でアクセスできるように設定できるので、SSHのポートすら開ける必要がありません。

Cloudflareの有料機能

無料プランでも多くの機能が利用可能ですが、有料プランではさらに高度な機能が提供されています:

  1. Waiting page: トラフィック急増時にユーザーを待機させる機能
  2. Image compression: 画像をwebpに自動変換
  3. Advanced WAF: HTTPリクエストの内容を分析し、不審なアクセスを検知

考察

Cloudflareを導入してみて大きく変わったことを書いておきます。

レスポンスタイムが超速い

CDNにキャッシュされると表示が一瞬で終わります。
このページをリロードしてみると、ブラウザキャッシュが読み込んだかのような速さで表示されると思います。

軽くベンチマークをしてみると、 512.91req/secでました。

❯ wrk  'https://blog.teraren.com/'
Running 10s test @ https://blog.teraren.com/
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    28.82ms   90.99ms   1.40s    98.65%
    Req/Sec   257.79     43.32   353.00     73.50%
  5147 requests in 10.03s, 2.06GB read
Requests/sec:    512.91
Transfer/sec:    210.53MB

参考までにWordPressの動的ページをベンチマークしてみます。

❯ wrk  -H 'Host: blog.teraren.com' http://localhost:8080/
Running 10s test @ http://localhost:8080/
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   611.64ms   94.75ms 948.43ms   74.05%
    Req/Sec     9.68      5.43    30.00     72.22%
  158 requests in 10.02s, 64.64MB read
Requests/sec:     15.77
Transfer/sec:      6.45MB

15.77req/secでした。

WordPressで運用しているサイトはそのままで運用しているとキャパが少ないので普通はWP Super Cacheを入れて運用すると思いますが、これを機にページをキャッシュするプラグインをこの際にすべて削除しました。

Webサーバ側の設定が激減

WordPressのサイトでは1サイトあたり約100行削減できました。

HTTPのリダイレクト、HSTS、text compressionの設定などなどを削減できてメンテナンス性が上がります。

# git diff --stat a9d832ed7f1493ea0ce8e75664e7a1b70f95760e blog.teraren.com.conf

nginx/conf.d/blog.teraren.com.conf | 178 ++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------------------

1 file changed, 40 insertions(+), 138 deletions(\-)

TLS証明書更新から開放

以前はcertbotを利用して自動で証明書の更新をしていましたが、知らない間に動かなくなっていたりとか設定ファイルが変わっていたりして結局は手での運用が発生してしまっていました。

Let’s encryptからの更新のお知らせのメールも気にしなくて良くなるので運用の手間から開放されました。

Cloudflare上で行うTLS証明書の設定関連では、HTTPからHTTPSのリダイレクト設定がスイッチ1つで行えたり、HSTSのヘッダ送信がUI上から行えます。

HSTSの設定項目。

自宅サーバ用の固定IPまたはDynamic DNSが不要

tunnelをCloudflareに張るだけなのでローカルで動いているサービスをインターネット上に公開できるので自宅の回線に固定IPアドレスやDynamic DNSが不要になりました。

自宅ネットワークにDMZを作ったりVLANを切っている場合は、設定を無くせる場合もあります。(セキュリティ要件に応じてですが)

ポート開放が不要になった

外部から自宅のネットワークのポート開放(静的NAT)を消せました。Web系、SSHなどのリモートアクセス、リモートアクセスVPN関係のサービスを無くせます。

$ nmap <my home address>
Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-22 11:40 JST
Note: Host seems down. If it is really up, but blocking our ping probes, try -Pn
Nmap done: 1 IP address (0 hosts up) scanned in 3.04 seconds

Webページのパフォーマンススコアが向上

Webサイトのベストプラクティスを自動的に、または簡単に導入できるためGT metrixのスコアが上がります。

Cloudflare WordPress Pluginの機能

内部でどんなことをやっているのか軽く紹介します。
まず1つ目に、管理者がログインしていたらCDNのキャッシュを使わないようにしてくれています。

該当コード。

    public function initAutomaticPlatformOptimization()
    {
        // it could be too late to set the headers,
        // return early without triggering a warning in logs
        if (headers_sent()) {
            return;
        }

        // add header unconditionally so we can detect plugin is activated
        $cache = apply_filters('cloudflare_use_cache', !is_user_logged_in());
        if ($cache) {
            header('cf-edge-cache: cache,platform=wordpress');
        } else {
            header('cf-edge-cache: no-cache');
        }
    }

記事やコメントが更新されたらCDNの関連するページを削除してくれます。CloudflareのCDNは削除命令を出してから数秒で消えますが
AWS CloudFrontは数秒で消えてくれます。

該当コード。

    public function purgeCacheOnPostStatusChange($new_status, $old_status, $post)
    {
        if ('publish' === $new_status || 'publish' === $old_status) {
            $this->purgeCacheByRelevantURLs($post->ID);
        }
    }

    public function purgeCacheOnCommentStatusChange($new_status, $old_status, $comment)
    {
        if (!isset($comment->comment_post_ID) || empty($comment->comment_post_ID)) {
            return; // nothing to do
        }

        // in case the comment status changed, and either old or new status is "approved", we need to purge cache for the corresponding post
        if (($old_status != $new_status) && (($old_status === 'approved') || ($new_status === 'approved'))) {
            $this->purgeCacheByRelevantURLs($comment->comment_post_ID);
            return;
        }
    }

Cloudflareの統計情報

Reverse ProxyとCDNを使いだすと以下のような統計情報が表示されてきます。眺めているだけでも楽しいです。

右にあるメニューに注目してほしいのですが、もしサービスが攻撃を受けていたら一時的に「JavaScriptによる人間かどうかのチャレンジを挟む」というスイッチがあるのでONにすればoriginのサービスを過負荷から守れます。

トラフィックの統計も細かく表示されます。

Webサイトへの攻撃に関する統計情報

使われたプロトコルの種別や、節約できた帯域の情報が表示されます。

30日で見てみると、300GBのトラフィックを節約できたみたいです。(無駄なトラフィックを節約することでCO2削減にも貢献!)

まとめ

最初はCloudflareのCDNが無料なので調査していましたが、実際はCDN以外の機能も充実していることがわかりました。

しかも無料で使えるサービスが沢山あるので今回は無料で使えるサービスをフルに活用して安全で安定的なサービス提供をできるように設定をしました。

Cloudflareを使いだす最初の設定が大変ですし、知識が必要になりますがその後は安心して高速で安全なWebサービスの提供を行えるようになります。

Discussion