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 # 销毁资源

planapply 的区分是这个工具最值得养成的习惯: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。这个文件是下次 planapply 的基准——没有它,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.tfvarsprod.tfvars),在命令里用 -var-file 指定,避免每次手工传参,也方便不同环境复用同一份代码。

一个落地案例

小团队要为一套博客系统搭「测试 / 生产」两套环境,手工在控制台建一次大约 40 分钟,还容易漏配置。用 Terraform 把 VPC、安全组、两台实例、RDS 数据库写成一个模块,跑 terraform apply -var-file=test.tfvars 五分钟内就能得到一套和上次完全一致的环境。要销毁也很干净——terraform destroy 一次把资源清空,不会留下孤儿实例继续产生费用。更进一步,把整套环境定义成 module,测试、预发、生产三个环境只是三个变量文件和三行命令的区别,这也是很多团队从「控制台点一点」走向「代码化」的第一步。

常见问题

  • apply 一半报错怎么办? 先跑 terraform plan 看状态文件里记录了什么,再决定是 apply 重试还是手动修正。不要绕过 Terraform 在控制台里改资源,状态会漂移。
  • 不小心改了别人的资源? 这正是状态文件存在的意义。不要直接删 tfstate,用 terraform state listterraform state rm 精确管理。
  • 要不要把 tfstate 提交到 Git? 不要。状态文件里常含敏感信息,且和机器绑定,应该放到远程后端并用锁保护。

参考:Terraform 官方文档 https://developer.hashicorp.com/terraform/docs ;AWS Provider 文档 https://registry.terraform.io/providers/hashicorp/aws/latest