Terraform 基础设施即代码入门:用代码管理云资源
先想一个常见场景:给一个项目搭测试环境,通常要在控制台里点半天——创建 VPC、安全组、几台实例、挂上负载均衡,然后再手动把配置文件填进每台机器。第一次做要半小时,做第二次、第三次还得半小时,而且两次结果往往还不一样,差一个端口、差一条规则,排查起来非常费劲。Terraform 要解决的正是这个问题:把基础设施写成声明式代码,跑一条命令就能重复地、一致地建出来。
Terraform 是 HashiCorp 开发的「基础设施即代码(IaC)」工具,用一个统一的 HCL 语法管理几乎所有主流云平台的资源——AWS、Azure、Google Cloud、阿里云、腾讯云都行。
一、安装与初始化
macOS 上最省事的方式是用 Homebrew:
# macOS
brew install terraform
# 验证安装
terraform version
安装好之后,一个项目通常按下面的命令循环推进:
terraform init # 初始化项目,下载 provider 插件
terraform plan # 预览变更,不实际执行
terraform apply # 执行变更,创建/修改资源
terraform destroy # 销毁资源
plan 和 apply 的区分是这个工具最值得养成的习惯:plan 会把将要发生的增删改逐条列出来,眼睛先扫一遍再决定要不要真动手,生产环境尤其依赖这步来避免误操作。
二、第一个配置
在项目目录建一个 main.tf,声明 AWS provider 和一台 Web 服务器:
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "WebServer"
}
}
resource "aws_security_group" "web_sg" {
name = "web-sg"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
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"]
}
}
这段配置声明了两件事:一台 t2.micro 实例,以及只开放 80/443 端口的安全组。注意 Terraform 的声明式特点——你描述「想要什么」,而不是「一步步怎么做」。实例和安全组之间的关联在代码里就能体现,比在控制台里按顺序点按钮清晰得多。
三、核心概念
| 概念 | 说明 |
|---|---|
| Provider | 云平台插件(AWS、Azure、GCP 等),负责和具体 API 对接 |
| Resource | 被管理的资源(实例、网络、存储桶等) |
| Data Source | 只读查询已有资源信息,不创建新资源 |
| State | 基础设施状态文件(terraform.tfstate) |
| Module | 可复用的配置模块,把一组资源打包 |
理解 Resource 和 Data Source 的差别很有用:Resource 会创建或修改真实资源,Data Source 只是「查一下现在有什么」。比如读取某个已有的 VPC ID,用 Data Source 就够,不必重新创建。
四、状态管理:单机的坑
terraform apply 之后,Terraform 会把当前资源的实际状态写进 terraform.tfstate。这个文件是下次 plan 和 apply 的基准——没有它,Terraform 不知道哪些资源是它管的。本地状态在单人项目里没问题,一旦两个人同时操作,就会互相覆盖,轻则重复创建资源,重则误删。
所以团队协作的标准做法是把状态放到远程后端,AWS 上最常见的是 S3,配合 DynamoDB 做锁:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
}
}
远程状态配合锁机制,能保证同一时刻只有一个 apply 在跑,存储成本和安全性都比本地文件靠谱。记得给这个 S3 桶开启版本控制,状态文件本身也能回滚。
五、变量与输出
把容易变的东西抽成变量,把有价值的信息暴露成输出:
# variables.tf
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
# outputs.tf
output "instance_ip" {
value = aws_instance.web.public_ip
}
使用变量时,可以在命令行用 -var 覆盖,也可以放在 terraform.tfvars 文件里。output 会把实例公网 IP 之类的信息打印出来,方便脚本接着用。
六、格式化与校验
写 Terraform 配置时养成两个习惯能省很多事。一是 terraform fmt,它会把代码格式化成官方风格,消除「这段是不是该缩进」的争论;二是 terraform validate,在 apply 之前先做一次静态校验,变量类型错误、块语法错误都能提前暴露。把它们放进 CI,合并请求时就自动检查,比部署到一半才发现拼写错误快得多。
变量文件也有讲究:环境相关的值放在 terraform.tfvars(比如 test.tfvars、prod.tfvars),在命令里用 -var-file 指定,避免每次手工传参,也方便不同环境复用同一份代码。
一个落地案例
小团队要为一套博客系统搭「测试 / 生产」两套环境,手工在控制台建一次大约 40 分钟,还容易漏配置。用 Terraform 把 VPC、安全组、两台实例、RDS 数据库写成一个模块,跑 terraform apply -var-file=test.tfvars 五分钟内就能得到一套和上次完全一致的环境。要销毁也很干净——terraform destroy 一次把资源清空,不会留下孤儿实例继续产生费用。更进一步,把整套环境定义成 module,测试、预发、生产三个环境只是三个变量文件和三行命令的区别,这也是很多团队从「控制台点一点」走向「代码化」的第一步。
常见问题
- apply 一半报错怎么办? 先跑
terraform plan看状态文件里记录了什么,再决定是apply重试还是手动修正。不要绕过 Terraform 在控制台里改资源,状态会漂移。 - 不小心改了别人的资源? 这正是状态文件存在的意义。不要直接删 tfstate,用
terraform state list和terraform state rm精确管理。 - 要不要把 tfstate 提交到 Git? 不要。状态文件里常含敏感信息,且和机器绑定,应该放到远程后端并用锁保护。
参考:Terraform 官方文档 https://developer.hashicorp.com/terraform/docs ;AWS Provider 文档 https://registry.terraform.io/providers/hashicorp/aws/latest