Terraform ↗ は Infrastructure as Code のツールです。トンネルをほかのインフラと一緒に定義し、管理できます。このガイドでは次をデプロイします。
- Web サーバーを動かす GCP 仮想マシン
- そのサーバーをインターネットへ公開する Cloudflare Tunnel
- (任意)接続できるユーザーを定義する Cloudflare Access ポリシー
お使いの OS 向けの Terraform インストールガイド ↗ を参照してください。
-
Terraform が GCP アカウントと連携できるように、gcloud CLI をインストール ↗ します。
-
次を実行して CLI で認証します。
gcloud auth application-default login
Terraform が Cloudflare アカウントと連携できるように、API トークンを作成 します。トークンには、少なくとも次の権限を含めます。
| 種類 | 項目 | 権限 |
|---|---|---|
| Account | Cloudflare Tunnel | Edit |
| Account | Access: Apps and Policies | Edit |
| Zone | DNS | Edit |
Terraform は、設定ファイルを含む作業ディレクトリで動作します。設定は複数のファイルに分けても、1 つのファイルにまとめても構いません。Terraform はディレクトリ内のすべての設定ファイルを、1 つのドキュメントとして評価します。
-
Terraform 設定用のフォルダーを作成します。
mkdir cloudflare-tf -
そのディレクトリに移動します。
cd cloudflare-tf
次の変数は、GCP と Cloudflare の構成に渡されます。
-
構成ディレクトリに
.tfファイルを作成します。touch variables.tf -
テキストエディターでファイルを開き、次の内容をコピーして貼り付けます。
# GCP variables variable "gcp_project_id" { description = "Google Cloud Platform (GCP) project ID" type = string } variable "zone" { description = "Geographical zone for the GCP VM instance" type = string } variable "machine_type" { description = "Machine type for the GCP VM instance" type = string } # Cloudflare variables variable "cloudflare_zone" { description = "Domain used to expose the GCP VM instance to the Internet" type = string } variable "cloudflare_zone_id" { description = "Zone ID for your domain" type = string } variable "cloudflare_account_id" { description = "Account ID for your Cloudflare account" type = string sensitive = true } variable "cloudflare_email" { description = "Email address for your Cloudflare account" type = string sensitive = true } variable "cloudflare_token" { description = "Cloudflare API token" type = string sensitive = true }
-
設定ディレクトリに
.tfvarsファイルを作成します。touch terraform.tfvarsファイル名が
terraform.tfvarsであれば、Terraform はこれらの変数を自動的に使います。それ以外の名前の場合は、変数ファイルを手動で渡す必要があります。 -
次の変数を
terraform.tfvarsに追加します。例は自分の値に書き換えてください。cloudflare_zone = "example.com" cloudflare_zone_id = "023e105f4ecef8ad9ca31a8372d0c353" cloudflare_account_id = "372e67954025e0ba6aaa6d586b9e0b59" cloudflare_email = "user@example.com" cloudflare_token = "y3AalHS_E7Vabk3c3lX950F90_Xl7YtjSlzyFn_X" gcp_project_id = "testvm-123" zone = "us-central1-a" machine_type = "e2-medium"
インフラストラクチャのプロビジョニングに使う プロバイダー ↗ を宣言します。
-
設定ディレクトリに
.tfファイルを作成します。touch providers.tf -
次のプロバイダーを
providers.tfに追加します。randomプロバイダーはトンネルシークレットの生成に使います。terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" version = ">= 5.8.2" } google = { source = "hashicorp/google" } } required_version = ">= 1.2" } # Providers provider "cloudflare" { api_token = var.cloudflare_token } provider "google" { project = var.gcp_project_id } provider "random" { }terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" version = ">= 4.40.0, < 5.0.0" } google = { source = "hashicorp/google" } random = { source = "hashicorp/random" } } required_version = ">= 1.2" } # Providers provider "cloudflare" { api_token = var.cloudflare_token } provider "google" { project = var.gcp_project_id } provider "random" { }
次の設定は、Cloudflare アカウントの設定を変更します。
-
設定ディレクトリに
.tfファイルを作成します。touch Cloudflare-config.tf -
Cloudflare-config.tfに次のリソースを追加します。# Creates a new remotely-managed tunnel for the GCP VM. resource "cloudflare_zero_trust_tunnel_cloudflared" "gcp_tunnel" { account_id = var.cloudflare_account_id name = "Terraform GCP tunnel" config_src = "cloudflare" } # Reads the token used to run the tunnel on the server. data "cloudflare_zero_trust_tunnel_cloudflared_token" "gcp_tunnel_token" { account_id = var.cloudflare_account_id tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id } # Creates the CNAME record that routes http_app.${var.cloudflare_zone} to the tunnel. resource "cloudflare_dns_record" "http_app" { zone_id = var.cloudflare_zone_id name = "http_app" content = "${cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id}.cfargotunnel.com" type = "CNAME" ttl = 1 proxied = true } # Configures tunnel with a published application for clientless access. resource "cloudflare_zero_trust_tunnel_cloudflared_config" "gcp_tunnel_config" { tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id account_id = var.cloudflare_account_id config = { ingress = [ { hostname = "http_app.${var.cloudflare_zone}" service = "http://httpbin:80" }, { service = "http_status:404" } ] } } # (Optional) Routes internal IP of GCP instance through the tunnel for private network access using WARP. resource "cloudflare_zero_trust_tunnel_cloudflared_route" "example_tunnel_route" { account_id = var.cloudflare_account_id tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id network = google_compute_instance.http_server.network_interface.0.network_ip comment = "Example tunnel route" } # Creates a reusable Access policy. resource "cloudflare_zero_trust_access_policy" "allow_emails" { account_id = var.cloudflare_account_id name = "Allow email addresses" decision = "allow" include = [ { email = { email = var.cloudflare_email } }, { email_domain = { domain = "@example.com" } } ] } # Creates an Access application to control who can connect to the public hostname. resource "cloudflare_zero_trust_access_application" "http_app" { account_id = var.cloudflare_account_id type = "self_hosted" name = "Access application for http_app.${var.cloudflare_zone}" domain = "http_app.${var.cloudflare_zone}" policies = [ { id = cloudflare_zero_trust_access_policy.allow_emails.id precedence = 1 } ] }# Generates a 32-byte secret for the tunnel. resource "random_bytes" "tunnel_secret" { byte_length = 32 } # Creates a new remotely-managed tunnel for the GCP VM. resource "cloudflare_zero_trust_tunnel_cloudflared" "gcp_tunnel" { account_id = var.cloudflare_account_id name = "Terraform GCP tunnel" secret = random_bytes.tunnel_secret.base64 } # Creates the CNAME record that routes http_app.${var.cloudflare_zone} to the tunnel. resource "cloudflare_record" "http_app" { zone_id = var.cloudflare_zone_id name = "http_app" content = "${cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.cname}" type = "CNAME" proxied = true } # Configures tunnel with a published application for clientless access. resource "cloudflare_zero_trust_tunnel_cloudflared_config" "gcp_tunnel_config" { tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id account_id = var.cloudflare_account_id config { ingress_rule { hostname = "${cloudflare_record.http_app.hostname}" service = "http://httpbin:80" } ingress_rule { service = "http_status:404" } } } # (Optional) Route internal IP of GCP instance through the tunnel for private network access using WARP. resource "cloudflare_zero_trust_tunnel_route" "example_tunnel_route" { account_id = var.cloudflare_account_id tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id network = google_compute_instance.http_server.network_interface.0.network_ip comment = "Example tunnel route" } # Creates an Access application to control who can connect to the public hostname. resource "cloudflare_zero_trust_access_application" "http_app" { account_id = var.cloudflare_account_id name = "Access application for http_app.${var.cloudflare_zone}" domain = "http_app.${var.cloudflare_zone}" } # Creates a (legacy) Access policy for the Access application. resource "cloudflare_zero_trust_access_policy" "allow_emails" { application_id = cloudflare_zero_trust_access_application.http_app.id account_id = var.cloudflare_account_id name = "Example policy for http_app.${var.cloudflare_zone}" precedence = "1" decision = "allow" include { email = [var.cloudflare_email] } }
これらのリソースの詳細は、Cloudflare provider のドキュメント ↗ を参照してください。
次の構成は、GCP 仮想マシンの仕様を定義し、起動時に実行するスタートアップスクリプトを設定します。
-
構成ディレクトリに
.tfファイルを作成します。touch GCP-config.tf -
次の内容を
GCP-config.tfに追加します。# OS the server will use data "google_compute_image" "image" { family = "ubuntu-2204-lts" project = "ubuntu-os-cloud" } # GCP Instance resource resource "google_compute_instance" "http_server" { name = "test" machine_type = var.machine_type zone = var.zone tags = [] boot_disk { initialize_params { image = data.google_compute_image.image.self_link } } network_interface { network = "default" access_config { //Ephemeral IP } } // Optional config to make instance ephemeral /* scheduling { preemptible = true automatic_restart = false } */ // Pass the tunnel token to the GCP server so that the server can install and run the tunnel upon startup. metadata_startup_script = templatefile("./install-tunnel.tftpl", { tunnel_token = data.cloudflare_zero_trust_tunnel_cloudflared_token.gcp_tunnel_token.token }) }# OS the server will use data "google_compute_image" "image" { family = "ubuntu-2204-lts" project = "ubuntu-os-cloud" } # GCP Instance resource resource "google_compute_instance" "http_server" { name = "test" machine_type = var.machine_type zone = var.zone tags = [] boot_disk { initialize_params { image = data.google_compute_image.image.self_link } } network_interface { network = "default" access_config { //Ephemeral IP } } // Optional config to make instance ephemeral /* scheduling { preemptible = true automatic_restart = false } */ // Pass the tunnel token to the GCP server so that the server can install and run the tunnel upon startup. metadata_startup_script = templatefile("./install-tunnel.tftpl", { tunnel_token = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.tunnel_token }) }
次のスクリプトは cloudflared をインストールし、トンネルをサービスとして起動します。この例では、接続確認に使える軽量な HTTP アプリケーションもインストールします。
-
設定ディレクトリに Terraform テンプレートファイルを作成します。
touch install-tunnel.tftpl -
テキストエディターでファイルを開き、次の bash スクリプトをコピーして貼り付けます。
# Script to install Cloudflare Tunnel and Docker resources # Docker configuration cd /tmp sudo apt-get install software-properties-common # Retrieving the docker repository for this OS curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" # The OS is updated and docker is installed sudo apt update -y && sudo apt upgrade -y sudo apt install docker docker-compose -y # Add the HTTPBin application and run it on localhost:8080. cat > /tmp/docker-compose.yml << "EOF" version: '3' services: httpbin: image: kennethreitz/httpbin restart: always container_name: httpbin ports: - 8080:80 cloudflared: image: cloudflare/cloudflared:latest restart: always container_name: cloudflared command: tunnel run --token ${tunnel_token} EOF cd /tmp sudo docker-compose up -d
構成ファイルをデプロイするには:
-
構成ディレクトリを初期化します。
terraform init -
作成される内容をプレビューします。
terraform plan -
構成を適用します。
terraform apply
GCP インスタンスとトンネルがオンラインになるまで、数分かかることがあります。新しいトンネルは、Cloudflare ダッシュボード ↗ の Networking > Tunnels で確認できます。
-
Cloudflare ダッシュボード ↗ で Networking > Tunnels を開き、トンネルが Active であることを確認します。
-
(任意)Access を設定した場合は、Security > Access > Applications を開き、自分の Cloudflare メールが Access ポリシーで許可されていることを確認します。
-
任意の端末のブラウザーで
http_app.<CLOUDFLARE_ZONE>を開きます(例:http_app.example.com)。Access を設定している場合は、Access のログインページが表示されます。Cloudflare のメールでログインします。
-
HTTPBin のホームページが表示されれば、トンネルが正しくトラフィックをルーティングしています。