ブログに戻る

フォロー&ご登録

Terraform を使った Fastly サービスの設定方法

Mark McDonnell

Developer Relations、Staff Software Engineer, Fastly

This post was written with version 0.30.0 of the Fastly Terraform provider. This is no longer the latest release so please refer to the documentation.

必要に応じてコントロールパネルやコマンドラインインターフェイス、API などを通じて Fastly サービスの設定を行う一方で、アプリケーションコードの進化と並行して設定内容を保存し、追跡できるようにすることも大切です。エッジで構築する機会が増えるに従い、アプリケーションやインフラストラクチャへ変更をデプロイするのと同じように、エッジロジックをデプロイすることがより重要になります。クラウドサービスによってインフラストラクチャとソフトウェアの境界線が曖昧になる中、複数のベンダーのサービスに対するコードや設定の変更を1つのバージョンにまとめてデプロイする方法がいっそう必要となります。Fastly サービスにも当てはまるこの原則を利用することを Infrastructure as Code (IaC) と言います。

以前、人気ツールの Terraform を使って継続的デプロイのパイプラインを構築する方法をご紹介しましたが、Fastly ではバージョンレスリソースWAF、サードパーティのログエンドポイント、TLSCompute@Edge のサポートを強化すべく、機能の反復的な改善を続けてきました。今回は、Terraform を使用して Fastly サービスの設定、管理、デプロイを行う際の全体的なプロセスとベストプラクティスをご紹介します。

まず、Terraform を使ってシンプルな VCL サービスを作成します。このサービスは、人気のリクエスト/レスポンスのテストサイト https://httpbin.org の前に透過型プロキシとしてまず配置されます (つまり httpbin にプロキシされる前はリクエストオブジェクトへの変更は行われません)。その後、プログラムロジックをエッジで実行するための変更を行います。

今回のデモでは、以下のステップを行います。

  • 登録 : Fastly アカウントを作成し、API キーを生成します。

  • 記述 : Terraform コードで要件を定義します。

  • 実行 : Terraform の CLI を使って要件を計画し、実行します。

  • 検証 : Web ブラウザを開き、サービスが正常に動作していることを確認します。

  • 変更 : 定義されたリソースに変更を加え、新たなプランを生成して再度実行します。

これらのステップを頭の中でたどれるように、以下の図で全体的なワークフローをわかりやすくまとめています。

Fastly のアカウントを作成する

まずは、Fastly のアカウントを作成します。アカウントの作成は無料で、最大50ドル分のトラフィックを無料で試すことができます。アカウント作成後、ログインして Fastly API トークンを作成します。これは、個々のユーザーに関連づけられた固有の認証情報です。ユーザーに代わって Fastly API と通信できるようにするために、このトークンを Terraform プロバイダーに渡す必要があります。 

Terraform 設定を記述する

次に、お好きなコードエディターで新しい Terraform ファイルを作成します。ファイルを main.tf と名付け、以下のコードを加えます。

terraform {
  required_providers {
    fastly = {
      source  = "fastly/fastly"
      version = "~> 0.30"
    }
  }
}

resource "fastly_service_v1" "test_service" { 
 name = "An Example Service"

  domain {
    name = "<name>.global.ssl.fastly.net"
  }

  backend {
    address = "httpbin.org"
    name     = "My Test Backend"
  }

  force_destroy = true
}

output "active" {
  value = fastly_service_v1.test_service.active_version
}

今回はセットアップを素早く済ますためにコードを1つのファイルにまとめましたが、プロジェクトが発展するのにつれて、コードを別々のファイルに分割した方が管理しやすくなります。

上記のコードは、さまざまなブロックやコンテナを name { ... } で示してコンテンツを整理することができる HashiCorp Configuration Language (HCL) で書かれています。上記の例では3つのトップレベルのブロックがあります。

1. terraform
2. resource
3. output

terraform ブロックは、使用する Terraform プロバイダーのリストを定義します。今回、私たちが使用するのは Fastly Terraform プロバイダー です。 

