Chapter 01

IaC 基础与 Terraform/OpenTofu 架构

理解基础设施即代码的核心思想,掌握 Terraform 工作流与核心概念体系

什么是基础设施即代码(IaC)?

从手工运维到 IaC 的演进

在云计算普及之前,运维工程师需要手动安装服务器硬件、配置操作系统、安装软件。这个过程耗时且容易出错,且几乎无法精确重现。每台服务器都有自己的"雪花"状态——看似相同,实则略有差异,久而久之形成配置漂移(Configuration Drift)问题。

云计算改变了基础设施的供给方式,但手工在云控制台点点点依然存在同样的问题。基础设施即代码(Infrastructure as Code,IaC)用代码文件描述基础设施的期望状态,将基础设施管理纳入软件工程的最佳实践体系:版本控制、代码审查、自动化测试、持续集成。

命令式 vs 声明式

IaC 工具主要分为两种范式:

范式思维方式代表工具特点
命令式描述"如何做"Ansible、Shell 脚本按步骤执行,顺序重要,幂等性需手动保证
声明式描述"要什么"Terraform、Kubernetes描述目标状态,工具负责执行,天然幂等

Terraform 采用声明式方式:你只需在 HCL 文件中描述你想要的云资源("我需要一台 t3.micro EC2 实例,在 us-east-1 区域"),Terraform 负责计算并执行达到这个状态所需的操作(创建、修改或删除资源)。

幂等性(Idempotency)的重要性

声明式 IaC 的核心优势是幂等性:无论执行多少次,结果始终是你描述的目标状态。不会因为重复执行而创建多余的资源或产生意外副作用。

IaC 工具对比

Terraform / OpenTofu
最广泛使用的 IaC 工具。HCL 声明式语言,Provider 生态最丰富(3000+),支持几乎所有云平台和服务。本教程的主角。
Pulumi
用真实编程语言(TypeScript/Python/Go/Java)编写 IaC。适合开发者,可使用完整语言特性(循环、条件、函数),但需要运行时环境。
AWS CDK
亚马逊官方的 IaC 框架,底层编译为 CloudFormation。仅支持 AWS,但与 AWS 服务深度集成,类型安全。
Ansible
命令式配置管理工具,YAML Playbook 语法,擅长软件配置和部署(而非资源供给),常与 Terraform 配合使用。
OpenTofu
2023 年从 Terraform fork 的开源替代品,由 Linux Foundation 托管,100% 兼容 Terraform 语法,持续增加新特性。

Terraform 工作流

核心四步工作流

┌─────────────────────────────────────────────────┐ │ Terraform 工作流 │ │ │ │ 1. terraform init │ │ 初始化工作目录,下载 Provider 插件 │ │ ↓ │ │ 2. terraform plan │ │ 预览变更:对比当前 state 与配置,生成执行计划 │ │ ↓ │ │ 3. terraform apply │ │ 执行变更:创建/修改/删除云资源 │ │ ↓ │ │ 4. terraform destroy │ │ 销毁所有托管资源(谨慎!) │ └─────────────────────────────────────────────────┘
# 1. 初始化:下载 Provider 插件,初始化 backend
terraform init

# 2. 格式化:统一代码风格
terraform fmt

# 3. 验证:检查语法错误
terraform validate

# 4. 计划:预览变更(不执行)
terraform plan

# 5. 保存 plan 到文件(供 CI/CD 使用)
terraform plan -out=tfplan

# 6. 执行变更
terraform apply

# 7. 执行保存的 plan(CI/CD 推荐方式)
terraform apply tfplan

# 8. 跳过交互确认(CI/CD 场景)
terraform apply -auto-approve

# 9. 销毁所有资源(谨慎!)
terraform destroy

核心概念体系

