BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

Cloudflare でショップページをちょっとだけ速くしてみた - 導入/SSL for SaaS 編

この記事はBASEアドベントカレンダー 2025 の 6 日目の記事です。

エンジニアの右京です。BASE では今年、表示速度の改善を目標にすべてのショップページへ Cloudflare を導入しました。これは、その過程や技術面の簡単な解説です。

記事は前後半になっており、この記事は前半で、Cloudflare を導入〜直後までの話題となります。

モチベーション

ショップページの表示が遅いことに尽きます。サービスが大きくなり、機能が増えていく中で処理が増え、速度が犠牲になってしまうのはある程度は仕方ないことだとは思います。とはいえレスポンスを返し始めるまでに 1 秒以上かかるようなケースもザラにあり、そこにアクセスのスパイクが重なると 10 秒以上返ってこない、オートスケーリングや手動でのスケーリングが都度必要… と、成り立ってはいるものの「良い」とは言えない状態でした。

一方でオーナーが利用する管理画面とは異なり、ショップページは在庫などの一部のデータを除けば、利用者によって変わる表示も極一部で、ほとんど静的サイトのような作りになっています。そこで、Edge Worker でのコンテンツのキャッシュがスピード面でもインフラ面でも効果が期待できるだろうということで Cloudflare の導入検討を始めました。

vs CloudFront

BASE は基本的に AWS に乗っかって動いているので、 Cloudflare の特に Workers への対抗馬として CloudFront + Lambda@Edge / Functions があります。今回検討していた時点では技術面では以下の点を考慮して、採用を見送りました:

  • 独自ドメインの証明書の問題
    • CloudFront をショップページとして扱う場合、証明書を CloudFront に配置する必要がある
    • 具体的な数値は出せないが、現状のドメインをすべてカバーするためには複数のディストリビューションを管理する必要があり、現実的ではない
  • コンテンツをキャッシュすることを前提とした設計
    • CDN なので当たり前といえばそうだが、あくまでキャッシュされているコンテンツに対する操作という印象
    • BASE では特定の会員のみが利用できるシークレット EC 機能を同じ仕組みの上で提供しているので、キャッシュ前提となるのが扱いづらい
  • KV やストレージの自由度
    • コンテンツのキャッシュ以外にも何かしらのメタ情報はストアできる必要があるだろうという前提があった
    • CloudFront KeyValueStore があるものの、Edge Worker からは読み取り専用
    • Cloudflare の Workers KV と比べるとどうしても取り回しが難しいように感じた

現在ではどうかというと、AWS 側でもこれらを解決するようなソリューションが発表されており、状況が変わっていることに注意が必要です。

aws.amazon.com

Cloudflare を導入する

当然ですが、 Cloudflare が BASE のアプリケーション(Origin と呼ぶ)よりもエンドユーザーに近い位置で動作する必要があります。そのため、これまで Internet → Origin だった経路を、 Internet → Cloudflare → Origin に変更する必要があります。ここで問題になるのがショップページのドメインです。

ショップページのドメインには 2 つのパターンがあります:

  • BASE の管理ドメインのサブドメイン
    • base.shop / base.ec / theshop.jp のようなドメインを BASE が管理
    • これのサブドメインとして、例えば example.base.shop でショップページを配信
  • オーナーの持ち込み独自ドメイン
    • 独自ドメイン App で CNAME を利用して任意のドメインでショップページを配信

前者の場合は特に問題はなく、経路変更を行うだけで済みます。詳細な内容はそれぞれの都合で異なると思うので割愛しますが、Origin に Cloudflare からアクセスされるサブドメインを新たに用意しておき、DNS 設定を切り替えることで経路が次のように変更されます:

[導入以前]
Internet ──▶ example.base.shop(Origin)

[切替後]
Internet ──▶ example.base.shop(Cloudflare) ──▶ from-cloudflare.base.shop(Origin)

from-cloudflare の部分をユーザーが作ったり上書きできないようにしておく必要はありますが、大した問題ではないでしょう。このタイプのドメインは特にメンテナンスを必要とすることなく、無停止で移行していきました。

問題は後者のオーナーの持ち込みドメインです。前述のように CNAME で管理されており、オーナーが設定した DNS では CNAME cname.thebase.in となっています。Cloudflare を通すという理由でこれをすべてのオーナーに変更してもらうのは現実的ではないので、なんらかの方法で設定を維持したままドメインが Cloudflare へ解決される必要があります。ここで登場するのが SSL for SaaS です。

Cloudflare SSL for SaaS

developers.cloudflare.com

qiita.com

ドキュメントの説明にもある通り、カスタムドメインをサポートする機能です。簡単に言ってしまうと、 持ち込みドメインをBASE 管理下にあるドメインと同様に from-cloudflare.* へ転送する機能です。

developers.cloudflare.com

Custom Hostname を作成し、 SSL 証明書が発行された状態で cname.thebase.in が Cloudflare を向くようになると、持ち込みドメインが Cloudflare を通過してから Origin へ到達するようになります。

