4.15 Terraform 基础设施即代码入门

预计阅读时间:15 分钟

📖 目录

学习目标

完成本章后,你将能够:

  • 理解基础设施即代码(IaC)的核心概念与价值
  • 掌握 HCL 语法,编写 Provider、Resource、Data Source 定义
  • 独立执行 init → plan → apply 全流程
  • 理解 State 文件的作用并配置远程 backend
  • 设计可复用的 Terraform 模块
  • 区分 Terraform 与 Ansible 的适用场景并配合使用

核心知识

概念说明
IaC用代码定义和管理基础设施,替代手动点击云控制台
HCLHashiCorp Configuration Language,Terraform 的声明式配置语言
Provider连接 Terraform 与云平台(阿里云、AWS、Azure)的插件
ResourceTerraform 管理的基础设施对象(VPC、ECS、RDS)
Data Source读取已有云资源信息,不创建新资源
State存储资源 ID 与真实云资源的映射关系,Terraform 的"大脑"
Module可复用的 Terraform 配置包,类似编程语言的函数库
Plan / ApplyPlan 预览变更,Apply 执行变更

知识关联

  • 前置知识6.10:云网络基础 云网络基础(VPC/CIDR 概念)、4.1:Ansible 自动化 Ansible 自动化配置管理入门(IaC 对比参照)
  • 后续影响6.7:GitOps 入门 GitOps 入门(Git 驱动基础设施)、6.10:云网络基础 云网络基础(Terraform 管理云网络资源)
  • 配套技术:Terraform + Ansible 互补(Provisioning vs Configuration),State 远程 backend(OSS/S3)实现团队协作

原理讲解

声明式 vs 命令式:Terraform 采用声明式模型——你定义"目标状态",Terraform 自动计算当前状态到目标状态的 diff 并执行。Ansible 则用命令式 Playbook 描述"怎么做"。

State 机制:Terraform 通过 terraform.tfstate 跟踪已创建的资源。每次 apply 时,Terraform 读取 State 文件对比 .tf 配置,生成增量变更计划。State 是 Terraform 的命脉——损坏或丢失 State 会导致 Terraform "失忆",无法管理已有资源。

工作流init(安装 Provider 插件)→ plan(预览,只读)→ apply(执行,写入 State)。多人团队需远程 backend 共享 State 并加锁防止并发冲突。

核心概念关系图

Terraform 的五个核心概念环环相扣,理解它们的关系比背命令更重要:

  • Provider 是"翻译官":把统一的 HCL 声明翻译成云平台 API 调用(阿里云、AWS、Azure、腾讯云各有各的 provider 插件)。一个配置里可以声明多个 provider,统一管理多云资源。
  • Resource 是"资产清单":每个 resource 块对应一朵真实的云资源(VPC、交换机、ECS、RDS、安全组)。
  • Data Source 是"只读查询":读取已有资源的信息供 Resource 引用——查镜像 ID、查现有 VPC、查可用区列表。它不创建、不修改任何东西,只提供数据。
  • State 是"账本":保存每朵资源的唯一 ID 和属性快照。apply 时 Terraform 用 State 对比配置算出 diff——没有 State 就不知道"现在有什么"。
  • Module 是"函数库":把一组 Resource 打包成可复用模块,用输入参数控制差异。

典型引用链:data.alicloud_images.ubuntu 查到镜像 ID → 传给 alicloud_instance.web.image_id → 实例创建后输出 IP → 交给 Ansible inventory 做配置管理。这五者的配合构成了 IaC 的完整闭环。

Plan / Apply 执行流程

terraform apply 内部并非"照着配置直接建",而是分六步:

  1. Refresh:向云 API 查询 State 中记录资源的真实状态;
  2. Diff:把 .tf 配置与 State 对比,找出新增/修改/删除的资源;
  3. 依赖排序:按引用关系拓扑排序(VPC 先于交换机,交换机先于 ECS);
  4. 输出计划:plan 展示每步动作(+ 创建 / ~ 更新 / - 删除),人工确认;
  5. Apply:按序调用 API,每步成功才更新 State,失败即停止(部分 Provider 支持 -target 精确执行);
  6. 输出结果:apply 成功后打印 output 值(如实例公网 IP)。
terraform plan    # 输出: Plan: 3 to add, 0 to change, 0 to destroy.
terraform apply --auto-approve   # 输出: Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
terraform apply -target=alicloud_instance.web   # 只执行某个资源(慎用,可能破坏依赖顺序)

