🪵

FastlyのRealtime Log Streamingで苦労したあれこれ

に公開

これはなんですか

  • FastlyのRealtime Log Streaming機能を使って試行錯誤した際に知った細かいTipsを書きます。
  • 以下のサンプルが広いユースケースをカバーしているので、これを読み解いて必要な要素をロギングすることをお勧めします。

やりたかったこと

  • ワイルドカード表記のドメインでCDN Serviceをホストしています。
    • ex *.myendpoint.example.com
  • ワイルドカードに当てはまるサブドメインごとにアクセスログを集計したいと考えています。
    • アクセス数
    • データ転送量
    • Fastly Image Optimizer (IO) リクエスト数

前提

FastlyのLog Streamingによるログの記録はベストエフォートです。あくまで参考値である点に注意しましょう。

About Fastly's real-time log streaming features | Fastly Documentation

BigQueryにFastlyのアクセスログを転送する

基本的にはドキュメントの記載通りに設定すれば問題ありません。

About Fastly's real-time log streaming features | Fastly Documentation

TerraformでIAM Impersonation(アカウントに成り代わる権限を振ることで、キーを使わずに認証を通す方式)方式の設定をする際は、次のようにaccount_nameにサービスアカウント名(サービスアカウントを表すメールアドレスのユーザー名部分)を設定します。

Configuring Google IAM service account impersonation to avoid storing keys on Fastly logging | Fastly Documentation
fastly_service_vcl | Resources | fastly/fastly | Terraform | Terraform Registry

  logging_bigquery {
    name               = "logging_to_bq_sample"
    project_id         = "your-gcp-project-id"

    # 'your-service-account-name@your-gcp-project-id.iam.gserviceaccount.com' のユーザー名部分
    account_name       = "your-service-account-name" 

    dataset            = "your_bq_dataset_name"
    table              = "your_bq_table_name"
    format             = "..."
  }

Fastly Image Optimizerによる画像変換の有無を記録する

Fastlyのサポートに問い合わせました。結果、レスポンスヘッダのFastly-Statsを使用すればよいことがわかりました。

Fastly-Stats | Fastly Documentation

レスポンスヘッダはVCL上でresp.http.{NAME}という変数でアクセスできるため、次のようにログフォーマットを設定しました。

vcl_log
  {""fastly_is_transformed_by_io":"} if(resp.http.Fastly-Stats ~ "io=1", "true", "false") {", "}

データ転送量集計用のメトリクスを記録する

こちらもFastlyのサポートに問い合わせました。Websocketを使わない場合は、次の2つの変数を使って上りと下りのデータ転送量を計算できます。

Shielding設定時の注意点

FastlyにはShielding(シールド)という機能があります。

Shielding | Fastly Documentation

FastlyはキャッシュサーバーグループをPOP(Point of Presence)という単位で管理しています。Shieldingでは、指定したPOPがShield POPとなり、世界中のPOP(Edge POP)からのオリジンへのリクエストをShield POPが中継します。これにより、オリジンへのリクエスト回数を減らすことができます。

ただし、Shieldingを有効にするとEdge POPとShield POPの両方で同じVCLが実行されるため、ヘッダの操作やログ設定に注意が必要です。
また、シールド間通信はFastlyの課金対象となるため、ログからデータ転送量を計算する際にはシールド間通信の有無を考慮する必要があります。

ログをEdge POPでのみ記録する

Fastly VCLにはリクエストが通過したキャッシュサーバーの数を記録する変数として、fastly.ff.visits_this_serviceがあります。

fastly.ff.visits_this_service | Fastly Documentation

この変数を使って、vcl_logサブルーチンでログを記録する条件を記述します。

vcl_log | Fastly Documentation

vcl_log
if (fastly.ff.visits_this_service == 0) {
  log "....";
}

こうすることで、シールド間通信が走った際にEdge POPでのみログを記録できます。

なお、Realtime Log StreamingにはCondition(条件)という設定が存在するので、そちらに条件式を書くことでも同様に設定できます。以下はTerraformの設定例です。

  condition {
    name      = "log_only_on_edge"
    statement = "fastly.ff.visits_this_service == 0"
    type      = "RESPONSE"
  }

  logging_bigquery {
    name               = "log_to_bq"
    account_name       = "{{サービスアカウント名}}"
    project_id         = "{{Google CloudプロジェクトID}}"
    dataset            = "{{BQデータセット名}}"
    table              = "{{BQテーブル名}}"
    response_condition = "log_only_on_edge"
    format             = "{{ログフォーマット}}"
  }

クライアントへのレスポンスから特定のヘッダを落とす

Shieldingが有効な場合、VCLはEdge POPとShield POPの両方で動作するため、vcl_deliverサブルーチンで単純にunset resp.http.{NAME}と書いてレスポンスヘッダを落とすと、Shield POPの時点でレスポンスヘッダが落ちてしまいます。Edge POPはShield POPのレスポンスを元に処理をするため、resp.http.{NAME}が使えなくなります。

レスポンスヘッダの値を取り回しつつ、クライアントへのレスポンスからのみ除外するには、fastly.ff.visits_this_service変数を使ってEdge POPの場合のみレスポンスヘッダを削除します。