ヒント : バージョン制約について詳しくは、Terraform ドキュメントをご覧ください。

resource ブロックは、1つ以上のインフラオブジェクトを示します。上記の設定では、Terraform プロバイダーによって提供され、新しい VCL サービスの作成に必要な fastly_service_v1 という特定のリソース (「test_service」と表示される) が定義されています。このリソースには2つのブロック domainbackend がネストされています。これらのブロックは、どのドメイン名に対して VCL サービスがリクエストを処理するべきか、そして最終的にどのバックエンドサービスがリクエストを処理すべきか決定するために必要になります。Terraform の使用の有無にかかわらず、すべての Fastly サービスでこの2つの設定が最低限必要です。

このデモでは、domain 値の <name> 部分を固有の名前に変更します。変更しない場合、ドメイン名が他のサービスによってすでに使用されているというエラーが表示される可能性があります。

output ブロックでは、インフラストラクチャのデプロイ完了後に Terraform が表示する値を定義することができます。例えば、バージョン番号など、デプロイによって変わる値がよく使われます。

fastly_service_v1 リソースによって active_version 属性が生成され、サービスが正常に作成・有効化された後にその値が表示されます。Terraform コード内の active と表示された output ブロックに割り当てられた値は、アクティブなバージョンへのアクセスを可能にする HCL のプログラム表現です。

以下のシンタックスでリソース内の属性値を参照することができます。 

<resource_type>.<resource_label>.<resource_attribute>

特定の Fastly リソースに関する属性はすべて、そのリソースの Fastly Terraform プロバイダーのドキュメントで確認できます。

Terraform を実行してプランを生成する

Terraform の設定が完了したら、Terraform CLI をインストールします。インストール後、ターミナルで以下を実行すると利用可能なコマンドがすべて表示されます。

$ terraform -help

そのコマンドの一つである init は、現在使用中の作業ディレクトリの Terraform プロジェクトを初期化します。先ほど main.tf ファイルを作成した際に使用したディレクトリで以下を実行します。

$ terraform init

この初期化ステップによって、Terraform は required_providers ブロックで指定されたすべてのプロバイダーのダウンロードを開始します。後で新しいプロバイダーを追加する必要が生じた場合は、新しいプロバイダーを参照するように main.tf をアップデートし、terraform init を再度実行します。

Terraform は main.tf の設定を使用し、意図した状態を実現するために必要な操作を示す「実行計画」を生成します。そして、Terraform は計画を実行して記述されたインフラストラクチャを構築します。その際、Terraform が代理として Fastly API と通信するため、先ほど作成した Fastly API トークンを Terraform に提供する必要があります。API トークンを環境変数としてシェルにエクスポートして Terraform の実行計画を生成し、内容を確認します。

$ export FASTLY_API_KEY="====PUT YOUR TOKEN HERE===="
$ terraform plan

まだこの設定が Fastly サービスに対してデプロイされていないため、この出力ではさまざまなリソースが未作成であることが示されています。

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:

  + create