Provider
与云平台或服务交互的插件。每个 Provider 封装了特定平台的 API(如 aws、google、azurerm)。在 registry.terraform.io 中注册,使用前需在 required_providers 中声明并 init 下载。
Resource
基础设施中的一个具体对象,如一台 EC2 实例、一个 S3 Bucket、一条 DNS 记录。格式:resource "provider_type" "local_name" {}。
Data Source
查询现有资源的信息(只读,不创建)。例如查询最新的 AMI ID、已有的 VPC 信息,供 resource 引用。
State(状态)
Terraform 用 terraform.tfstate 文件记录它管理的所有资源的当前状态。这是 Terraform 的"记忆",用于 plan 时对比目标配置与真实状态的差异。
Module(模块)
一组 .tf 文件的集合,封装可复用的基础设施逻辑。可以从本地目录、Git 仓库或 Terraform Registry 引用模块。
Workspace(工作空间)
同一套配置代码的不同状态实例,用于区分 dev/staging/prod 等环境。每个 workspace 有独立的 state 文件。
Backend(后端)
state 文件的存储位置。默认存在本地,生产环境应使用远程后端(S3、GCS、Terraform Cloud),支持多人协作和状态锁定。

安装 Terraform / OpenTofu

# macOS:使用 Homebrew 安装 Terraform
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# macOS:安装 OpenTofu(开源替代)
brew install opentofu

# Linux(Debian/Ubuntu)
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

# Windows:使用 Chocolatey
choco install terraform

# 验证安装
terraform version
# Terraform v1.9.x

# OpenTofu 版本(命令相同)
tofu version
推荐使用 tfenv 管理版本

类似 nvm,tfenv 可以在项目间切换不同版本的 Terraform:brew install tfenv && tfenv install 1.9.0 && tfenv use 1.9.0。在 .terraform-version 文件中固定项目版本,避免版本不一致问题。

第一个 Terraform 配置

# main.tf — 创建一个 AWS S3 Bucket

# 声明需要的 Provider
terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# 配置 AWS Provider
provider "aws" {
  region = "us-east-1"
}

# 创建 S3 Bucket 资源
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-unique-bucket-name-2024"

  tags = {
    Name        = "My Bucket"
    Environment = "Dev"
    ManagedBy   = "Terraform"
  }
}

# 输出 Bucket 的 ARN
output "bucket_arn" {
  value = aws_s3_bucket.my_bucket.arn
}
使用 AWS 前需配置凭证

运行上述示例前,需要配置 AWS 凭证。最简单的方式:安装 AWS CLI 后执行 aws configure,输入 Access Key ID 和 Secret Access Key。生产环境应使用 IAM Role 而不是长期凭证。

Terraform 工作流深度解析

terraform plan 实际做了什么?

terraform plan 的执行过程 1. 读取 State(从本地 terraform.tfstate 或远程 Backend) → 了解 Terraform 当前管理的所有资源 2. 刷新 State(调用 Provider API 查询真实资源状态) → 对比 State 记录与云上实际状态 → 检测配置漂移(资源被手工修改过) 3. 读取 .tf 配置文件 → 解析期望的目标状态 4. 计算差异并生成变更计划: + 绿色:需要创建的资源 ~ 黄色:需要原地更新的资源(无中断) - 红色:需要销毁的资源 -/+ 红色:需要销毁并重建(强制替换,有中断)

Terraform vs OpenTofu

OpenTofu
Terraform 的社区 fork,命令从 terraform 改为 tofu,语法和功能基本兼容。完全 MPL 2.0 开源,被 CNCF 接受为沙盒项目。适合需要避免 BUSL 许可证风险的场景。
Terraform(HashiCorp)
原版工具,与 HCP Terraform 深度集成。BUSL 1.1 许可证仅限制「提供托管 Terraform 服务的竞争者」,普通用户和企业内部使用不受影响。
Terragrunt
Terraform/OpenTofu 的 wrapper 工具,解决了多环境代码重复、Backend 配置 DRY、多模块编排等问题。适合大型多账号、多区域基础设施。

Terraform 核心命令详解

# ===== 初始化命令 =====

# 初始化工作目录(下载 Provider,初始化 Backend)
terraform init

# 升级 Provider 到满足约束的最新版本(同时更新 .terraform.lock.hcl)
terraform init -upgrade

# 将现有 State 迁移到新的远程 Backend
terraform init -migrate-state

# ===== 代码质量命令 =====

# 格式化所有 .tf 文件(标准化缩进、对齐等号)
terraform fmt
# 格式化并显示修改的文件
terraform fmt -diff
# 递归格式化子目录
terraform fmt -recursive

# 验证配置语法(不调用 API,不检查资源是否合法)
terraform validate

# ===== Plan 命令 =====

# 基本 plan(显示变更预览)
terraform plan

