【Fastly】VCL Service + BigQuery ログ構成を terraform で管理する
はじめに
Fastly の VCL Service は、デフォルトの状態だとアクセスログを出力してくれないため、外部にログをエクスポートする必要があります。
エクスポート先は HTTP や SFTP など汎用的なエンドポイントを指定することもできますし、S3 や Google BigQuery などマネージドサービスの選択肢が豊富に用意されているため、システムの構成に合わせて選択することも可能です。
本記事では、ログを Google BigQuery にエクスポートする VCL Service の構成を terraform で実装する方法を、サンプルコードを交えながら説明します。
本記事に記載しているサンプルコードは以下のリポジトリに載っていますので、参考にしていただければと思います。
BigQuery ログ構成の terraform 実装
本記事では、以下の手順で terraform を構成していきます。
- provider など前準備
- Fastly サービスアカウントの権限借用
- BigQuery テーブルの用意
- Fastly VCL Service の作成、Bigquery ログエンドポイントの追加
- サブドメインの払い出し
なお、以下のリソースをすでに用意していることを前提とします。
リソース | 説明 |
---|---|
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 "gcp_project_id" {
type = string
}
variable "gcp_project_region" {
type = string
}
variable "fastly_api_key" {
type = string
}
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 では、サービスアカウントの権限借用を利用する以下の方法を推奨しています。
- 自身の Google Cloud プロジェクトで、対象のデータベースに対してログ書き込み権限を持つサービスアカウント
fastly-bigquery-writer
を作成する - Fastly が用意したサービスアカウント
fastly-logging@datalog-bulleit-9e86.iam.gserviceaccount.com
に対して、fastly-bigquery-writer
での「サービスアカウントトークン作成者」権限(roles/iam.serviceAccountTokenCreator
)を付与する
これによって、Fastly 側に生の認証トークンを扱うことを避けながら、必要なログ書き込み権限を付与することができます。
terraform で実装すると、以下のようになります。
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
も同時に作成します。
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
[
{
"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_log
の access
ブロック、すなわちデータセットに対する権限付与です。
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
テーブルにログをエクスポートする
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
に合わせたログフォーマットです。
{
"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"
とする必要があります。
詳細な情報は、以下の公式ドキュメントをご覧ください。
ここまでの 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 のテーブルに書き込まれるようになります。
詳細な情報は、以下の公式ドキュメントをご覧ください。
まとめ
ログを Google BigQuery にエクスポートする VCL Service の構成を terraform で実装する方法を説明しました。
以下の3点に注意しながら、サンプルコードを利用して Fastly VCL + BigQuery 構成を試作してみてください。
- BigQuery テーブルの OWNER 権限を適切に付与する
- 独特なログフォーマットの記法をうまく乗りこなし、フォーマットから変換される json が BigQuery テーブルのスキーマと一致するように調整する
- 払い出すサブドメインの CNAME として指定するドメインを Fastly コンソールから探す
付録
BigQuery データセットで OWNER 権限を設定し忘れた時のリカバリー方法
以下の Google Cloud ドキュメントに従って、Google Cloud コンソール上で terraform を実行するアカウントに対してデータベースに対するアクセス権限を付与してください。
Discussion