Terraform ↗ 是一种基础设施即代码(infrastructure as code)软件工具,允许您使用标准化的配置语法部署来自不同提供商的服务。创建 Terraform 配置文件时,您定义的是配置的最终状态,而不是逐步过程。这使您能够轻松地与其他基础设施一起部署、修改和管理您的 Tunnel。
在本指南中,您将使用 Terraform 部署:
- 一个运行 HTTP 测试服务器的 Google Cloud Project (GCP) 虚拟机
- 一个使服务器在互联网上可用的 Cloudflare Tunnel
- 一个定义谁可以连接到服务器的 Cloudflare Access 策略
要完成以下步骤,您需要:
请参阅适用于您的操作系统的 Terraform 安装指南 ↗。
-
安装 gcloud CLI ↗,以便 Terraform 可以与您的 GCP 账户进行交互。
-
通过运行以下命令进行 CLI 身份验证:
gcloud auth application-default login
创建 API 令牌,以便 Terraform 可以与您的 Cloudflare 账户进行交互。您的令牌至少应包含以下权限:
| 类型 | 项目 | 权限 |
|---|---|---|
| 账户 | Cloudflare Tunnel | 编辑 |
| 账户 | Access:应用程序和策略 | 编辑 |
| 区域 | DNS | 编辑 |
Terraform 通过包含配置文件的工作目录发挥作用。您可以将配置存储在多个文件中,也可以仅存储在一个文件中——Terraform 将把目录中的所有配置文件视为处于单个文档中进行评估。
-
为您的 Terraform 配置创建一个文件夹:
mkdir cloudflare-tf -
进入该目录:
cd cloudflare-tf
以下变量将被传递到您的 GCP 和 Cloudflare 配置中。
-
在您的配置目录中,创建一个
.tf文件:touch variables.tf -
在文本编辑器中打开该文件并复制并粘贴以下内容:
# GCP 变量 variable "gcp_project_id" { description = "Google Cloud Platform (GCP) 项目 ID" type = string } variable "zone" { description = "GCP VM 实例的地理区域" type = string } variable "machine_type" { description = "GCP VM 实例的机器类型" type = string } # Cloudflare 变量 variable "cloudflare_zone" { description = "用于向互联网公开 GCP VM 实例的域名" type = string } variable "cloudflare_zone_id" { description = "您域的区域 (Zone) ID" type = string } variable "cloudflare_account_id" { description = "您 Cloudflare 账户(account)的账户 ID" type = string sensitive = true } variable "cloudflare_email" { description = "您 Cloudflare 账户(account)的电子邮件地址" type = string sensitive = true } variable "cloudflare_token" { description = "Cloudflare API 令牌" 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"
您需要声明用于配置基础设施的 提供程序(providers) ↗。
-
在您的配置目录中,创建一个
.tf文件:touch providers.tf -
将以下提供程序添加到
providers.tf中。random提供程序用于生成隧道机密(secret)。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 账户(account)中的设置。
-
在您的配置目录中,创建一个
.tf文件:touch Cloudflare-config.tf -
将以下资源添加到
Cloudflare-config.tf中:# 为 GCP VM 创建一个新的远程管理隧道。 resource "cloudflare_zero_trust_tunnel_cloudflared" "gcp_tunnel" { account_id = var.cloudflare_account_id name = "Terraform GCP tunnel" config_src = "cloudflare" } # 读取用于在服务器上运行该隧道的令牌。 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 } # 创建将 http_app.${var.cloudflare_zone} 路由到该隧道的 CNAME 记录。 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 } # 用已发布的应用配置隧道以进行无客户端访问。 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" } ] } } # (可选)通过隧道路由 GCP 实例的内部 IP,以便使用 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" } # 创建一个可复用的 Access 策略。 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" } } ] } # 创建一个 Access 应用程序以控制谁可以连接到公共主机名。 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 } ] }# 为隧道生成一个 32 字节的机密(secret)。 resource "random_bytes" "tunnel_secret" { byte_length = 32 } # 为 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 } # 创建将 http_app.${var.cloudflare_zone} 路由到该隧道的 CNAME 记录。 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 } # 用已发布的应用配置隧道以进行无客户端访问。 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" } } } # (可选)通过隧道路由 GCP 实例的内部 IP,以便使用 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" } # 创建一个 Access 应用程序以控制谁可以连接到公共主机名。 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}" } # 为 Access 应用程序创建一个(旧版)Access 策略。 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 虚拟机(VM)的规格,并配置了在引导时运行的启动脚本。
-
在您的配置目录中,创建一个
.tf文件:touch GCP-config.tf -
将以下内容添加到
GCP-config.tf中:# 服务器将使用的操作系统 data "google_compute_image" "image" { family = "ubuntu-2204-lts" project = "ubuntu-os-cloud" } # GCP 实例资源 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 { // 临时 IP } } // 可选配置,以使实例成为临时实例 /* scheduling { preemptible = true automatic_restart = false } */ // 将隧道令牌传递给 GCP 服务器,以便服务器可以在启动时安装并运行隧道。 metadata_startup_script = templatefile("./install-tunnel.tftpl", { tunnel_token = data.cloudflare_zero_trust_tunnel_cloudflared_token.gcp_tunnel_token.token }) }# 服务器将使用的操作系统 data "google_compute_image" "image" { family = "ubuntu-2204-lts" project = "ubuntu-os-cloud" } # GCP 实例资源 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 { // 临时 IP } } // 可选配置,以使实例成为临时实例 /* scheduling { preemptible = true automatic_restart = false } */ // 将隧道令牌传递给 GCP 服务器,以便服务器可以在启动时安装并运行隧道。 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 脚本:
# 安装 Cloudflare Tunnel 和 Docker 资源的脚本 # Docker 配置 cd /tmp sudo apt-get install software-properties-common # 检索此操作系统的 docker 软件源 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" # 更新操作系统并安装 docker sudo apt update -y && sudo apt upgrade -y sudo apt install docker docker-compose -y # 添加 HTTPBin 应用程序并在 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 实例和 Tunnel 可能需要几分钟时间才能上线。您可以在 Cloudflare 仪表板 ↗的 Networks(网络) > Tunnels(隧道) 下查看您的新 Tunnel,在 Zero Trust > Access controls(访问控制) > **Applications(应用程序)**下查看您的 Access 应用程序和策略。新的 DNS 记录可在 DNS 记录页面上找到。
-
在 Networks(网络) > Tunnels(隧道) 中,验证您的 Tunnel 是否处于活动状态。
-
在 Access controls(访问控制) > Applications(应用程序) 中,验证您的 Access 策略是否允许您的 Cloudflare 电子邮件。
-
从任何设备打开浏览器,转到
http_app.<CLOUDFLARE_ZONE>(例如,http_app.example.com)。如果您最近没有登录,您将看到 Access 登录页面。
-
使用您的 Cloudflare 电子邮件登录。
您应该会看到 HTTPBin ↗ 主页。