# 保存 plan 到文件(CI/CD 中常用:先 plan 审批,再 apply)
terraform plan -out=tfplan.binary

# 只计划 destroy(预览将要删除什么)
terraform plan -destroy

# 跳过 Refresh(不查询真实状态,加速 plan,但可能遗漏漂移)
terraform plan -refresh=false

# 只计划特定资源(不影响其他资源)
terraform plan -target=aws_instance.web

# ===== Apply 命令 =====

# 交互式 apply(需要手动输入 yes 确认)
terraform apply

# 执行之前保存的 plan(跳过确认步骤,CI/CD 推荐)
terraform apply tfplan.binary

# 跳过交互确认(自动化场景)
terraform apply -auto-approve

# 覆盖命令行变量
terraform apply -var="environment=prod" -var="region=us-west-2"

# 使用变量文件
terraform apply -var-file="prod.tfvars"

# 强制替换指定资源(先 destroy 再 create)
terraform apply -replace=aws_instance.web

# ===== 销毁命令 =====

# 销毁所有 Terraform 管理的资源(生产环境需谨慎!)
terraform destroy

# 等价写法
terraform apply -destroy

# ===== 查看命令 =====

# 查看当前 State 中的所有资源
terraform show

# 以 JSON 格式输出(便于 jq 处理)
terraform show -json | jq '.values.root_module.resources | length'

# 查看已定义的输出值
terraform output

# 查看特定输出值
terraform output load_balancer_dns

# 以 JSON 格式查看所有输出(便于脚本读取)
terraform output -json

plan 输出解读

理解 terraform plan 的输出符号对日常使用至关重要:

# plan 输出示例

Terraform will perform the following actions:

  # aws_instance.web will be created
  + resource "aws_instance" "web" {    # + 绿色:将被创建
      + ami           = "ami-0c55b159"
      + instance_type = "t3.micro"
      + id            = (known after apply)  # apply 后才知道 ID
    }

  # aws_security_group.web will be updated in-place
  ~ resource "aws_security_group" "web" {    # ~ 黄色:原地更新(无中断)
      ~ description = "old description" -> "new description"
      # (2 unchanged attributes hidden)
    }

  # aws_instance.old_server will be destroyed
  - resource "aws_instance" "old_server" {    # - 红色:将被销毁
      - ami           = "ami-abc123" -> null
    }

  # aws_db_instance.main must be replaced
  -/+ resource "aws_db_instance" "main" {   # -/+ 红色:销毁并重建(有中断!)
      ~ identifier = "old-name" -> "new-name"  # forces replacement
    }

Plan: 1 to add, 1 to change, 1 to destroy, 1 to replace.
注意 -/+(forces replacement)

某些属性修改会触发资源的"强制替换"(destroy + create)而非原地更新。例如修改 EC2 实例的 AMI、RDS 的标识符等。这会导致服务中断!plan 输出中会用 -/+ 标记,并在属性旁边显示 # forces replacement。修改前务必检查 plan 是否有 -/+ 操作,评估对业务的影响。

最佳实践:项目文件组织

Terraform 项目的文件组织没有强制规范,但以下约定被业界广泛采用:

terraform-project/ ├── main.tf # 主要资源定义(入口文件) ├── variables.tf # 所有 variable 块(集中管理输入) ├── outputs.tf # 所有 output 块(集中管理输出) ├── locals.tf # 局部变量(本地计算值) ├── versions.tf # terraform 块和 required_providers(版本约束) ├── provider.tf # provider 块配置(认证、region 等) ├── data.tf # data source 块(可选,查询已有资源) ├── terraform.tfvars # 默认变量值(不含密钥,可提交 Git) ├── prod.tfvars # 生产环境变量(不含密钥) ├── .gitignore # 忽略 .terraform/, *.tfstate, *.tfvars(含密钥时) └── .terraform.lock.hcl # Provider 锁文件(应提交 Git)
大型项目的目录结构

项目规模增长时,推荐按环境+应用模块拆分目录,避免单个 State 管理所有资源:

environments/
├── dev/
│   ├── main.tf     # 调用模块,使用 dev 参数
│   └── variables.tf
├── staging/
└── prod/
modules/
├── vpc/
├── eks/
└── rds/

每个环境目录独立执行 terraform init/plan/apply,有独立的 State 文件,互不影响。

本章小结

本章核心要点