基础设施即代码(IaC):Terraform入门、声明式配置管理、状态文件管理、模块化设计
好,咱们今天聊聊基础设施即代码。说白了,就是把以前靠鼠标点来点去的运维操作,变成写代码、存仓库、能回滚。我刚开始接触这个概念时,也觉得不就是自动化吗?后来踩了坑才明白,IaC 的核心不是自动化,而是「声明式」——你告诉系统最终要什么状态,它自己想办法去达成。
一、Terraform 入门:从零到第一个资源
Terraform 是目前最流行的 IaC 工具,没有之一。为什么?因为它多云、声明式、生态好。我个人习惯把 Terraform 当作「云资源的 Git」——你写代码描述资源,它帮你 diff、apply、destroy。
先看一个最简单的例子:在 AWS 上创建一个 EC2 实例。
# main.tf
provider "aws" {
region = "cn-north-1"
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "HelloWorld"
}
}
然后执行:
terraform init
terraform plan
terraform apply
嗯,这里要注意:terraform init 是必须的第一步,它会下载 provider 插件。我见过不少新手直接跳过 init 就 apply,结果报错一脸懵。
terraform fmt 格式化代码。这能帮你避免很多缩进导致的语法错误。
二、声明式配置管理:你只管「要什么」,别管「怎么要」
声明式和命令式的区别,我举个例子你就明白了。
命令式(像 Ansible 的某些写法):
- 先安装 Nginx
- 再复制配置文件
- 然后启动服务
声明式(Terraform 的写法):
- 我要一个 Nginx 服务,监听 80 端口,配置文件内容如下...
你想想看,声明式的好处是什么?幂等性。不管跑多少次,结果都一样。我在项目中遇到过一个问题:用命令式脚本部署,某次网络中断导致安装了一半,脚本报错退出。下次再跑,它以为 Nginx 已经装好了,直接跳过安装步骤,结果配置文件没复制成功——服务挂了。换成 Terraform 后,每次 apply 都会重新检查当前状态和目标状态是否一致,不一致就修正。
prevent_destroy 或 lifecycle 块。
三、状态文件管理:Terraform 的「大脑」
状态文件(terraform.tfstate)是 Terraform 的核心。它记录了当前所有资源的状态。说白了,Terraform 就是靠这个文件知道「现在有什么」和「目标要什么」之间的差距。
状态文件有多重要?我举个例子:
有一次,我手贱在 AWS 控制台手动删了一个安全组。然后跑 terraform apply,Terraform 发现状态文件里记录着这个安全组,但实际已经没了。它尝试重新创建,但创建时发现依赖的 VPC ID 变了——结果整个环境崩了。嗯,从那以后,我坚决不允许任何人手动修改云资源。
状态文件管理有几个关键点:
| 问题 | 解决方案 | 我的建议 |
|---|---|---|
| 本地文件丢失 | 使用远程后端(S3、OSS、Consul) | 团队协作必须用远程后端 |
| 多人同时操作 | 状态锁(DynamoDB、Consul) | 不加锁会出大问题 |
| 状态文件泄露 | 加密存储、访问控制 | 状态文件里可能有密码 |
| 状态文件损坏 | 定期备份、使用 terraform state 命令修复 |
我每周自动备份一次 |
terraform.tfstate 文件!我曾经见过有人用 Vim 打开状态文件改了一个 IP 地址,结果 Terraform 认为整个资源需要重建,导致线上服务中断了 20 分钟。如果你需要修改状态,请使用 terraform state mv、terraform state rm 等官方命令。
远程后端的配置示例:
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "prod/network/terraform.tfstate"
region = "cn-north-1"
dynamodb_table = "terraform-state-lock"
encrypt = true
}
}
配置好后,每次 terraform apply 都会自动从 S3 拉取最新状态,apply 完成后自动上传。多人协作时,DynamoDB 会加锁,防止两个人同时修改。
四、模块化设计:别把代码写成一坨
我刚开始用 Terraform 时,把所有资源都写在一个 main.tf 里。结果项目越来越大,一个文件上千行,找资源全靠 Ctrl+F。后来我学会了模块化——把可复用的资源封装成模块。
模块化设计的好处:
- 复用性: 同样的 VPC 配置,开发、测试、生产环境直接引用
- 可维护性: 每个模块只做一件事,改起来不担心影响其他地方
- 版本控制: 模块可以打 tag,不同环境用不同版本
一个典型的模块结构:
modules/
vpc/
main.tf
variables.tf
outputs.tf
ecs/
main.tf
variables.tf
outputs.tf
rds/
main.tf
variables.tf
outputs.tf
environments/
dev/
main.tf
terraform.tfvars
prod/
main.tf
terraform.tfvars
调用模块的方式:
# environments/dev/main.tf
module "vpc" {
source = "../../modules/vpc"
vpc_cidr = "10.0.0.0/16"
env_name = "dev"
}
module "ecs" {
source = "../../modules/ecs"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.public_subnet_ids
}
source = "git::https://gitlab.com/terraform-modules/vpc.git?ref=v1.2.0"。这样所有人都能拿到同一份代码。
模块设计时,有几个原则要记住:
- 输入输出明确: 用
variables.tf定义输入,用outputs.tf定义输出。不要直接在模块里写死值。 - 单一职责: 一个模块只管理一类资源。VPC 模块只管网络,别在里面创建 EC2。
- 版本化: 模块发布到 Git 仓库后,打上语义化版本标签。生产环境锁定版本,开发环境可以用 latest。
- 文档化: 每个模块写 README,说明输入参数、输出值、使用示例。相信我,三个月后你自己都会忘记这个模块怎么用。
最后说一句:Terraform 的学习曲线其实不陡,但状态文件管理和模块化设计这两个点,是区分「会用」和「用好」的分水岭。我见过太多人把 Terraform 用成了「高级版 CloudFormation」——所有代码写在一个文件里,状态文件存在本地,出了问题全靠手动恢复。嗯,别这样干。
下一章,咱们聊聊 CI/CD 流水线怎么和 Terraform 集成,实现真正的「自动化运维」。