Terraform 管理华为云 OBS 桶:从创建到静态网站托管

举报
yd_288131123 发表于 2026/11/09 11:14:07 2026/11/09
【摘要】 Huawei Cloud × Terraform × IaCTerraform 管理华为云 OBS 桶:从创建到静态网站托管用基础设施即代码的方式,一键创建 OBS 桶、配置权限、启用网站托管、绑定自定义域名发布于 2026-11-09 · 约 15 分钟阅读目录引言前置准备Provider 配置第一步:创建 OBS 桶第二步:设置桶权限第三步:静态网站托管第四步:上传网站内容第五步:自定义...
Huawei Cloud × Terraform × IaC

Terraform 管理华为云 OBS 桶:从创建到静态网站托管

用基础设施即代码的方式,一键创建 OBS 桶、配置权限、启用网站托管、绑定自定义域名

发布于 2026-11-09 · 约 15 分钟阅读

引言:为什么用 Terraform 管理 OBS?

如果你在华为云控制台上手动创建过 OBS 桶,你大概经历过这样的流程:点"创建桶"→ 填名字 → 选区域 → 选存储类别 → 点确定。然后又要手动配置权限策略、开启网站托管、上传文件、绑定域名……每一步都是点击和填表。

这样做有几个问题:

  • 不可复现:下次要创建一个同样的桶,你得记住每一步点过什么
  • 不可审计:三个月后你很难说清这个桶的权限策略到底是怎么配的
  • 不可批量:要一次创建 10 个不同配置的桶?手动操作既慢又容易出错
  • 不可销毁:手动创建的资源,清理时容易遗漏,留下"僵尸桶"持续计费

Terraform 解决的就是这些问题。你用声明式代码描述"我要什么样的基础设施",Terraform 负责创建、更新、销毁,并且全程可复现、可审计、可版本化。

本文带你走完一条完整链路:从零开始,用 Terraform 在华为云上创建一个 OBS 桶,配置公共读权限,启用静态网站托管,上传网站文件,最后绑定自定义域名——全部用代码完成,一次 terraform apply 搞定。

💡 本文适合谁

了解 OBS 桶的基本概念(对象存储、桶、权限),但不熟悉 Terraform 的同学。如果你连 OBS 是什么都不知道,建议先看一下华为云 OBS 产品文档。

前置准备

开始写代码之前,需要准备好两样东西:Terraform CLI 和华为云的访问凭证。

安装 Terraform

如果还没有安装 Terraform,macOS 上可以用 Homebrew:


shell
brew install hashicorp/tap/terraform

Linux 上可以直接下载二进制:


shell
wget https://releases.hashicorp.com/terraform/1.6.0/terraform_1.6.0_linux_amd64.zip
unzip terraform_1.6.0_linux_amd64.zip
sudo mv terraform /usr/local/bin/

验证安装:


shell
terraform version
# Terraform v1.6.0

Provider 配置

华为云 Terraform Provider 需要以下信息来认证:

参数 说明 获取方式
access_key AK(访问密钥 ID) 控制台 → 我的凭证 → 访问密钥
secret_key SK(秘密访问密钥) 创建 AK 时一并生成,仅显示一次
region 区域,如 cn-north-4 控制台右上角区域选择器
project_id 项目 ID(部分区域需要) 我的凭证 → 项目列表
⚠️ AK/SK 安全提醒

AK/SK 是你华为云账号的访问凭证,泄露后他人可以操作你的云资源。建议通过环境变量传入,不要硬编码到 .tf 文件中,更不要提交到 Git 仓库。

通过环境变量传入凭证(推荐做法):


shell
# 设置华为云凭证(当前终端会话有效)
export HW_ACCESS_KEY="你的AK"
export HW_SECRET_KEY="你的SK"
export HW_REGION="cn-north-4"

Provider 块写在 provider.tf 中:


provider.tf
terraform {
  required_providers {
    huaweicloud = {
      source  = "huaweicloud/huaweicloud"
      version = ">= 1.50.0"
    }
  }
}

provider "huaweicloud" {
  # AK/SK 通过环境变量传入,不硬编码
  region = "cn-north-4"
}

