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