この切り替えは停止メンテナンスで行ったのですが、メンテナンス中にすべてのアクティブな独自ドメインを Custom Hostname として登録し、証明書の発行を終えるのは現実的ではなかったため、事前に Pre-validation という仕組みを使って移行の準備を進めていました。

developers.cloudflare.com

Pre-validation を使うと、現行のアプリケーションを稼働させたまま Cloudflare 側に SSL 証明書を配置しておくことができます。BASE では HTTP Tokens の方を使用していて、Custom Hostname を作成すると Cloudflare の Dashboard もしくは API から検証用の http_urlhttp_body を得ることができます。

{
  "result": [
    {
      "id": "24c8c68e-bec2-49b6-868e-f06373780630",
      "hostname": "app.example.com",
      // ...
      "ownership_verification_http": {
          "http_url": "http://app.example.com/.well-known/cf-custom-hostname-challenge/24c8c68e-bec2-49b6-868e-f06373780630",
          "http_body": "48b409f6-c886-406b-8cbc-0fbf59983555"
      },
      "created_at": "2020-03-04T20:06:04.117122Z"
    }
  ]
}

https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/hostname-validation/pre-validation/ より引用

ドキュメントでは ownership_verification_http というフィールドでそれが返ってきていますが、このフィールドは少し時間が経過すると使用することができず、そのタイミングまでにレスポンスを準備できない場合は ssl フィールドに含まれる http_urlhttp_body を使う必要がありました。

{
  "result": {
      "ssl": {
          "status": "pending_validation",
          "http_url": "http://app.example.com/.well-known/acme-challenge/uVKCkPGJZt2PewfPPQxSRe8QTDDyq_DzOLZ4AK5T1Vg",
          "http_body": "uVKCkPGJZt2PewfPPQxSRe8QTDDyq_DzOLZ4AK5T1Vg.LNFwUG0womdgXgxKtKU4B6bqUXvBkIouc5BNejjQTh0",
    },
 }
}

定期的にこの URL に Cloudflare からアクセスがあり、 http_body の内容を返すことができれば検証に成功し、Custom Hostname が有効になって SSL 証明書が配置されます。簡単に図にすると以下のようになり、一時的に SSL 証明書が 2 つになります:

                                     [Origin]
               app.example.com
 [Internet]    ────────────────▶   元々ある SSL 証明書 
               ◀────────────────   アプリケーションの動作
             
             
               create apps.example.com
               ◀────────────────   ドメインの登録や更新をトリガーに作成
 [Cloudflare]  /.well-known/acme-challenge/uVK...
               ────────────────▶ 
               ◀────────────────   uVK....
    apps.example.com の SSL 証明書が事前に発行される
 
移行準備中
────────────────────────────────────────────────────────────────────────────
切り替え後
 
 [Internet]                           [Origin]
     │ app.example.com              元々あった SSL 証明書は不要に
     │
     │
     ▼         from-cloudflare.*
 [Cloudflare]  ────────────────▶    アプリケーションの動作
   SSL証明書

切り替えメンテナンスの事前準備として、すべてのドメインを事前に Cloudflare に登録、 SSL 証明書が発行された状態にしておき、実際のメンテナンスでは NS の切り替えのみにすることで、比較的短い時間での切り替えが完了しました。 また、これ以降は SSL 証明書の管理を Cloudflare が行ってくれるようになります。自前での更新が不要になるので、これも利点の一つと言えるでしょう。

切り替え直後のトラブル

過去の動作との不整合で一部個別対応が必要なケースはあったものの、特に大きな問題はなく切り替えを終えることができました。ただ、少し想定外だったことが 2 つあったので、それを書いてこの記事は締めようと思います。

Cloudflare を通った時点でデフォルトのキャッシュが動作する

何も設定をしていない場合、一般的にキャッシュできるとみなされるアセットのキャッシュが自動的に始まります。デフォルトの挙動は以下で確認することができます:

developers.cloudflare.com

殆どの場合は困らないと思いますが、動的に JS を作成したり、ビルド済み JS をアプリケーションから配信している場合は注意が必要です。影響は軽微だったものの一部動的に生成されているものがあり、これが原因で不具合が発生しました。 この設定は Cache Rules を作成することで上書きすることができます。切替時はすべてを Bypass する設定にしておくのが無難に思いました。

developers.cloudflare.com

O2O

管理している Cloudflare より前に別の Cloudflare がいる状態です。Cloudflare のアイコンがオレンジなので Orange-to-Orange というらしいです、可愛いですね。

developers.cloudflare.com

この状態が存在することを認識していないと、「キャッシュをしていないはずなのに Cloudflare がキャッシュを返している」という状態になったときに混乱します、しました。

これは持ち込まれるドメインの DNS が Cloudflare の場合に起こります。CNAME を設定する際に自身の Cloudflare でも Proxy をするという設定があり、これを使うとオーナー側の Cloudflare でコンテンツをキャッシュできるようになります。こうなると、こちらが管理できる範囲よりユーザーに近い場所でキャッシュが起こってしまい、基本的には手が出せない状態になってしまうので、個別でなにかしらの解決をする必要があります。

おわりに

明日は続けて Cloudflare Workers でのコンテンツのキャッシュについて書きます。よろしくお願いします!