这里有个概念要解释一下:required_providers 块告诉 Terraform "我要用华为云的 provider",source 指定了从哪里下载(Terraform Registry),version 指定了版本约束。>= 1.50.0 表示"1.50.0 或更高版本"。

💡 什么是 Provider?

Provider 是 Terraform 与云平台之间的"翻译官"。Terraform 本身不知道什么是 OBS 桶,它只知道如何调用 Provider 暴露的 API。华为云 Provider 把 Terraform 的声明式语法翻译成华为云 OpenAPI 调用。

Step 1创建 OBS 桶

先从最简单的开始——用 Terraform 创建一个 OBS 桶。

在 Terraform 中,一切资源都用 resource 块来声明。OBS 桶对应的资源类型是 huaweicloud_obs_bucket。来看代码:


main.tf
resource "huaweicloud_obs_bucket" "website_bucket" {
  bucket        = "my-terraform-blog-demo"
  storage_class = "STANDARD"
  acl           = "private"
}

逐行解释:

  • resource "huaweicloud_obs_bucket" "website_bucket" — 声明一个 OBS 桶资源,website_bucket 是你在 Terraform 代码中引用它的名字(逻辑名),不是桶的真实名称
  • bucket = "my-terraform-blog-demo" — 桶的真实名称,在 OBS 中全局唯一,创建后不可修改
  • storage_class = "STANDARD" — 存储类别,STANDARD 是标准存储,还有 WARM(低频访问)和 COLD(归档)
  • acl = "private" — 桶的 ACL(访问控制列表),private 表示只有桶拥有者能访问。后面我们会改成 public-read 来支持网站托管

现在运行一下看看效果:


shell
# 初始化:下载 provider 插件
terraform init

# 预览:看看 Terraform 要做什么
terraform plan

# 执行:真正创建资源
terraform apply

terraform plan 的输出大致是:


plan output
Terraform will perform the following actions:

  # huaweicloud_obs_bucket.website_bucket will be created
  + resource "huaweicloud_obs_bucket" "website_bucket" {
      + acl           = "private"
      + bucket        = "my-terraform-blog-demo"
      + id            = (known after apply)
      + storage_class = "STANDARD"
    }

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

+ 号表示"将要创建"。确认无误后输入 yes,Terraform 就会调用华为云 API 创建这个桶。

💡 terraform plan 是你的安全网

plan 不会真正执行任何操作,它只是对比"代码描述的状态"和"云端实际状态"的差异,告诉你接下来会发生什么。养成"先 plan 再 apply"的习惯,能避免很多误操作。

Step 2设置桶访问权限

静态网站托管需要桶内的对象能被公网读取,所以我们要把 ACL 从 private 改为 public-read。

在 Terraform 中,修改资源属性非常简单——直接改代码,再 apply:


main.tf
resource "huaweicloud_obs_bucket" "website_bucket" {
  bucket        = "my-terraform-blog-demo"
  storage_class = "STANDARD"
  acl           = "public-read"  # 改为公共读
}

再次 terraform apply,Terraform 会检测到 ACL 变了,只更新这一个属性,不会重建桶。

⚠️ public-read 意味着什么

public-read 表示任何人都能读取桶内对象,但只有拥有者能写入。对于静态网站托管这是必须的,但如果你桶里有敏感文件,请用桶策略(bucket policy)做更细粒度的控制,而不是简单设为 public-read。

如果你需要更细粒度的权限控制,可以用 huaweicloud_obs_bucket_policy 资源:


main.tf
resource "huaweicloud_obs_bucket_policy" "website_policy" {
  bucket = huaweicloud_obs_bucket.website_bucket.id
  policy = jsonencode({
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["GetObject"]
        Resource = ["arn:aws:s3:::my-terraform-blog-demo/*"]
        Principal = { ID = "*" }
      }
    ]
  })
}

这样只有 GetObject(读取对象)被允许,其他操作(如列举桶内所有对象 ListBucket)仍然需要认证。

Step 3配置静态网站托管

OBS 桶可以配置为静态网站托管模式:指定一个索引页面(如 index.html)和错误页面(如 404.html),用户访问桶的网站端点时,OBS 会自动返回对应页面。

在 Terraform 中,用 huaweicloud_obs_bucket_website 资源来配置:


