📚

【Fastly】VCL Service + BigQuery ログ構成を terraform で管理する

2025/01/18に公開

はじめに

Fastly の VCL Service は、デフォルトの状態だとアクセスログを出力してくれないため、外部にログをエクスポートする必要があります。
エクスポート先は HTTP や SFTP など汎用的なエンドポイントを指定することもできますし、S3 や Google BigQuery などマネージドサービスの選択肢が豊富に用意されているため、システムの構成に合わせて選択することも可能です。

本記事では、ログを Google BigQuery にエクスポートする VCL Service の構成を terraform で実装する方法を、サンプルコードを交えながら説明します。
本記事に記載しているサンプルコードは以下のリポジトリに載っていますので、参考にしていただければと思います。
https://github.com/torafugu2929/terraform_fastly_bigquery_sample

BigQuery ログ構成の terraform 実装

本記事では、以下の手順で terraform を構成していきます。

  1. provider など前準備
  2. Fastly サービスアカウントの権限借用
  3. BigQuery テーブルの用意
  4. Fastly VCL Service の作成、Bigquery ログエンドポイントの追加
  5. サブドメインの払い出し

なお、以下のリソースをすでに用意していることを前提とします。

リソース 説明
Google Cloud プロジェクト BigQuery データベースを作成する対象のプロジェクト。BigQuery API を有効化する必要あり
Google Cloud ユーザーアカウント/サービスアカウント 上記の Google Cloud プロジェクトに対してリソース変更の権限を持つアカウント。 terraform コマンドの実行に使う。ユーザーアカウントを用いてローカルで terraform コマンドを実行する場合、gcloud auth login でログインしておく必要がある
Fastly アカウント VCL サービスを作成するアカウント
Fastly API トークン Fastly アカウントによって作成されたトークン。Fastly を terraform で扱うのに必要
ドメイン Fastly VCL Service のエントリーポイントとして使用するドメインの Apex ドメイン(example.com など)。今回はサブドメイン(foo.example.com)を DNS レコードに登録することを想定

ディレクトリ構成

今回は、以下のようなディレクトリ構成で作成します。
ベストプラクティスに従った構成ではないため、より良い形があれば教えていただけると嬉しいです。

fastly-bigquery
├── variable.tf
├── terraform.tf
├── google_cloud.tf
├── bigquery.tf
├── fastly.tf
├── vcl
|   └── main.vcl
└── bigquery_format
    └── main.json

provider など前準備

機密情報などを variable.tf で受け取り、以下のように provider の設定を行います。

variable.tf
variable "gcp_project_id" {
    type = string
}

variable "gcp_project_region" {
    type = string
}

variable "fastly_api_key" {
    type = string
}
terraform.tf
terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 6.15.0"
    }
    fastly = {
      source  = "fastly/fastly"
      version = "~> 5.13.0"
    }
  }
}

provider "google" {
  project = var.gcp_project_id
  region  = var.gcp_project_region
}

provider "fastly" {
  api_key = var.fastly_api_key
}

Fastly サービスアカウントの権限借用

BigQuery データセットや Fastly のログエクスポートエンドポイントを作成する前に、Fastly 側にログ書き込み権限を付与します。Fastly から自身の Google Cloud プロジェクトに存在する BigQuery テーブルに対してログを送信するためです。
Fastly では、サービスアカウントの権限借用を利用する以下の方法を推奨しています。

  1. 自身の Google Cloud プロジェクトで、対象のデータベースに対してログ書き込み権限を持つサービスアカウント fastly-bigquery-writer を作成する
  2. Fastly が用意したサービスアカウント fastly-logging@datalog-bulleit-9e86.iam.gserviceaccount.com に対して、fastly-bigquery-writer での「サービスアカウントトークン作成者」権限(roles/iam.serviceAccountTokenCreator)を付与する

これによって、Fastly 側に生の認証トークンを扱うことを避けながら、必要なログ書き込み権限を付与することができます。

terraform で実装すると、以下のようになります。

service_account.tf
resource "google_service_account" "fastly_bigquery_writer" {
  project      = var.gcp_project_id
  account_id   = "fastly-bigquery-writer"
  display_name = "BigQuery Writer for Fastly"
}

resource "google_service_account_iam_member" "fastly_logging_token_creator" {
  service_account_id = google_service_account.fastly_bigquery_writer.name
  role               = "roles/iam.serviceAccountTokenCreator"
  member             = "serviceAccount:fastly-logging@datalog-bulleit-9e86.iam.gserviceaccount.com"
}

BigQuery テーブルの用意

次に、データをエクスポートする先の BigQuery テーブル sample_vcl を用意します。
BigQuery テーブルは BigQuery データセットに含まれるリソースなため、BigQuery データセット fastly_access_log も同時に作成します。

