什么是基础设施即代码(IaC)?
从手工运维到 IaC 的演进
在云计算普及之前,运维工程师需要手动安装服务器硬件、配置操作系统、安装软件。这个过程耗时且容易出错,且几乎无法精确重现。每台服务器都有自己的"雪花"状态——看似相同,实则略有差异,久而久之形成配置漂移(Configuration Drift)问题。
云计算改变了基础设施的供给方式,但手工在云控制台点点点依然存在同样的问题。基础设施即代码(Infrastructure as Code,IaC)用代码文件描述基础设施的期望状态,将基础设施管理纳入软件工程的最佳实践体系:版本控制、代码审查、自动化测试、持续集成。
命令式 vs 声明式
IaC 工具主要分为两种范式:
| 范式 | 思维方式 | 代表工具 | 特点 |
|---|---|---|---|
| 命令式 | 描述"如何做" | Ansible、Shell 脚本 | 按步骤执行,顺序重要,幂等性需手动保证 |
| 声明式 | 描述"要什么" | Terraform、Kubernetes | 描述目标状态,工具负责执行,天然幂等 |
Terraform 采用声明式方式:你只需在 HCL 文件中描述你想要的云资源("我需要一台 t3.micro EC2 实例,在 us-east-1 区域"),Terraform 负责计算并执行达到这个状态所需的操作(创建、修改或删除资源)。
声明式 IaC 的核心优势是幂等性:无论执行多少次,结果始终是你描述的目标状态。不会因为重复执行而创建多余的资源或产生意外副作用。
IaC 工具对比
Terraform 工作流
核心四步工作流
# 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
核心概念体系
安装 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
类似 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 CLI 后执行 aws configure,输入 Access Key ID 和 Secret Access Key。生产环境应使用 IAM Role 而不是长期凭证。
Terraform 工作流深度解析
terraform plan 实际做了什么?
Terraform vs OpenTofu
terraform 改为 tofu,语法和功能基本兼容。完全 MPL 2.0 开源,被 CNCF 接受为沙盒项目。适合需要避免 BUSL 许可证风险的场景。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.
某些属性修改会触发资源的"强制替换"(destroy + create)而非原地更新。例如修改 EC2 实例的 AMI、RDS 的标识符等。这会导致服务中断!plan 输出中会用 -/+ 标记,并在属性旁边显示 # forces replacement。修改前务必检查 plan 是否有 -/+ 操作,评估对业务的影响。
最佳实践:项目文件组织
Terraform 项目的文件组织没有强制规范,但以下约定被业界广泛采用:
项目规模增长时,推荐按环境+应用模块拆分目录,避免单个 State 管理所有资源:
environments/ ├── dev/ │ ├── main.tf # 调用模块,使用 dev 参数 │ └── variables.tf ├── staging/ └── prod/ modules/ ├── vpc/ ├── eks/ └── rds/
每个环境目录独立执行 terraform init/plan/apply,有独立的 State 文件,互不影响。
本章小结
- IaC 的本质是声明式:描述"期望状态"而非"操作步骤";Terraform 负责计算差异并执行变更,天然支持幂等执行。
- 核心工作流:init(下载 Provider)→ fmt/validate(代码质量)→ plan(计算差异)→ apply(执行变更)→ destroy(销毁资源)。
- State 是 Terraform 的核心:记录已管理资源的 ID、属性和依赖关系;plan 通过对比 State、实际状态和 .tf 配置三者的差异来生成变更计划。
- plan 输出符号:
+创建,~原地更新,-销毁,-/+强制替换(有中断);apply 前务必仔细核查-/+操作。 - Terraform vs OpenTofu:功能基本兼容;OpenTofu 完全开源(MPL 2.0);本课程的代码在两者上均可运行。
- 安装推荐 tfenv:在 .terraform-version 文件中锁定版本,避免团队成员版本不一致导致的行为差异。
- 文件组织约定:main.tf + variables.tf + outputs.tf + versions.tf 是最小标准集;大型项目用目录隔离不同环境。