main.tf
resource "huaweicloud_obs_bucket_website" "static_site" {
  bucket = huaweicloud_obs_bucket.website_bucket.id

  index_document = "index.html"
  error_document = "404.html"
}

解释:

  • bucket = huaweicloud_obs_bucket.website_bucket.id — 这里用到了 Terraform 的引用语法。我们引用了 Step 1 创建的桶的 ID。这样 Terraform 知道这两个资源有依赖关系,会先创建桶再配置网站托管
  • index_document — 当用户访问网站根路径时返回的页面
  • error_document — 当用户访问不存在的路径时返回的页面
💡 引用语法是 Terraform 的核心

资源类型.逻辑名.属性 是 Terraform 中引用其他资源属性的标准写法。Terraform 会自动分析这些引用来构建依赖图,决定资源的创建顺序。你不需要手动指定"先创建 A 再创建 B"。

配置完成后,桶的网站端点地址格式为:


endpoint
https://my-terraform-blog-demo.obs-website.cn-north-4.myhuaweicloud.com

Step 4上传网站内容

桶和网站托管都配好了,但桶里还是空的。我们需要上传 index.html 和 404.html。

在 Terraform 中,用 huaweicloud_obs_bucket_object 资源来上传文件:


main.tf
resource "huaweicloud_obs_bucket_object" "index_page" {
  bucket  = huaweicloud_obs_bucket.website_bucket.id
  key     = "index.html"
  content = templatefile("${path.module}/templates/index.html", {
    title = "My Static Website"
    date  = formatdate("YYYY-MM-DD", timestamp())
  })
  content_type = "text/html"
  acl          = "public-read"
}

resource "huaweicloud_obs_bucket_object" "error_page" {
  bucket  = huaweicloud_obs_bucket.website_bucket.id
  key     = "404.html"
  content = templatefile("${path.module}/templates/404.html", {})
  content_type = "text/html"
  acl          = "public-read"
}

这里用到了 templatefile() 函数——它读取一个本地模板文件,用提供的变量做字符串替换后返回内容。这样你可以把 HTML 模板放在 templates/ 目录下,用 Terraform 变量动态填充。

path.module 是一个内置变量,表示当前 Terraform 模块所在的目录路径。

如果文件很大(如图片)

对于大文件,用 source 而不是 content:


main.tf
resource "huaweicloud_obs_bucket_object" "logo" {
  bucket  = huaweicloud_obs_bucket.website_bucket.id
  key     = "assets/logo.png"
  source  = "${path.module}/assets/logo.png"
  content_type = "image/png"
  acl          = "public-read"
}
⚠️ Terraform 不适合管理大量文件

Terraform 为每个 obs_bucket_object 资源在 state 文件中记录一条记录。如果你有几百个文件,state 文件会变得很大,plan 也会变慢。对于大量文件上传,建议用 obsutil(华为云 OBS 命令行工具)或 CI/CD 流水线,Terraform 只负责基础设施(桶、权限、托管配置)。

Step 5自定义域名与 DNS 解析

OBS 默认的网站端点地址很长很难记(xxx.obs-website.cn-north-4.myhuaweicloud.com)。通常我们会绑定自己的域名,比如 blog.example.com。

这需要两步:

1. 在 OBS 桶上绑定自定义域名


main.tf
resource "huaweicloud_obs_bucket_custom_domain" "blog_domain" {
  bucket = huaweicloud_obs_bucket.website_bucket.id
  domain = "blog.example.com"
}

2. 在华为云 DNS 中添加 CNAME 记录

将 blog.example.com CNAME 到 OBS 网站端点:


main.tf
# 先查询或创建 DNS 公网 Zone
data "huaweicloud_dns_zone" "example_zone" {
  name = "example.com."
}

# 添加 CNAME 记录,指向 OBS 网站端点
resource "huaweicloud_dns_recordset" "blog_cname" {
  zone_id   = data.huaweicloud_dns_zone.example_zone.id
  name      = "blog.example.com."
  type      = "CNAME"
  ttl       = 300
  records   = [huaweicloud_obs_bucket.website_bucket.website_endpoint]
}

这里用到了 data 块——它表示"查询一个已存在的资源",而不是创建新的。我们假设 example.com 这个 DNS Zone 已经在华为云 DNS 服务中创建好了,只是查询它的 ID。

💡 data vs resource

