云端部署

Google Cloud:用 Terraform 建一个 Cloud Storage 备份桶

用 Terraform 建一个 Cloud Storage 备份桶:禁止公开访问、开启版本控制,30 天后自动转低频存储、一年后删除、旧版本只留 3 个;同时给项目设月度预算,花到 50%、90% 和预测超支时发邮件。

BINBHO · 图文教程

用 Terraform 建一个 Cloud Storage 备份桶:禁止公开访问、开启版本控制,30 天后自动转低频存储、一年后删除、旧版本只留 3 个;同时给项目设月度预算,花到 50%、90% 和预测超支时发邮件。

先看清成品

Terraform 1.16.6 + google provider 8.7.0 在本机通过 fmt -check、init、validate,terraform test(mock provider)2 个测试通过:桶禁止公开且有 3 条生命周期规则、预算包含预测提醒。没有在真实项目执行 apply。

注意:Google Cloud 按用量计费,价格以官方价格页和账单为准,本文不列金额。Google Cloud 不在中国大陆提供服务,请在服务可用的地区按条款注册使用,不要用代理或借用他人账号绕过地区限制,也不要共享账号。

做完之后你会得到什么?

成果 说明
备份桶 gs://你的桶名,私有
生命周期 30 天转 NEARLINE、365 天删除、旧版本留 3 个
预算 50% / 90% 实际花费 + 100% 预测花费
邮件通知渠道 每个邮箱一个

需要准备哪些环境?

项目 要求 说明
项目和结算账号 你要有结算账号的 Billing Account Administrator 或预算相关权限 预算建在结算账号下
API Cloud Storage、Cloud Billing Budget、Cloud Monitoring 首次 apply 前启用
Terraform 1.7 以上 本文用 1.16.6
凭证 gcloud auth application-default login

文件结构

21-google-cloud-C-src/
├── main.tf
└── tests/guard.tftest.hcl

第 1 步:配置

main.tf:

terraform {
  required_version = ">= 1.7"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 8.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

variable "project_id" { type = string }
variable "region" {
  type    = string
  default = "asia-east1"
}
variable "bucket_name" { type = string }
variable "billing_account" {
  type        = string
  description = "结算账号 ID,形如 012345-6789AB-CDEF01"
}
variable "monthly_budget" {
  type        = string
  description = "预算金额,单位与结算账号币种一致"
}
variable "alert_emails" { type = list(string) }

# 备份桶:统一访问控制、强制禁止公开、开启版本
resource "google_storage_bucket" "backup" {
  name                        = var.bucket_name
  location                    = var.region
  storage_class               = "STANDARD"
  uniform_bucket_level_access = true
  public_access_prevention    = "enforced"

  versioning {
    enabled = true
  }

  # 30 天后转 NEARLINE
  lifecycle_rule {
    condition {
      age = 30
    }
    action {
      type          = "SetStorageClass"
      storage_class = "NEARLINE"
    }
  }

  # 当前版本 365 天后删除
  lifecycle_rule {
    condition {
      age        = 365
      with_state = "LIVE"
    }
    action {
      type = "Delete"
    }
  }

  # 旧版本只保留最近 3 个
  lifecycle_rule {
    condition {
      num_newer_versions = 3
      with_state         = "ARCHIVED"
    }
    action {
      type = "Delete"
    }
  }
}

data "google_project" "this" {}

resource "google_monitoring_notification_channel" "email" {
  for_each     = toset(var.alert_emails)
  display_name = "预算提醒 ${each.value}"
  type         = "email"
  labels = {
    email_address = each.value
  }
}

resource "google_billing_budget" "monthly" {
  billing_account = var.billing_account
  display_name    = "monthly-${var.project_id}"

  budget_filter {
    projects = ["projects/${data.google_project.this.number}"]
  }

  amount {
    specified_amount {
      units = var.monthly_budget
    }
  }

  threshold_rules {
    threshold_percent = 0.5
  }
  threshold_rules {
    threshold_percent = 0.9
  }
  threshold_rules {
    threshold_percent = 1.0
    spend_basis       = "FORECASTED_SPEND"
  }

  all_updates_rule {
    monitoring_notification_channels = [for c in google_monitoring_notification_channel.email : c.id]
    disable_default_iam_recipients   = false
  }
}

output "bucket_url" {
  value = google_storage_bucket.backup.url
}

要点:

  • NEARLINE 有最短存储时间和读取费用,适合很少读取的备份;经常读取的数据不要转。
  • 版本控制 + “旧版本留 3 个”,误删或被覆盖后还能找回。
  • 预算金额 units 的币种跟随结算账号。

第 2 步:离线测试

tests/guard.tftest.hcl:

mock_provider "google" {
  mock_data "google_project" {
    defaults = { number = "123456789012" }
  }
}

variables {
  project_id      = "demo-project"
  bucket_name     = "binbho-backup-test"
  billing_account = "000000-000000-000000"
  monthly_budget  = "100"
  alert_emails    = ["ops@example.com"]
}

run "bucket_is_private" {
  command = plan
  assert {
    condition     = google_storage_bucket.backup.public_access_prevention == "enforced" && google_storage_bucket.backup.uniform_bucket_level_access
    error_message = "备份桶必须禁止公开访问"
  }
  assert {
    condition     = length(google_storage_bucket.backup.lifecycle_rule) == 3
    error_message = "应有 3 条生命周期规则"
  }
}

run "budget_has_forecast_alert" {
  command = plan
  assert {
    condition     = anytrue([for r in google_billing_budget.monthly.threshold_rules : r.spend_basis == "FORECASTED_SPEND"])
    error_message = "需要预测超支提醒"
  }
}
terraform init
terraform validate
terraform test

检查结果(本机实际输出):Success! 2 passed, 0 failed.

第 3 步:创建(本文未执行)

gcloud services enable storage.googleapis.com billingbudgets.googleapis.com monitoring.googleapis.com
terraform apply \
  -var project_id=你的项目ID -var bucket_name=全局唯一桶名 \
  -var billing_account=你的结算账号ID -var monthly_budget=100 \
  -var 'alert_emails=["你的邮箱"]'

预算金额只是示例,按你的实际情况填。

第 4 步:验证

gcloud storage buckets describe gs://你的桶名 --format="yaml(public_access_prevention,versioning,lifecycle_config)"
echo hello > a.txt && gcloud storage cp a.txt gs://你的桶名/
gcloud storage objects describe gs://你的桶名/a.txt --format="value(storage_class)"

预期结果:public_access_prevention: enforced,版本控制开启,新对象为 STANDARD;生命周期规则在后台异步执行,不会立刻生效。

调试示例

如果把 public_access_prevention 改成 "inherited",测试 bucket_is_private 会失败并显示 备份桶必须禁止公开访问,在 plan 之前就挡住了不安全的改动。

出错了怎么办?

现象 原因 解决
The requested bucket name is not available 桶名全球唯一 换名字
预算创建 403 没有结算账号权限,或 ADC 的配额项目不对 找结算管理员授权;gcloud auth application-default set-quota-project 项目ID
API has not been used in project API 未启用 运行第 3 步的 enable 命令
文件没有按时转存储类别 生命周期是异步的 等待,或在控制台查看规则
预算邮件收不到 通知渠道邮箱填错 在 Monitoring 通知渠道里检查

模板规则

  • 存储桶默认 public_access_prevention = "enforced"。
  • 备份数据一定开版本控制并限制旧版本数量。
  • 每个项目都配预算,至少包含一条预测提醒。

项目资源

用途 地址
Cloud Storage 生命周期管理 https://cloud.google.com/storage/docs/lifecycle
禁止公开访问 https://cloud.google.com/storage/docs/public-access-prevention
创建预算 https://cloud.google.com/billing/docs/how-to/budgets
Terraform google provider https://registry.terraform.io/providers/hashicorp/google/latest/docs
Terraform 测试中的 mock https://developer.hashicorp.com/terraform/language/tests/mocking
Google Cloud 服务条款 https://cloud.google.com/terms

备份最怕两件事:数据被公开,以及存储费用悄悄涨上去。生命周期规则管住费用,禁止公开访问和预算提醒管住风险,写成代码以后每个项目都能照搬。