Terraform vs Ansible 职责划分

维度TerraformAnsible
定位Provisioning(建资源)Configuration Management(管配置)
模型声明式(定义目标状态)命令式 Playbook(描述执行步骤)
有状态?有(State 记录资源 ID)无(幂等执行,每次从头跑)
生命周期创建/更新/销毁资源不销毁资源,只管内部配置
典型任务VPC、ECS、RDS、安全组、DNS 记录装软件、改配置、起服务、下发密钥
执行顺序依赖图自动排序按 Playbook 书写顺序
云平台集成原生 Provider 插件通过 cloud modules 间接支持

推荐组合:Terraform 创建 ECS 并输出 IP → Ansible dynamic inventory(AWS EC2 plugin 或 terraform-inventory)读取实例列表 → Ansible 安装 Nginx、部署代码、下发配置。一条流水线:terraform apply && ansible-playbook -i inventory.ini site.yml

为什么 Terraform 要用状态文件

Terraform 的 State 文件(terraform.tfstate)是其声明式模型的核心基础设施。没有 State,Terraform 无法回答三个关键问题:"现在有什么?"——State 记录了每个 Resource 的唯一 ID(如 i-0abc123),没有 ID 就无法调用云 API 查询或修改资源;"上次改了什么?"——apply 时 Terraform 对比 .tf 配置与 State 中记录的上一次状态,计算出增量 diff(新增/修改/删除),而非每次重新创建所有资源;"资源之间什么关系?"——State 保存了资源的属性值(如 VPC 的 CIDR、ECS 的 IP),其他 Resource 可以通过 alicloud_vpc.main.id 引用这些值。如果 State 丢失或损坏,Terraform 对已有资源"失忆"——它不知道这些资源的存在,再次 apply 可能尝试重复创建。因此 State 文件必须版本化存储(远程 backend + 锁),并禁止手动编辑。

Terraform vs Pulumi vs CloudFormation:IaC 方案对比

维度TerraformPulumiCloudFormation
语言HCL(专用 DSL)通用语言(Python/TypeScript/Go)YAML/JSON(模板)
云覆盖多云(AWS/Azure/GCP/阿里云)多云(通过 Provider)仅 AWS
状态管理State 文件(本地/远程)State 文件(Pulumi Cloud/本地)CloudFormation 服务端托管
学习曲线中(需学 HCL)低(用熟悉的编程语言)低(YAML 模板)
可编程性有限(HCL 循环/条件有限)高(完整编程语言能力)低(模板函数有限)
社区生态最大(Registry 3000+ Provider)增长中仅 AWS 生态
适用场景多云、团队协作、主流选择需要复杂逻辑的 IaC纯 AWS、CloudFormation 深度集成

选型建议:多云或需要最大社区支持 → Terraform;团队熟悉编程语言且需要复杂逻辑 → Pulumi;纯 AWS 且希望零运维 → CloudFormation。

为什么 Terraform 推荐 init → plan → apply 三步工作流

很多初学者觉得 terraform apply --auto-approve 更方便,但官方推荐严格遵循三步工作流:init——下载 Provider 插件和模块依赖,确保本地环境与配置匹配;plan——生成变更计划(+ 创建 / ~ 更新 / - 删除),这是唯一的安全检查点——在真正执行前预览所有变更,避免误操作;apply——确认 plan 结果后执行变更。plan 的价值在于"只读预览":它向云 API 查询资源真实状态,与 .tf 配置对比后输出精确的 diff。如果跳过 plan 直接 apply,你无法在执行前知道 Terraform 将要创建、修改或删除哪些资源——这在生产环境中是不可接受的风险。团队协作时 plan 输出还可以作为变更审批的依据。

示例代码

# Provider 配置(声明使用阿里云,region 决定资源部署地域)
provider "alicloud" {
  region = "cn-hangzhou"
}

# Data Source:查询可用镜像(只读查询,不创建资源)
data "alicloud_images" "ubuntu" {
  name_regex = "^ubuntu_22.*"
}

# Resource:创建 VPC(虚拟私有云,类似机房的逻辑隔离网络)
resource "alicloud_vpc" "main" {
  vpc_name   = "my-vpc"
  cidr_block = "10.0.0.0/8"
}

# Resource:创建交换机(VPC 内的子网,引用 VPC 的 ID 建立依赖关系)
resource "alicloud_vswitch" "main" {
  vpc_id     = alicloud_vpc.main.id
  cidr_block = "10.0.1.0/24"
  zone_id    = "cn-hangzhou-b"
}

