BK クラウド / システム 記事
JA
English ENDeutsch DEEspañol ES中文 ZHFrançais FRPortuguês (Brasil) PT-BR日本語 JA
つながりましょう ↗
← 実践ノートに戻る

静的サイトにも息づかいを。AWSで作る、プライバシーに配慮したアクセス分析

静的サイトは、うれしいくらい平穏です。パッチを待つアプリサーバーも、枯渇するDB接続プールも、常時動かす公開APIもありません。ブラウザーがファイルを求め、CDNが返す。それだけです。

そこから疑問が生まれました。このシンプルさを保ったまま、どこで読まれ、アクセスがどう変化しているかを表示できるだろうか。動的アプリにしたり、非公開にすべき情報を出したりせずに。

答えは、CloudFrontアクセスログ、CloudWatch、Lambda、EventBridge、S3上のJSONひとつを使う小さなパイプラインでした。訪問者に見せるのは世界地図、国別集計、人気ページ、リクエストの推移。生ログ、IP、User-Agent、個人の閲覧履歴は渡しません。

サイトは静的なままです。データは別の場所で準備され、ほかの静的ファイルと同じように届きます。

公開リクエストの経路

公開側の経路は、意図的に短くしています。

  1. 訪問者がAmazon CloudFront経由でサイトを開きます。
  2. CloudFrontが、非公開S3をオリジンとするキャッシュ済みファイルを返します。
  3. その後、ブラウザーが content/public-visitors.json を、ほかの静的アセットと同じように取得します。
  4. ブラウザー側のJavaScriptが匿名化済みのデータから地図とグラフを描画します。

ブラウザーはLambdaもCloudWatchも呼びません。AWS認証情報も持たず、別の期間や詳細な記録を要求することもできません。費用のかかる処理と機微な処理は、公開リクエストの経路から外しています。

裏側のデータ経路は、次のようになります。

CloudFront → CloudWatch Logs → scheduled Lambda → aggregated JSON → S3 → CloudFront → browser

この分離が設計の要です。運用側では詳細なアクセスデータを扱えますが、公開側へ出すのは、見せると決めた小さな範囲だけです。

一度集めて、公開は絞る

CloudFront標準ログv2は、選んだ項目をCloudWatch Logsへ配信します。調査に必要なリクエストやステータス情報があっても、すべてを公開ダッシュボードに出す必要はありません。

公開用の関数で、次の集計に絞ります。

  • 成功したHTMLページリクエストの国コード別集計
  • リクエストの多いHTMLパス
  • 集計済みHTTPステータスメトリクスから求めた1分あたりのリクエスト数
  • 更新時刻と各対象期間のラベル

生成ファイルには送信元IP、市区町村、Cookie、User-Agent、個人の閲覧経路を特定する並びを含めません。「誰が来たか」ではなく、「どんなアクセスがあったか」を示します。

だから、ここで使う言葉は ページ表示数であって、 人数ではありません。1人でも多くのリクエストを送ります。検索クローラー、リンクプレビュー、監視などの自動処理も混ざります。表示しているのはインフラのシグナルで、個人の識別情報ではありません。

間にCloudWatchを置く理由

CloudWatchでは、同じ活動を二つの形で使えます。Logs Insightsで構造化ログから国・ページ別集計を取得し、メトリクスフィルターで2xx・3xx・4xx・5xxを数えます。推移の計算で毎回全ログを読み直す必要がありません。

公開データには、次の表示を用意しました。

  • 過去24時間、7日、30日の国別集計
  • 24時間、7日、30日、12カ月のリクエスト推移
  • 長い期間ほど集計単位を粗くして、データを小さく保つ

CloudFrontの標準ログはリアルタイム配信ではありません。通常は1時間以内に届きますが、さらに遅れる場合もあります。正直に過去を振り返る用途には向いていますが、ライブの人数カウンターには向きません。

公開APIではなく、定期スナップショット

EventBridgeのルールが6時間ごとにLambdaを起動します。関数は範囲を限定したクエリとメトリクス取得を行い、まとめたデータでS3のひとつのキーを上書きします。 content/public-visitors.json.

6時間という間隔は、意図的に控えめです。アクセス分析に秒単位の鮮度は必要ありません。更新のたびにログを読むので、短くすると、実用上の利点が少ないまま実行数と費用が増えます。