Terraform will perform the following actions:

  # fastly_service_v1.test_service will be created
  + resource "fastly_service_v1" "test_service" {
      + activate              = true
      + active_version        = (known after apply)
      + cloned_version        = (known after apply)
      + comment               = "Managed by Terraform"
      + default_host          = (known after apply)
      + default_ttl           = 3600
      + force_destroy         = true
      + id                    = (known after apply)
      + name                  = "An Example Service"

      + backend
          + address                = "httpbin.org"
          + auto_loadbalance       = true
          + between_bytes_timeout  = 10000
          + connect_timeout        = 1000
          + error_threshold        = 0
          + first_byte_timeout     = 15000
          + max_conn               = 200
          + name                   = "My Test Backend"
          + port                   = 80
          + ssl_check_cert         = true
          + use_ssl                = false
          + weight                 = 100
        }

      + domain {
          + name = "<name>.global.ssl.fastly.net"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + active = (known after apply)

出力の最後の数行を見ると、作成予定のリソースが1つあり、変更または削除されるリソースがないことが分かります。

プロバイダーは、サービスとドメイン、バックエンドを (設定内にネストされたブロックに基づいて) 作成します。fastly_service_v1 リソースで複数のブロックを定義したにもかかわらず、Terraform 側では作成するリソースは1つのみであることが示されています。

内容に間違いがなければ計画を実行します。

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

...<snip>...

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:

すると、先ほどと同じ出力と、計画の実行を確認するプロンプトが表示されます。「yes」と入力し、Enter キーを押します。残りの出力にはステータスアップデートが表示されます。

  Enter a value: yes

fastly_service_v1.test_service: Creating...
fastly_service_v1.test_service: Still creating... [10s elapsed]
fastly_service_v1.test_service: Still creating... [20s elapsed]
fastly_service_v1.test_service: Creation complete after 29s
[id=2Z2KJsHtnsNGkGpafGVdbx]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

active = 1

最後の方にある「Outputs:」セクションには、main.tf で定義した active の出力変数が表示されています。この値が「1」と表示されているのは、今回作成されたサービスがバージョン1であることを示しています。

ヒント : CI システムなどの自動化された環境で Terraform を使用している場合、apply サブコマンドに <u>-auto-approve</u>undefined フラグを使用することで、プロンプトに対して「yes」と手動で入力するステップを省くことができます。

設定を検証する

サービス設定で定義したドメインを用いて Fastly が生成する特別なテスト URL を使ってサービス設定への変更を検証しながら、さまざまな変更を試すことができます。

https://<name>.global.ssl.fastly.net

例えば、ドメインの <name>example に設定されている元の Terraform コードが適切に実行された場合、テスト URL は次のようになります。

https://example.global.ssl.fastly.net

新しい設定が有効になるまで最大60秒程度かかる場合があります。設定完了後、テスト URL はそのバックエンドとして設定されている https://httpbin.org/ と同じコンテンツを表示します。この場合 Terraform コードのサービスリソースではカスタム VCL コードが定義されていませんが、Fastly のデフォルト VCL が使用されます。デフォルト VCL は指定されたバックエンドに対するリクエストをプロキシし、レスポンスのキャッシュを自動的に処理するなど、合理的かつ安全に動作します。

設定を変更する

では、Terraform のコードに新しいブロックを追加してみましょう。カスタム VCL ファイルを表す新しいブロックを fastly_service_v1 に追加します。その際、創造力を働かせて工夫しながらエッジで実行したいロジックのカスタム VCL ファイルを作成することができますが、今回のデモではシンプルなファイルを使います。 

まずは main.tf と同じディレクトリ内に vcl ディレクトリを作成し、その中に main.vcl ファイルを追加します。

$ mkdir vcl && touch vcl/main.vcl

次に、main.vcl ファイルに以下の VCL コードを追加します。

sub vcl_recv {
  #FASTLY recv

  if (req.url.path == "/anything/here") {
    set req.http.X-CustomHeader = "example";
  }

  if (req.url.path == "/anything/not/found") {
    error 600;
  }

  return(lookup);
}

sub vcl_error {
  #FASTLY error
  if (obj.status == 600) {
    set obj.status = 404;
    set obj.http.Content-Type = "text/html";
    synthetic {"<h1>Not Found</h1>"};
    return(deliver);
  }
}

このカスタム VCL は単純ながら、Terraform でも VCL アップデートの管理が可能であることを示します。この VCL コードには2つの明確なロジックが含まれています。

  1. /anything/here に対するリクエストを受信した場合、リクエストにカスタム HTTP ヘッダーを追加する (httpbin.org は /anything/ で始まるパスに対して 200 OK と、リクエストの詳細を含む JSON レスポンスを返します)。

  2. /anything/not/found に対するリクエストを受信した場合、通常 httpbin が返す 200 OK レスポンスの代わりに 404 Not Found のシンセティックレスポンスを返す。

ヒント : カスタム VCL の記述やテストには Fastly Fiddle が便利です。また、Developer Hub の VCL に関するページもぜひご覧ください。

次に、fastly_service_v1 リソースに以下のような <u>vcl</u> ブロックを追加して Terraform コードを変更します。

resource "fastly_service_v1" "test_service" {
  ...<snip>...

  vcl {
    content = file("vcl/main.vcl")
    main      = true
    name     = "custom_vcl"
  }

  ...<snip>...
}

変更が完了したら、planapply コマンドを再実行します。

$ terraform plan

vcl の追加が出力に反映されているはずです。

fastly_service_v1.test_service: Refreshing state...[id=2Z2KJsHtnsNGkGpafGVdbx]

An execution plan has been generated and is shown below.

Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # fastly_service_v1.test_service will be updated in-place
  ~ resource "fastly_service_v1" "test_service" {
        id             = "2Z2KJsHtnsNGkGpafGVdbx"
        name           = "An Example Service"
        # (5 unchanged attributes hidden)

      + vcl {
          + content = <<-EOT
                sub vcl_recv {
                  #FASTLY recv

                  if (req.url.path == "/anything/here")
                    set req.http.X-CustomHeader = "example";
                  }

                  if (req.url.path == "/anything/not/found")
                    error 600;
                  }
                  return(lookup);
                }

                sub vcl_error {
                  #FASTLY error

                  if (obj.status == 600) {
                    set obj.status = 404;
                    set obj.http.Content-Type = "text/html";
                    synthetic {"<h1>Not Found</h1>"};
                    return(deliver);
                  }
                }
            EOT
          + main    = true
          + name    = "custom_vcl"
        }
        # (2 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