# Resource:创建 ECS 实例(引用 Data Source 查到的镜像 ID 和交换机 ID)
resource "alicloud_instance" "web" {
  image_id      = data.alicloud_images.ubuntu.images[0].id
  instance_type = "ecs.t6-c1m1.large"
  vswitch_id    = alicloud_vswitch.main.id
  instance_name = "web-server"
}

# Output:apply 成功后打印结果(如实例公网 IP)
output "instance_ip" {
  value = alicloud_instance.web.public_ip
}
# 模块化示例:modules/vpc/main.tf
variable "env" {
  type = string
}
variable "vpc_cidr" {
  type    = string
  default = "10.0.0.0/16"
}
resource "alicloud_vpc" "this" {
  vpc_name   = "vpc-${var.env}"
  cidr_block = var.vpc_cidr
}
resource "alicloud_vswitch" "public" {
  count      = 2
  vpc_id     = alicloud_vpc.this.id
  cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
  zone_id    = "cn-hangzhou-${count.index == 0 ? "b" : "c"}"
}
output "vpc_id" {
  value = alicloud_vpc.this.id
}
# 使用模块
module "vpc_dev" {
  source   = "./modules/vpc"
  env      = "dev"
  vpc_cidr = "10.10.0.0/16"
}

# Data Source 示例:查询已有的 VPC
data "alicloud_vpcs" "existing" {
  name_regex = "my-vpc"
}

output "existing_vpc_id" {
  value = data.alicloud_vpcs.existing.vpcs[0].id
}

完整实战:AWS EC2 + 安全组

以 AWS 为例走一遍完整生命周期(云厂商只是 provider 差异,概念与阿里云一一对应):

# main.tf —— VPC + 子网 + 安全组 + EC2 实例
terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"   # 东京区域
}

# Data Source:查询最新的 Ubuntu 24.04 官方镜像
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]   # Canonical 官方账号
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-24.04-amd64-server-*"]
  }
}

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = { Name = "main-vpc" }
}

resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "ap-northeast-1a"
  map_public_ip_on_launch = true
}

resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Allow HTTP/SSH from office"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["203.0.113.0/24"]   # 只放行办公网段,禁止 0.0.0.0/0
  }
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.small"
  subnet_id     = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]
  key_name      = "ops-key"
  user_data = <<-EOF
    #!/bin/bash
    apt update && apt install -y nginx
  EOF
  tags = { Name = "web-01" }
}

output "web_ip" {
  value = aws_instance.web.public_ip
}
# 完整生命周期操作
terraform init                 # 输出: Terraform has been successfully initialized!
terraform plan                 # 输出: Plan: 5 to add, 0 to change, 0 to destroy.
terraform apply                # 输入 yes 执行,输出: Apply complete! ... web_ip = 54.x.x.x
curl http://$(terraform output -raw web_ip)   # 验证 nginx 欢迎页

# 增量变更:给安全组加 443 端口后再 apply
# 输出: Plan: 0 to add, 1 to change, 0 to destroy. ~ aws_security_group.web

# 销毁前必须先预览影响面
terraform plan -destroy       # 输出: Plan: 0 to add, 0 to change, 5 to destroy.
terraform destroy             # 输入 yes,资源按依赖逆序删除

常见错误

  • State 文件被手动修改或丢失:永远不要手动编辑 terraform.tfstate。误操作后可用 terraform state pull 检查,或用 terraform import 重新导入资源。
  • 多人同时 apply 未加锁:本地 backend 不锁,多人同时操作会覆盖 State。必须配置远程 backend(OSS/S3)天然支持锁。
  • 硬编码敏感信息:把 AccessKey、密码写在 .tf 文件中。改用变量 + sensitive = true 或环境变量 TF_VAR_xxx
  • 不理解 destroy 的风险terraform destroy 会删除 State 中记录的所有资源。先用 terraform plan -destroy 预览再执行。
  • 在 .tf 中使用 Tab 缩进:HCL 要求缩进统一。推荐编辑器安装 Terraform 插件自动格式化。

最佳实践

变量管理:variable / tfvars / 环境变量优先级

变量让同一份配置复用于 dev/staging/prod。取值优先级从高到低:

  1. -var 命令行参数(最高)
  2. *.auto.tfvars 自动加载的变量文件
  3. terraform.tfvars 默认变量文件
  4. 环境变量 TF_VAR_<变量名>
  5. variable 块内的 default