resource 是"我要创建/管理这个资源",data 是"这个资源已经存在了,我只是要读取它的信息"。用 data 查询的资源不会被 Terraform 销毁。

3. 配置 HTTPS 证书(可选但推荐)

如果你需要 HTTPS 访问,需要在 OBS 桶上绑定 SSL 证书。证书可以通过华为云 SCM(证书管理服务)上传:


main.tf
resource "huaweicloud_scm_certificate" "blog_cert" {
  name              = "blog-example-com-cert"
  certificate       = file("${path.module}/certs/cert.pem")
  private_key       = file("${path.module}/certs/key.pem")
}

resource "huaweicloud_obs_bucket_custom_domain" "blog_domain_https" {
  bucket          = huaweicloud_obs_bucket.website_bucket.id
  domain          = "blog.example.com"
  certificate_id  = huaweicloud_scm_certificate.blog_cert.id
}

完整示例:一键部署

把前面所有步骤合在一起,完整的文件结构如下:


project structure
obs-static-website/
├── provider.tf          # Provider 配置
├── variables.tf         # 变量定义
├── main.tf              # 主资源定义
├── outputs.tf           # 输出值
├── templates/
│   ├── index.html       # 网站首页模板
│   └── 404.html         # 404 页面模板
└── certs/
    ├── cert.pem         # SSL 证书
    └── key.pem          # 私钥

variables.tf — 把可变参数抽出来:


variables.tf
variable "bucket_name" {
  description = "OBS 桶名称(全局唯一)"
  type        = string
}

variable "custom_domain" {
  description = "自定义域名"
  type        = string
  default     = null
}

variable "dns_zone_name" {
  description = "DNS Zone 名称"
  type        = string
  default     = null
}

main.tf — 完整资源定义:


main.tf
# ===== OBS 桶 =====
resource "huaweicloud_obs_bucket" "website" {
  bucket        = var.bucket_name
  storage_class = "STANDARD"
  acl           = "public-read"
}

# ===== 静态网站托管 =====
resource "huaweicloud_obs_bucket_website" "config" {
  bucket        = huaweicloud_obs_bucket.website.id
  index_document = "index.html"
  error_document = "404.html"
}

# ===== 上传首页 =====
resource "huaweicloud_obs_bucket_object" "index" {
  bucket       = huaweicloud_obs_bucket.website.id
  key          = "index.html"
  content      = file("${path.module}/templates/index.html")
  content_type = "text/html"
  acl          = "public-read"
}

# ===== 上传 404 页面 =====
resource "huaweicloud_obs_bucket_object" "error" {
  bucket       = huaweicloud_obs_bucket.website.id
  key          = "404.html"
  content      = file("${path.module}/templates/404.html")
  content_type = "text/html"
  acl          = "public-read"
}

# ===== 自定义域名 + DNS(可选)=====
resource "huaweicloud_obs_bucket_custom_domain" "custom" {
  count  = var.custom_domain != null ? 1 : 0
  bucket = huaweicloud_obs_bucket.website.id
  domain = var.custom_domain
}

data "huaweicloud_dns_zone" "zone" {
  count = var.dns_zone_name != null ? 1 : 0
  name  = var.dns_zone_name
}

resource "huaweicloud_dns_recordset" "cname" {
  count  = var.custom_domain != null ? 1 : 0
  zone_id = data.huaweicloud_dns_zone.zone[0].id
  name    = "${var.custom_domain}."
  type    = "CNAME"
  ttl     = 300
  records = [huaweicloud_obs_bucket.website.website_endpoint]
}

outputs.tf — 输出有用的信息:


outputs.tf
output "website_endpoint" {
  description = "OBS 网站访问端点"
  value       = huaweicloud_obs_bucket.website.website_endpoint
}

output "bucket_name" {
  description = "OBS 桶名称"
  value       = huaweicloud_obs_bucket.website.bucket
}

部署命令:


shell
terraform init
terraform plan -var "bucket_name=my-blog-2026" \
                  -var "custom_domain=blog.example.com" \
                  -var "dns_zone_name=example.com."
terraform apply -var "bucket_name=my-blog-2026" \
                   -var "custom_domain=blog.example.com" \
                   -var "dns_zone_name=example.com."