bigquery.tf
resource "google_bigquery_dataset" "fastly_access_log" {
  dataset_id = "fastly_access_log"
  project    = var.gcp_project_id

  default_partition_expiration_ms = 90 * 24 * 60 * 60 * 1000 # = 90日
  location                        = "asia-northeast1"

  access {
    role          = "OWNER"
    special_group = "projectOwners"
  }
  access {
    role          = "WRITER"
    user_by_email = google_service_account.fastly_bigquery_writer.email
  }
}

resource "google_bigquery_table" "sample_vcl" {
  dataset_id = google_bigquery_dataset.fastly_access_log.dataset_id
  project    = var.gcp_project_id
  table_id   = "sample_vcl"

  time_partitioning {
    field = "timestamp"
    type  = "HOUR"
  }

  schema = file("${path.module}/logging_bigquery/schema.json")
}

BigQuery スキーマファイル logging_bigquery/schema.json の例をトグルボタンに載せました。今回は、後述する VCL Service のログフォーマット logging_bigquery/log_format.json に合わせています。

logging_bigquery/schema.json
logging_bigquery/schema.json
[
    {
        "name": "timestamp",
        "type": "TIMESTAMP",
        "mode": "NULLABLE"
    },
    {
        "name": "client_ip",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "geo_country",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "geo_city",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "host",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "url",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "request_method",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "request_protocol",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "request_referer",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "request_user_agent",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "response_state",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "response_status",
        "type": "INTEGER",
        "mode": "NULLABLE"
    },
    {
        "name": "response_reason",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "response_body_size",
        "type": "INTEGER",
        "mode": "NULLABLE"
    },
    {
        "name": "fastly_server",
        "type": "STRING",
        "mode": "NULLABLE"
    },
    {
        "name": "fastly_is_edge",
        "type": "BOOLEAN",
        "mode": "NULLABLE"
    }
]
  

注意点

気をつける必要があるのは、データセット fastly_access_logaccess ブロック、すなわちデータセットに対する権限付与です。

  access {
    role          = "OWNER"
    special_group = "projectOwners"
  }
  access {
    role          = "WRITER"
    user_by_email = google_service_account.fastly_bigquery_writer.email
  }

先ほど権限借用に使用したサービスアカウント fastly_bigquery_writer に WRITER 権限を付与するのはもちろん必要なのですが、加えて terraform 実行者に対する OWNER 権限 も付与する必要があります(この例では、var.gcp_project_id でオーナー権限を持つ全ユーザーアカウントに対して OWNER 権限が与えられます)。
この権限を必要なアカウントに付与しないと、terraform 適用時に fastly_access_log の作成自体は可能なのですが、fastly_access_log に対する変更・削除ができなくなります(=後から terraform 経由で権限を足すことも不可能)。筆者は見事にハマりました。

後述する付録ページにて、OWNER 権限を設定し忘れた時のリカバリー方法を記載しています。困った場合はこちらを参照してください。

Fastly VCL Service の作成、Bigquery ログエンドポイントの追加

いよいよ Fastly VCL Service サービスを作成し、Bigquery ログエンドポイントを追加します。
以下の terraform では、次のような内容を実装しています。

  • foo.example.com というドメインをエントリーポイントとする。TLS 証明書も Fastly 側で発行する
  • バックエンドバケットとして Cloud Storage の example-backend.storage.googleapis.com を利用する
  • VCL Service で行われる具体的な処理は、カスタム VCL として vcl/main.vcl ですべて実装する
  • 前の項目で作成した sample_vcl テーブルにログをエクスポートする
fastly.tf
locals {
  domain_name     = "foo.example.com"
  backend_name    = "example-backend"
  backend_address = "example-backend.storage.googleapis.com"
}

resource "fastly_tls_subscription" "foo_example_com" {
  domains               = [local.domain_name]
  certificate_authority = "certainly"
}

resource "fastly_service_vcl" "example" {
  name = "example"

  domain {
    name = local.domain_name
  }

  backend {
    address = local.backend_address
    name    = local.backend_name

    port              = 443
    override_host     = local.backend_address
    request_condition = ""
    ssl_cert_hostname = "storage.googleapis.com"
    ssl_sni_hostname  = "storage.googleapis.com"
  }

  vcl {
    name    = "vcl_main"
    content = file("${path.module}/vcl/main.vcl")
    main    = true
  }

  logging_bigquery {
    name         = "logging_bigquery_main"
    project_id   = var.gcp_project_id
    dataset      = google_bigquery_dataset.fastly_access_log.dataset_id
    table        = google_bigquery_table.sample_vcl.table_id
    account_name = google_service_account.fastly_bigquery_writer.account_id
    format       = file("${path.module}/logging_bigquery/log_format.json")
  }
}

logging_bigquery.account_name には、データセット fastly_access_log の WRITER 権限を持ち、権限借用に使用したサービスアカウント fastly_bigquery_writer のアカウント ID を指定します。