# variables.tf
variable "env" {
  type        = string
  description = "环境名"
  validation {
    condition     = contains(["dev", "staging", "prod"], var.env)
    error_message = "env 只能是 dev/staging/prod"
  }
}
variable "instance_type" {
  type    = string
  default = "t3.small"
}
variable "db_password" {
  type      = string
  sensitive = true   # apply 输出时打码,但注意 State 文件里仍是明文
}

# terraform.tfvars(明确不入库,敏感值走环境变量)
env            = "prod"
instance_type  = "t3.large"
# export TF_VAR_db_password='S3cr3t!'   # 用环境变量传敏感值

# 命令行覆盖(临时覆盖默认值)
terraform apply -var="instance_type=t3.xlarge" -var-file="prod.tfvars"

# 变量引用方式
resource "aws_instance" "web" {
  instance_type = var.instance_type
  tags = { Env = var.env }
}

State 管理:远程存储与迁移

单机开发时 state 存在本地 terraform.tfstate;团队协作必须放到远程,用防止并发 apply 互相覆盖。AWS 用 S3 + DynamoDB 锁:

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-company-terraform-state"
    key            = "prod/web/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"   # 锁表,防止并发冲突
  }
}

# 已有本地 state 时,init 会询问是否迁移
terraform init   # 输出: Do you want to copy existing state to the new backend? [yes/no]
# State 常用运维命令
terraform state list                  # 输出: aws_instance.web / aws_security_group.web ...
terraform state show aws_instance.web # 查看某个资源的完整属性
terraform state mv aws_instance.web module.web.aws_instance.web   # 重构模块后迁移 state 路径
terraform state rm aws_security_group.web   # 从 state 移除(云上资源保留,不再托管)
terraform import aws_instance.web i-0abc123   # 把已存在的实例纳入托管(需先写同名 resource 块)

# 事故恢复:远程 state 损坏/丢失时
terraform state pull > tfstate.backup.json   # 备份当前 state
terraform state push tfstate.backup.json     # 恢复(危险操作,push 会覆盖远端,务必先备份)

团队规范:state 文件禁止提交进 git(.gitignore 加 *.tfstate*);按环境分目录(dev/ prod/ 各自独立的 state key),或使用 terraform workspace 实现同名 state 隔离。

模块化:目录结构与发布

# 模块标准结构(modules/web/)
modules/web/
├── main.tf        # 资源定义
├── variables.tf   # 输入参数(必须显式声明)
├── outputs.tf     # 对外暴露的结果(IP、ID)
└── README.md      # 用法说明
# 模块文件内不要写 provider 配置,由根模块统一注入
# 根模块引用本地模块
module "web_prod" {
  source        = "./modules/web"
  env           = "prod"
  instance_type = "t3.large"
}
# 发布到 Git 后用 git URL 引用,任何同事都能复用
module "web_prod" {
  source  = "git::https://github.com/my-org/terraform-modules//modules/web?ref=v1.2.0"
  env     = "prod"
}
# 模块输出值的使用
output "web_public_ip" {
  value = module.web_prod.public_ip
}

模块设计经验:①变量全部显式声明,禁止模块内写死;②敏感输出标记 sensitive;③版本用 Git tag(ref=v1.2.0)而非 main 分支,保证可复现;④一个模块只做一件事(vpc 模块、ecs 模块、rds 模块分开);⑤公共模块可发布到 Terraform Registry,公开或私有均可。

  1. 远程 backend + 锁:团队协作必配 OSS/S3 backend,锁定 State 防止并发冲突。
terraform {
  backend "oss" {
    bucket  = "my-terraform-state"
    key     = "prod/network/terraform.tfstate"
    region  = "cn-hangzhou"
    encrypt = true
  }
}
  1. 模块化组织:按环境(dev/staging/prod)和组件(vpc/ecs/rds)拆分目录结构:
terraform/
├── modules/
│   ├── vpc/
│   ├── ecs/
│   └── rds/
├── dev/
│   ├── main.tf
│   └── terraform.tfvars
├── staging/
└── prod/
  1. 锁定 Provider 版本:用 required_providers 锁定大版本,避免无意升级破坏兼容性。
