DevOps 四大支柱的第三根:Infrastructure as Code(基础设施即代码)。
想想你现在的部署流程:手动 SSH 到服务器 → apt install → 改 Nginx 配置 → docker compose up。如果服务器挂了,你能在 5 分钟内重建吗?如果你管 10 台服务器,能保证配置一致吗?
IaC 的回答:把服务器、网络、防火墙、数据库——全部用代码定义。想改就改代码,想重建就跑命令,想回滚就 git revert。
Terraform 是 HashiCorp 出品的 IaC 工具。工作方式:
你写 .tf 文件 ──→ terraform plan(预览变更) ──→ terraform apply(执行变更)
(声明你想要什么) (看看会发生什么) (Do it)
核心概念:
| 概念 | 是什么 | 类比 |
|---|---|---|
| Provider | 「驱动」——Terraform 通过它操作具体平台 | 数据库驱动(mysql driver、pg driver) |
| Resource | 你要管理的一个东西(服务器、网络、域名…) | 一个 DOM 元素 |
| State | Terraform 记录的「当前世界是什么样」 | React 的 Virtual DOM |
| Plan | 「代码」和「现实」的 diff | git diff |
| Apply | 把 diff 变成现实 | git commit |
# Ubuntu
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [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 -y terraform
terraform version # 验证安装
不用云账号。用 Docker provider 在本地练习——概念和操作完全一样。
mkdir /tmp/terraform-demo && cd /tmp/terraform-demo
# main.tf —— 主配置文件
cat > main.tf << 'EOF'
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0"
}
}
}
provider "docker" {} # 连本机 Docker daemon
# 拉取 nginx 镜像
resource "docker_image" "nginx" {
name = "nginx:alpine"
keep_locally = true # 本地有就别再下载
}
# 创建 nginx 容器
resource "docker_container" "nginx" {
image = docker_image.nginx.image_id
name = "tf-nginx"
ports {
internal = 80
external = 8888 # 宿主机 8888 → 容器 80
}
}
# 输出:部署后告诉我怎么访问
output "url" {
value = "http://localhost:8888"
}
EOF
逐行解释:
terraform { required_providers {...} } — 声明需要哪些 provider(类似 package.json 的 dependencies)provider "docker" {} — 配置 provider(这里用默认配置,连本地 Docker socket)resource "docker_image" "nginx" — 声明一个资源:「我要一个 nginx:alpine 镜像」resource "docker_container" "nginx" — 声明另一个资源:「我要一个容器,用上面那个镜像」docker_image.nginx.image_id — 引用另一个资源的属性。Terraform 自动分析依赖顺序output "url" — 执行完输出一个值,方便你直接用cd /tmp/terraform-demo
# 1. 初始化(下载 provider,类似 npm install)
terraform init
# 2. 预览变更(不实际执行,看看会发生什么)
terraform plan
# 输出:+ create 表示要创建 2 个资源
# 3. 执行变更
terraform apply
# 输入 yes 确认
# 4. 验证
curl http://localhost:8888
# 看到 nginx 欢迎页!
docker ps --filter "name=tf-nginx"
# 看到 tf-nginx 容器在跑
# 5. 查看状态
terraform show # 显示当前所有资源
# 6. 销毁一切
terraform destroy
# 输入 yes 确认后,容器和镜像全部删除
现在改一下配置,看看 Terraform 怎么处理变更:
# 把端口从 8888 改成 9999
sed -i 's/8888/9999/' main.tf
terraform plan
# 输出:-/+ destroy and then recreate(检测到端口变化,需要重建容器)
terraform apply -auto-approve
# 跳过确认直接执行
curl http://localhost:9999
# 还是 nginx 欢迎页!
# variables.tf —— 声明变量
cat > variables.tf << 'EOF'
variable "app_port" {
description = "External port for the app"
type = number
default = 8888
}
variable "environment" {
description = "Deployment environment"
type = string
default = "dev"
}
EOF
# 在 main.tf 里用变量
sed -i 's/external = 9999/external = var.app_port/' main.tf
sed -i 's/name = "tf-nginx"/name = "tf-nginx-${var.environment}"/' main.tf
# 用不同参数运行
terraform apply -var="app_port=7777" -var="environment=prod" -auto-approve
# 容器名变成 tf-nginx-prod,端口 7777
Terraform 把「上次 apply 后世界长什么样」记录在 terraform.tfstate 文件里。这是它最核心的概念。
cat terraform.tfstate | python3 -m json.tool | head -20
# 看到所有资源的当前状态
团队协作时,state 文件不能存在本地——你同事的 state 跟你的不一样。解决方法:Remote State(存在 S3/GCS/Terraform Cloud 上),团队共享一份 state。
terraform.tfstate 到 Git。用 .gitignore 排除它,用 remote state backend 管理。
换成云 provider,语法完全一样:
provider "aws" {
region = "us-east-1"
}
resource "aws_security_group" "web" {
name = "web-sg"
description = "Allow HTTP and SSH"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # 允许所有人访问 80 端口
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # 允许所有人 SSH
}
egress {
from_port = 0
to_port = 0
protocol = "-1" # 允许所有出站流量
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0" # Ubuntu 22.04
instance_type = "t2.micro" # 免费套餐
security_groups = [aws_security_group.web.name]
key_name = "my-ssh-key"
user_data = <<-EOF
#!/bin/bash
apt update && apt install -y nginx
systemctl start nginx
EOF
tags = {
Name = "web-server"
}
}
output "public_ip" {
value = aws_instance.web.public_ip
}
跟 Docker provider 完全一样的模式:声明 provider → 声明 resource → 引用 → output。换 provider 就是换一套 resource 类型名,核心逻辑不变。
cd /tmp && rm -rf tf-practice && mkdir tf-practice && cd tf-practice
cat > main.tf << 'EOF'
terraform {
required_providers {
docker = { source = "kreuzwerker/docker"; version = "~> 3.0" }
}
}
provider "docker" {}
resource "docker_image" "nginx" {
name = "nginx:alpine"
keep_locally = true
}
resource "docker_container" "web" {
image = docker_image.nginx.image_id
name = "tf-web"
ports { internal = 80; external = 8080 }
}
output "access_url" {
value = "http://localhost:8080"
}
EOF
terraform init
terraform apply -auto-approve
curl http://localhost:8080
# 你的任务:
# 1. 再加一个容器(如 redis:alpine),暴露端口 6379
# 2. 用 terraform plan 看变更预览
# 3. terraform apply 创建
# 4. terraform destroy 清理
最后一课:监控 & 可观测性——你的服务上线了,怎么知道它还活着?怎么在用户发现之前知道出问题了?Prometheus + Grafana,DevOps 四大支柱的最后一根。
terraform destroy 之后确认 docker ps 容器确实没了。如果还在,手动 docker rm -f tf-nginx。Terraform 管理的东西不要手动改——会造成 state 和现实不一致。