まず「Refreshing state...」の部分に注目してください。Terraform のステートは、インフラストラクチャと設定のキャッシュです。このステートは、Terraform 設定にて定義された要件を実際の環境と比較するのに使用されます。このステートをリフレッシュすることで、Terraform は最新のリソースの内容に基づく実行計画を算出することができます。

次に、最後の行に注目してください。変更されるものが1つ存在することが示されています。これは、fastly_service_v1 リソースundefinedの変更が実行されることを意味しています (Terraform はステートの変更をチルダ記号「~」を使って表します)。 

また、この差分出力は既存のネストされたブロックの一部は変更がないことを示すと同時に、新たに追加された行にはプラス記号 (+) を使用して表示しています。Terraform 設定は file 関数を使ってプロジェクトディレクトリの VCL ファイルにアクセスしましたが、実行計画を算出する場合はファイルのコンテンツをインライン化し、それが差分出力に表示されます。

Terraform が表示した実行計画に間違いがなければ、変更を適用します (今回は「yes」と手動で入力せず、-auto-approve フラグを使ってみてください)。

$ terraform apply --auto-approve

変更適用後、新たな設定が Fastly のネットワーク全体にデプロイされ、VCL で定義されたエンドポイントが新しいルールに基づいて動作し始めるまでに20秒ほどかかります。

これで、最初のパス (/anything/here) では "X-Customheader": "example" のカスタムヘッダーが httpbin.org からの JSON レスポンスに表示され、2つ目のパス (/anything/not/found) では VCL コードによって生成される 404 Not Found のシンセティックレスポンスが返されるようになりました。これでサービス設定と VCL コードが想定どおりに適切に動作していることが確認できました。

これで完了です!ざっくりまとめると、今回のデモでは Terraform 設定を使って Fastly サービスのカスタム要件を定義し、実際に Fastly リソースを作成する実行計画を生成し、実行しました。その後、サービスリソースへの変更を完全に可視化し、コントロールしながらアップデートする典型的なライフサイクルを実行しました。 

次のステップ

VCL を利用することで、Terraform と Fastly 両方の機能を有効に活かすことができます。さらに、WebAssembly の優れた機能を活用し、RustAssemblyScript などの言語からコンパイルされた安全なカスタムバイナリを実行する Compute@Edge サービスを管理することも可能です。Compute@Edge と Terraform を駆使して Fastly を最大限に活用する方法については、Terraform と Fastly Terraform プロバイダーを使ったオーケストレーションに関するページと Fastly の Terraform ドキュメントをご覧ください。