terraform {
  required_providers {
    alicloud = {
      source  = "aliyun/alicloud"
      version = "~> 1.200.0"
    }
  }
}
  1. 变量与输出:所有可配参数用 variable,不要硬编码;敏感信息标记 sensitive = true
  2. Terraform + Ansible 分工:Terraform 建资源 → 输出 IP → Ansible dynamic inventory 接管配置,两者配合而非替代。

练习题

  1. 写一个 main.tf,用阿里云 Provider 创建一个 VPC(CIDR 172.16.0.0/16)和一个交换机(CIDR 172.16.1.0/24),并输出 VPC ID。
  2. 给上面的配置加上 remote backend(OSS),在本地验证 terraform init 是否能正常初始化。
  3. 写一个 Data Source 查询当前账号已有的安全组,输出安全组 ID。
  4. 把 VPC 定义提取到 modules/vpc/ 中,然后在根模块引用它,传递不同的 CIDR 给 dev 和 staging 环境。
  5. 解释以下场景你选择 Terraform 还是 Ansible:①创建 RDS 实例;②在 ECS 上安装 Nginx;③管理 DNS 记录;④配置 PostgreSQL 参数。
点击查看答案
  1. 参考 chapter 内的 VPC 示例。output 块:output "vpc_id" { value = alicloud_vpc.main.id }terraform applyterraform output vpc_id 显示 ID。
  2. backend 配置:terraform { backend "oss" { bucket="tf-state" key="prod/network" region="cn-hangzhou" } }terraform init 会提示是否迁移 state。
  3. data "alicloud_security_groups" "all" {},output 显示 data.alicloud_security_groups.all.groups[*].id
  4. 模块结构:modules/vpc/main.tf 中定义 variable "cidr",根模块 module "dev" { source = "./modules/vpc" cidr = "172.16.0.0/16" }
  5. ① 选 Terraform(创建云资源);② 选 Ansible(配置已有 ECS 软件);③ 选 Terraform(DNS 记录是 IaC 资源);④ 选 Ansible(数据库参数是配置管理)。

学习检查点

学完本章后,请检验自己是否掌握以下内容:

检查项自测问题验证方法
概念理解能用自己的话解释 Terraform 的声明式语法和状态管理尝试向他人讲解
命令操作能不查文档完成 Terraform 初始化、规划、应用在终端实际执行
原理掌握能说出 Terraform 的资源依赖图和状态文件作用画出流程图
故障排查能独立排查 Terraform 执行错误、状态冲突、Provider 问题模拟故障并修复
最佳实践能说明为什么需要为 Terraform 配置远程状态存储和版本控制对比不同方案

本章总结

Terraform 是现代云基础设施管理的标准工具——声明式、有状态、模块化。

速查表

命令用途
terraform init初始化工作目录,安装 Provider 插件
terraform plan预览变更(只读)
terraform apply执行变更并写入 State
terraform destroy销毁所有托管的资源
terraform state list列出 State 中管理的所有资源
terraform import将已有资源纳入 Terraform 管理

入门三步

  1. .tf 文件定义 Provider、Resource、Data Source
  2. terraform init && terraform plan 初始化和预览
  3. terraform apply 执行并写入 State

团队协作务必配置远程 backend + 锁,避免多人同时修改 State。牢记 Terraform 管"建"、Ansible 管"配"的分工原则。

延伸阅读

常见问题

Terraform 和 Ansible 的定位有什么不同?
Terraform 是基础设施编排(Provisioning):创建和管理云资源(VM、网络、存储)。Ansible 是配置管理(Configuration):在已有资源上安装软件、修改配置、部署应用。互补而非替代。推荐工作流:Terraform 创建基础设施 → Ansible 配置服务器 → Terraform 更新基础设施配置变更。
Terraform State 文件怎么管理?
State 文件记录了已管理资源的映射关系,非常关键。不要将 state 存在本地。推荐:① 使用远程后端(Backend)存储,如 AWS S3 + DynamoDB Lock、阿里云 OSS + Table Store Lock;② 启用 state locking 防止多人同时操作;③ 定期 terraform state pull > backup.tfstate 备份。多人团队必须用远程后端。
terraform plan 和 apply 的区别?
plan 是预览阶段:比较代码和当前 state,显示要创建/修改/删除哪些资源(不做实际变更)。apply 是执行阶段:按照 plan 的结果实际创建/修改/删除资源。plan 的输出中的 + 表示新增,- 表示删除,~ 表示修改。[0m 前缀表示原地更新(in-place update),[1m 前缀表示销毁重建。
↑ 回到顶部