JSONには再検証を意識したキャッシュヘッダーを付けます。CloudFrontがS3を保護しつつ、新しいスナップショットを見つけられます。統計の更新にサイト全体のデプロイや一括キャッシュ無効化は要りません。

この方式は失敗時の挙動も穏やかです。クエリやLambdaが失敗しても、前の静的データは残ります。統計が古くなることはあっても、サイトは動き続け、閲覧リクエストが集計を待つことはありません。

最小権限で影響範囲を小さく

公開用Lambdaは専用IAMロールを使い、関係する権限を絞っています。

  • 指定したアクセスロググループにだけクエリを実行し、結果を取得する
  • リクエスト推移に必要なCloudWatchメトリクスを読む
  • 書き込み先は content/public-visitors.json というサイトバケット内のキーだけ
  • 自分の実行ログを書き込む

任意のサイトファイルをデプロイしたり、CloudFrontを変更したり、バケットを公開したりはできません。EventBridgeにも、特定ルールからこの関数を起動する権限だけを付けます。

サイトのバケット自体は非公開です。CloudFrontはOACでS3へ署名付きリクエストを送り、閲覧者がCDNを迂回してS3を公開サーバーのように使うのを防ぎます。

地図の精度を、あえて抑える

詳しければよい公開ダッシュボードになるとは限りません。市単位の印は目を引きますが、少数の件数、正確な時刻、既知の人物を組み合わせると特定につながりかねません。国別集計で、個人の居住地を証明したふりをせず、サイトの広がりを伝えられます。

地図は国ごとの濃淡と順位で示し、すぐ下で「リクエストは人数ではない」と説明します。個別イベントまで掘り下げる機能はありません。

美しいグラフほど、確実そうに見えるので説明が重要です。IPから推定した国は、エッジ基盤が観測した情報にすぎません。VPN、社内ネットワーク、携帯回線、クローラー、キャッシュされたプレビューなどが、実際の人との対応を複雑にします。

費用を決めるのは、見た目よりスキャン量

地図とグラフは配信済みのコードでブラウザーが描くので、複雑な見た目がサーバー描画費用になるわけではありません。主な運用費は、ログの収集と保持、メトリクスの取り込み、定期クエリのスキャンです。

そこで、費用対策は見た目ではなく設計で行います。

  • ログ保持を30日にして、無制限の増加を防ぐ
  • 関数の実行を1日4回にする
  • クエリの期間と項目を必要な範囲に絞る
  • 長期間のグラフではメトリクスの間隔を広げる
  • 公開JSONを、生ログよりはるかに小さくする

この規模では、Lambda実行と小さなJSONひとつのS3保存費はわずかです。アクセスや保持量が大きくなったら、Logs Insightsのスキャン量を注視します。

作って学んだこと

静的でも、状態は見える。 同期的なリクエスト経路に入れなくても、可観測性をサイトの横に置けます。

集計は、セキュリティの境界になる。 運用ログを公開データへ変換する処理は、明示的に設計し、レビューし、一方向にします。

偽りのリアルタイムより、十分な鮮度を。 6時間のスナップショットは標準ログの遅延にも合い、無駄な処理を避けられます。

失敗しても、前の結果を残す。 分析サービスの不調でホームが落ちるより、少し古い集計を表示する方がよいでしょう。

限界は、グラフのそばで説明する。 公開グラフには、ボット、繰り返しのリクエスト、国の推定、アクセスと人数の違いを説明する必要があります。

別の設計を選ぶなら

この方式は、ほどよいアクセス量の個人サイトと、決まった少数の表示に合います。自由なクエリ、認証付き画面、秒単位の更新、大量の多次元データが必要なら、APIや専用分析ストア、可視化サービスを検討します。

このポートフォリオにそれらを足しても、今はない問題を解くだけです。定期関数ひとつと静的データひとつで、攻撃対象の少なさ、予測しやすい挙動、理解しやすい構成を保てます。

この障害から学んだこと

このダッシュボードで一番大切なのは、世界地図ではなく、その裏にある境界です。

詳細なリクエスト情報は運用環境に残し、最小権限の処理が定期的に匿名集計へ変換します。サイトには小さな静的スナップショットだけを渡し、ブラウザーがそれを読み取れる形にします。

静的サイトに息づかいを与えても、脆弱な動的システムの中心を作る必要はありません。

参考資料