ログのフォーマットについて

BigQuery に送信するログフォーマットは、スキーマの階層と型と完全一致させた json である必要があります。
VCL Service の logging_bigquery.format で指定するフォーマットももちろん完全一致する必要があるのですが、かなり記法が独特なため注意が必要です。

例えば、以下は BigQuery テーブルのスキーマ logging_bigquery/schema.json に合わせたログフォーマットです。

logging_bigquery/log_format.json
{
    "timestamp": "%{strftime(\{"%Y-%m-%dT%H:%M:%S"\}, time.start) "." time.start.usec_frac}V",
    "client_ip": "%{req.http.Fastly-Client-IP}V",
    "geo_country": "%{client.geo.country_name}V",
    "geo_city": "%{client.geo.city}V",
    "host": "%{if(req.http.Fastly-Orig-Host, req.http.Fastly-Orig-Host, req.http.Host)}V",
    "url": "%{json.escape(req.url)}V",
    "request_method": "%{json.escape(req.method)}V",
    "request_protocol": "%{json.escape(req.proto)}V",
    "request_referer": "%{json.escape(req.http.referer)}V",
    "request_user_agent": "%{json.escape(req.http.User-Agent)}V",
    "response_state": "%{json.escape(fastly_info.state)}V",
    "response_status": %{resp.status}V,
    "response_reason": %{if(resp.response, "%22"+json.escape(resp.response)+"%22", "null")}V,
    "response_body_size": %{resp.body_bytes_written}V,
    "fastly_server": "%{json.escape(server.identity)}V",
    "fastly_is_edge": %{if(fastly.ff.visits_this_service == 0, "true", "false")}V
}

このログフォーマットは VCL 内のログ出力コードに変換され、VCL のサブルーチンの1つ vcl_log に埋め込まれます。
このとき、%{foo}Vは「記述 foo を VCL として評価した結果を string 文字列として出力したもの」として変換されます。

例えば fastly_is_edge の値

%{if(fastly.ff.visits_this_service == 0, "true", "false")}V

は、fastly.ff.visits_this_service という変数が vcl_log 内で 0 であれば true、存在しなければ false と変換されます。
これらはダブルクォートがついていないので、「fastly_is_edge に boolean 値が指定されている」と BigQuery 側で解釈されることとなり、適切なフォーマットになります。

一方、string 型に値にはダブルクォートがついて欲しいので、%{foo}V の上からダブルクォートをつけて "%{foo}V" とする必要があります。

詳細な情報は、以下の公式ドキュメントをご覧ください。
https://www.fastly.com/documentation/guides/integrations/logging/#generating-logs-automatically-in-vcl

ここまでの VCL の記述が終わったら、terraform の適用を行い、リソースを作成を行いましょう。

サブドメインの払い出し

本記事のテーマから少し逸れますが、リソースを作成した後の最後の工程として、利用するサブドメイン foo.example.com の向き先を Fastly に向ける方法についても記載します。

example.com を払い出している DNS サービスで foo.example.com の CNAME レコードを作成すれば良いのですが、発行している TLS 証明書の種類によって CNAME の指定先が異なります。

どのドメインを指定すれば良いかは、Fastly コンソールで確かめることができます。
「Security -> TLS Management Domains」を選択し、対象のドメインの TLS 証明書をクリックすると以下のような画面が出てきます。対象のドメインについて赤字で囲ってある部分のリンクを選択すると、CNAME に指定するべきドメインが出てくるので、これを CNAME レコードとして登録します。

CNAME レコードを登録することで、晴れて foo.example.com へのアクセスは Fastly VCL Service に飛び、アクセスログが BigQuery のテーブルに書き込まれるようになります。

詳細な情報は、以下の公式ドキュメントをご覧ください。
https://docs.fastly.com/ja/guides/working-with-cname-records-and-your-dns-provider

まとめ

ログを Google BigQuery にエクスポートする VCL Service の構成を terraform で実装する方法を説明しました。
以下の3点に注意しながら、サンプルコードを利用して Fastly VCL + BigQuery 構成を試作してみてください。

  • BigQuery テーブルの OWNER 権限を適切に付与する
  • 独特なログフォーマットの記法をうまく乗りこなし、フォーマットから変換される json が BigQuery テーブルのスキーマと一致するように調整する
  • 払い出すサブドメインの CNAME として指定するドメインを Fastly コンソールから探す

付録

BigQuery データセットで OWNER 権限を設定し忘れた時のリカバリー方法

以下の Google Cloud ドキュメントに従って、Google Cloud コンソール上で terraform を実行するアカウントに対してデータベースに対するアクセス権限を付与してください。
https://cloud.google.com/bigquery/docs/control-access-to-resources-iam?hl=ja#grant_access_to_a_dataset

Discussion