apply 完成后,Terraform 会输出网站端点地址,直接在浏览器打开就能看到你的静态网站了。

生命周期管理:plan / apply / destroy

Terraform 的三个核心命令构成了基础设施的完整生命周期:

命令 作用 会改变云资源吗
terraform plan 预览变更,生成执行计划 ❌ 不会
terraform apply 执行变更,创建/更新/删除资源 ✅ 会
terraform destroy 销毁所有 Terraform 管理的资源 ✅ 会(不可逆)

更新资源

修改 main.tf 中的任何属性,再 apply 即可。Terraform 会自动判断哪些资源需要更新:


shell
# 比如把存储类别改为低频访问
# 修改 main.tf: storage_class = "WARM"

terraform plan
# ~> storage_class: "STANDARD" -> "WARM"
# Plan: 0 to add, 1 to change, 0 to destroy.

terraform apply

~> 符号表示"将要修改"。

销毁资源


shell
terraform destroy
🚨 destroy 是不可逆的

terraform destroy 会删除所有由这些 .tf 文件管理的云资源。如果桶里有重要数据,请先备份。OBS 桶在销毁前需要先清空桶内所有对象,否则会报错——这是一种保护机制。

State 文件

Terraform 在执行后会在当前目录生成一个 terraform.tfstate 文件,记录了所有资源的当前状态。这个文件是 Terraform 管理资源的依据——plan 时就是对比 .tf 文件(期望状态)和 .tfstate(实际状态)的差异。

⚠️ State 文件安全

State 文件可能包含敏感信息(如密码、密钥)。不要提交到 Git,建议加入 .gitignore。团队协作时建议使用远程 backend(如 OBS 桶)存储 state。

常见问题与排坑

1. 桶名已存在


error
Error: BucketAlreadyExists: The requested bucket name is not available.

OBS 桶名全局唯一(不是你的账号内唯一,是整个华为云唯一)。换个名字就行。建议在桶名中加入项目前缀或随机后缀。

2. 桶不为空无法销毁


error
Error: BucketNotEmpty: The bucket you tried to delete is not empty.

OBS 要求桶必须清空才能删除。解决方法:先用 obsutil 或控制台清空桶内对象,再 terraform destroy。或者在 Terraform 中添加 force_destroy = true:


main.tf
resource "huaweicloud_obs_bucket" "website" {
  bucket        = var.bucket_name
  storage_class = "STANDARD"
  acl           = "public-read"
  force_destroy = true  # destroy 时自动清空桶内对象
}

3. Provider 版本不兼容

如果遇到 "Unsupported argument" 之类的错误,通常是 Provider 版本太低。检查 provider.tf 中的 version 约束,适当升级。

4. 网站访问 403

检查以下几点:

  • 桶 ACL 是否设为 public-read
  • 对象的 ACL 是否设为 public-read(桶 ACL 和对象 ACL 是独立的)
  • 是否通过网站端点访问(.obs-website.),而不是 REST 端点(.obs.)

总结

回顾一下本文的完整链路:

步骤 Terraform 资源 做了什么
Step 1 huaweicloud_obs_bucket 创建 OBS 桶
Step 2 ACL = public-read 设置公共读权限
Step 3 huaweicloud_obs_bucket_website 配置静态网站托管
Step 4 huaweicloud_obs_bucket_object 上传 index.html / 404.html
Step 5 huaweicloud_dns_recordset 绑定自定义域名 + DNS 解析

用 Terraform 管理基础设施的核心价值:

  • 一份代码就是一份文档——所有配置都在 .tf 文件中,不需要去控制台翻找
  • 一键复现——terraform apply 就能重建整套环境,适合多环境部署(dev/staging/prod)
  • 一键清理——terraform destroy 不会遗漏,不会有"僵尸资源"持续计费
  • 版本控制——.tf 文件用 Git 管理,每次基础设施变更都有 commit 记录,可追溯、可回滚

下一步,你可以尝试:

  • 把这套配置封装成 Terraform Module,在多个项目中复用
  • 接入 CI/CD 流水线,实现 git push 自动部署
  • 用 Terraform 管理更复杂的华为云资源(ECS、VPC、RDS、ELB…)

希望这篇博客能帮你迈出 IaC 的第一步。代码即基础设施,从此告别手动点击控制台。


【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。