vcl_deliver
# resp.http.log-origin の値を取り回した後にレスポンスから落とす
if (fastly.ff.visits_this_service == 0) {
  unset resp.http.log-origin;
}

シールド間通信の有無を記録する

Edge POPとShield POP間のシールド間通信のデータ転送量はFastlyの課金対象です。したがって、ログからデータ転送量を集計する際には、シールド間通信の有無も記録する必要があります。シールド間通信の記録方法は、冒頭で紹介したサンプルがカバーしています。

Copy of Comprehensive logging, one value per line - Fastly Fiddle

このサンプルは様々なログを記録しているため、シールド間通信の記録に必要な要素のみ確認してみます。シールド間通信の有無は fastly_is_shield フィールドで記録されています。vcl_log サブルーチン内の該当箇所を抜粋します。

vcl_log
  {""fastly_is_shield":"} if(req.http.log-origin:shield == server.datacenter, "true", "false") {", "}

このフィールドは、「リクエストがEdge POPを経由せず、直接Shield POPに到達した」場合にtrueとなります。この判定には、log-origin:shieldというカスタムヘッダとserver.datacenter変数を使っています。server.datacenterはそのリクエストを処理するPOPのIDを表現します。

log-origin:shieldカスタムヘッダがどのように定義されるかを追いかけます。vcl_deliverサブルーチンを見ると、次のようにlog-originレスポンスヘッダの値をlog-originリクエストヘッダにコピーしています。

vcl_deliver
if (resp.http.log-origin) {
  set req.http.log-origin = resp.http.log-origin;
}

先ほど出てきたlog-origin:shieldという表記は、:演算子を使ってlog-originヘッダのshieldサブフィールドにアクセスしていることを意味しています。したがって、log-originをコピーすればサブフィールドであるshieldの値もコピーできます。

また、Fastly VCLではオリジンへのリクエスト以降、リクエストヘッダ(req.http.{NAME})の値は意味を持ちません。その性質を利用して、後続のサブルーチン(vcl_deliver,vcl_log)で変数のように使うことが多くあります。

resp.http.log-originレスポンスヘッダを追いかけると、vcl_fetchサブルーチンでberesp.http.log-originにアクセスしていることがわかります。

vcl_fetch
if (req.backend.is_origin) {
  set beresp.http.log-origin:shield = server.datacenter;
}

beresp変数は後続のバックエンドから得たレスポンスを格納する変数です。vcl_fetchサブルーチンでのみ書き込みが可能で、その結果は後続のvcl_delivervcl_logサブルーチンでresp変数として利用できます。
Variables in VCL | Fastly Documentation

vcl_fetchサブルーチンの処理を見ると、req.backend.is_origin変数trueの場合にserver.datacenter変数の値を詰めています。これは、オリジンへのリクエストを行った場合に、VCLを実行しているPOPのIDを記録することを意味しています。

ここまでの内容を踏まえて、シールド間通信がある場合とない場合でfastly_is_shieldの値がどうなるかを考えます。まず、リクエストを直接Shield POPで受け取ってオリジンまでリクエストを行う場合の処理順序を見ます。

  1. Shield POP上でVCLにしたがってリクエストを処理し、vcl_fetchサブルーチンに到達する。
  2. Shield POPはオリジンをバックエンドに持つため、if(req.backend.is_origin) {...}内の処理を実行し、log-origin:shieldヘッダにserver.datacenter変数の値を詰める。
  3. log-originヘッダは値を持つので、Shield POPはvcl_deliverサブルーチンのif (resp.http.log-origin) {...}を実行し、リクエストヘッダにlog-originヘッダを設定する。
  4. vcl_logサブルーチンでlog-origin:shieldリクエストヘッダの値を比較してログとして記録する。Shield POP上でのみ処理を行っているためserver.datacenterの値は一致し、fastly_is_shieldtrueとなる。

次に、Edge POPからShield POPを経由してオリジンまでリクエストを行う場合の処理順序を見てみます。

  1. Edge POP上でVCLにしたがってリクエストを処理する。
  2. Edge POPからShield POPにリクエストを行う。
    • Shield POPでも同様にVCLにしたがってリクエストを処理する。前述の通り、Shield POPではresp.http.log-originが値を持つので、Edge POPへのレスポンスにはlog-originヘッダが含まれる。
  3. Edge POP上でvcl_fetchサブルーチンに到達する。
  4. Edge POPのバックエンドはShield POPであるため、Edge POPはif(req.backend.is_origin) {...}を実行せず、Shield POPが返したberesp.http.log-originの値を保持する。
  5. log-originヘッダは値を持つので、Edge POPはvcl_deliverサブルーチンでif (resp.http.log-origin) {...}を実行し、リクエストヘッダにlog-originヘッダを設定する。
  6. vcl_logサブルーチンでlog-origin:shieldリクエストヘッダの値を比較してログとして記録する。Edge POPとShield POPは異なるserver.datacenterの値を持つのでfastly_is_shieldfalseとなる。

このように、それぞれの場合に応じてfastly_is_shieldが適切な値になることがわかります。

トラブルシュート用のAPIエンドポイント

Log Streamingがうまく動いているかどうかを確認するためのAPIがあります。トラブルシュートする際に便利です。

Setting up remote log streaming | Fastly Documentation

参考

そのほか、調査した中で読んだページを挙げます。

Discussion