☰
Terraform实战:在Ubuntu 22.04上用代码自动化管理AWS云资源
2026/9/28 6:52:20 网站建设 项目流程

如果你还在用AWS控制台一个个点鼠标创建EC2、VPC、安全组,那你一定受够了那份繁琐。资源一多,除了手工重复劳动,最怕的就是改错配置后忘了改回去,结果月末账单出来的时候血压飙升。Terraform就是来解决这个问题的:它把云端基础设施当作代码来管理,你在Ubuntu 22.04上写一份声明式配置文件,通过一套标准化命令就能完成AWS资源的创建、更新、删除,整个过程可复现、可审计、可版本化。这篇教程适合正在使用AWS、希望摆脱手动部署、想让团队协作更顺畅的运维或开发工程师。我会从环境准备讲起,一步步带你完成从代码编写到实际部署的完整闭环,并分享一些常规文档里不会写的踩坑经验。

1. 环境准备:Ubuntu 22.04 上安装 Terraform 与配置 AWS 凭证

1.1 为什么是 Ubuntu 22.04 和 Terraform

Ubuntu 22.04 LTS 是一个长期支持版本,稳定性有保障,而且软件仓库更新及时,很多云厂商的CLI工具和第三方软件都优先适配它。Terraform 是 HashiCorp 出品的开源基础设施即代码工具,生态成熟,社区插件(Provider)覆盖面广,AWS Provider 由官方维护,功能几乎和AWS API同步更新。

我见过不少团队直接用apt install terraform,但那个版本往往落后好几代,配置文件语法稍有不兼容就会让plan报错。所以我更推荐从官方渠道安装二进制包,保证版本可控。

1.2 安装 Terraform 的实操步骤

先确认系统架构,通常是amd64。然后到 HashiCorp 官网下载对应版本,我用的是比较稳定的 1.5.x 系列。步骤很简单:

# 下载并解压 sudo apt install unzip -y wget https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_linux_amd64.zip unzip terraform_1.5.7_linux_amd64.zip # 移动到 PATH 目录 sudo mv terraform /usr/local/bin/ # 验证 terraform version

如果你习惯用bash或zsh,可以顺手配置命令补全:

terraform -install-autocomplete

install-autocomplete这个命令实际不存在,正确做法是:

touch ~/.bashrc complete -C /usr/local/bin/terraform terraform

不过更常见的方式是让 shell 直接加载 Terraform 自带的补全脚本。我实测下来,source <(terraform -autocomplete)在某些版本里会有提示,最稳妥的就是加一行:

echo 'complete -C /usr/local/bin/terraform terraform' >> ~/.bashrc source ~/.bashrc

这时候输入terraform p再按 Tab,应该能补全plan、providers等子命令,效率高不少。

1.3 配置 AWS 命令行工具和凭证

Terraform 本身不负责认证,它默认读取 AWS 标准凭证体系。首先要安装awscli:

sudo apt update sudo apt install awscli -y aws --version

然后创建一个 IAM 用户,这里有一个值得养成的好习惯:永远不要用根用户的 Access Key。在 AWS 控制台创建用户时,只给最小权限。比如后面教程里需要创建 VPC 和 EC2,就赋AmazonEC2FullAccess和AmazonVPCFullAccess,外加AmazonS3FullAccess(用于远程状态存储)。生产环境建议直接用权限边界或者 IAM Role,让 Terraform 通过 instance profile 获取凭证,而不是把 Key 放在本地。

本地配置:

aws configure # 输入 Access Key ID、Secret Access Key、默认 region、输出格式

检查凭证是否生效:

aws sts get-caller-identity

这一步能快速确认~/.aws/credentials是否配置正确,比直接跑 Terraform 定位认证问题要快得多。

有一点容易踩坑:如果你在~/.bashrc里设置了AWS_PROFILE或AWS_ACCESS_KEY_ID环境变量,Terraform 会优先读取它们。有时候你改了aws configure却仍然报错,八成就是环境变量覆盖了配置文件。我习惯用unset AWS_PROFILE来排除干扰。

2. Terraform 核心概念:先搞懂这些再动手

2.1 声明式配置与资源模型

Terraform 用的是声明式语言 HCL,你只需要描述“最终要变成什么样”,它自己会算出一个“从现状到目标”的执行计划。这和命令式脚本(比如直接写aws ec2 run-instances)有本质区别:声明式的最大好处是幂等,同一份配置反复执行,最终状态一致,不会重复创建资源。

它的核心对象是resource,比如一个 EC2 实例就是一个 resource。还有一个容易混淆的是data,它用来读取已有资源的信息,而不是创建新资源。比如你想查询当前账号下某个 AMI 的 ID,就用data "aws_ami" "ubuntu" {...}。

2.2 配置文件结构与常用语法

一个标准的 Terraform 项目通常包含这几个文件:

  • main.tf:主配置,定义资源
  • variables.tf:声明输入变量
  • outputs.tf:定义输出值
  • terraform.tfvars:给变量赋值

一个好的实践是把provider和terraform块放在main.tf顶部:

terraform { required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = var.region }

required_providers指定插件来源和版本,这样即使不同机器上执行,也能锁定一致的环境。

变量定义(variables.tf):

variable "region" { description = "AWS region" type = string default = "us-east-1" }

输出定义(outputs.tf):

output "instance_id" { value = aws_instance.web.id }

terraform.tfvars文件里写具体的值:

region = "ap-southeast-1"

这里有个细节:terraform.tfvars里的变量名必须和variables.tf里声明的一致,否则运行plan时会警告no value assigned to variable。如果你用.tfvars.json格式(比如为了配合 CI/CD),里面语法是 JSON,别写错了。

3. 从零编写第一个 AWS 基础设施:VPC 和 EC2 实例

3.1 规划你的基础设施

在写代码之前,先在纸上把拓扑理清楚。我们要创建:

  • 一个 VPC,CIDR 使用10.0.0.0/16
  • 一个公有子网,CIDR 使用10.0.1.0/24
  • 一个互联网网关,让实例能访问公网
  • 一个安全组,放行 SSH 和 HTTP
  • 一个 EC2 实例,部署一个简单的 Web 服务

这种规划方式能避免后面频繁修改 CIDR,因为 VPC 的 CIDR 一旦定下来,再改就需要重建,很麻烦。

3.2 编写完整的 main.tf

我在这里直接给你一份可以用起来的代码,注释尽量写得清楚一点:

# 定义VPC resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" enable_dns_support = true enable_dns_hostnames = true tags = { Name = "terraform-demo-vpc" } } # 公有子网 resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" availability_zone = "us-east-1a" map_public_ip_on_launch = true tags = { Name = "terraform-demo-subnet" } } # 互联网网关 resource "aws_internet_gateway" "gw" { vpc_id = aws_vpc.main.id tags = { Name = "terraform-demo-igw" } } # 路由表 resource "aws_route_table" "public" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.gw.id } tags = { Name = "terraform-demo-route" } } resource "aws_route_table_association" "public" { subnet_id = aws_subnet.public.id route_table_id = aws_route_table.public.id } # 安全组 resource "aws_security_group" "web" { name = "terraform-demo-web-sg" description = "Allow SSH and HTTP" vpc_id = aws_vpc.main.id ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 80 to_port = 80 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"] } } # EC2实例 resource "aws_instance" "web" { ami = "ami-0c02fb55956c15836" # Ubuntu 22.04 LTS us-east-1 instance_type = "t3.micro" subnet_id = aws_subnet.public.id vpc_security_group_ids = [aws_security_group.web.id] associate_public_ip_address = true user_data = <<-EOF #!/bin/bash apt-get update apt-get install -y nginx systemctl enable nginx systemctl start nginx EOF tags = { Name = "terraform-demo-web" } }

代码里的核心逻辑就是隐式依赖:aws_subnet.public引用了aws_vpc.main.id,Terraform 会自动分析出依赖关系,按顺序创建。你不需要显式写depends_on,只有遇到循环依赖的时候才需要手动处理。

user_data那段脚本是用来在实例启动时安装 nginx 的,这就是“自动化部署”的雏形。如果你想让实例真正提供 Web 服务,建议配合aws_ami数据源去查最新 Ubuntu AMI,而不是硬编码一个可能过期的 ID。

3.3 使用 terraform init 初始化

写完文件后,进入项目目录执行:

terraform init

init会下载 AWS Provider 插件,并生成.terraform目录和.terraform.lock.hcl锁定文件。这个锁定文件很重要,它记录了你实际使用的 Provider 版本,提交到 Git 之后能保证团队其他人和你用一模一样的插件版本。我见过因为版本漂移导致的plan结果不一致,排查半天才发现是 Provider 版本不同。

初始化完成后,目录结构看起来像这样:

. ├── main.tf ├── variables.tf ├── outputs.tf ├── terraform.tfvars └── .terraform/ └── providers/

注意一点:terraform init不用每次执行,只有当你修改了required_providers或者切换模块、重新下载插件时才需要再次运行。

4. 自动化部署流程:plan、apply 与状态管理

4.1 terraform plan:预览变更

plan是 Terraform 最让我喜欢的一步,因为它让你在真正动手之前看清所有变更。执行:

terraform plan -out=plan.tfplan

输出里会以+号列出将要创建的资源。重点看这个摘要:

Plan: 6 to add, 0 to change, 0 to destroy.

这一步千万别全信命令行提示,尤其要注意destroy的条目。我在一次演示中想临时删掉一个安全组,结果plan提示要同时销毁依赖它的 EC2,幸好提前看见了,否则就是一次生产事故。建议把plan的输出保存下来,review 之后再执行apply。

4.2 terraform apply:执行部署

确认无误后:

terraform apply plan.tfplan

这里使用-out生成的 plan 文件,好处是它与当时的代码和状态完全一致,不会因为有人中途改配置文件而出现偏差。如果你直接执行terraform apply而不带 plan 文件,它会重新计算一次计划然后询问你,适合快速测试环境。

等待执行完成,看到Apply complete就表示基本成功了。接下来验证资源:

aws ec2 describe-instances --filters "Name=tag:Name,Values=terraform-demo-web"

或者直接从outputs里获取信息:

terraform output instance_public_ip

4.3 状态文件与远程存储

Terraform 会把所有资源的状态记录在terraform.tfstate文件里。这个文件是 JSON 格式,包含资源属性与真实云计算资源之间的映射关系。如果多人协作,每个人都保存在本地,那就等着相互覆盖吧。所以必须用远程状态存储。

最简单可靠的方式是把 state 存在 S3,并用 DynamoDB 做锁:

terraform { backend "s3" { bucket = "my-terraform-state-bucket" key = "prod/terraform.tfstate" region = "us-east-1" dynamodb_table = "terraform-lock" encrypt = true } }

改完后重新terraform init,它会提示你迁移现有状态文件到 S3。

我当时忽略了一个细节:S3 bucket 必须开启版本控制,否则误删或损坏的 state 文件无法恢复。DynamoDB 表也至少要有一个主键(通常是LockID),否则加锁时会报错。这些可以在 Terraform 配置里一并创建,不一定要手动去控制台操作。

5. 实战中的常见坑与优化建议

5.1 凭证与权限问题

最常见的一个报错是:

Error: No valid credential sources found for AWS Provider.

遇到这种问题,按顺序排查:aws configure是否正确、环境变量是否干扰、IAM 用户是否有权限。还有一个隐蔽点:如果你的 region 是cn-north-1,用us-east-1的 Provider 版本会出现 endpoint 错误,这时需要单独配置endpoints或者用国内的 AWS Provider 版本。

5.2 依赖关系与循环引用

HCL 的隐式依赖虽然方便,但一旦多了就容易绕晕。比如 A 引用 B,B 又引用了 A,Terraform 会报 “Cycle” 错误。解决办法是把其中一个资源的属性用数据源或变量替代,打破循环。记住一个原则:resource之间的引用只能在创建顺序上单向流动,不能反向。

还有一种坑是count和for_each用的 key 不稳定,导致每个plan都显示大量资源要重建。比如你用count = length(var.instances),一旦在中间插入一个元素,所有实例索引都会偏移,Terraform 会认为它们全部要重建。按顺序追加没事,但中间插入就会悲剧。如果要保持稳定,建议用for_each且 key 用实例的名字而不是索引。

5.3 团队协作与代码规范

我强烈推荐三个命令:

terraform fmt -recursive terraform validate terraform-docs markdown .

其中fmt自动格式化代码,validate检查语法错误,terraform-docs自动生成变量和输出文档。在 CI 里加入这三个步骤,能省掉很多无意义的代码 review 争论。

团队协作还有一个关键点:用 Git 分支管理基础设施变更。比如在main分支对应prod远程 State,在develop分支对应dev环境 State。只要 backend 里的key不同就能做到隔离。我见过一支团队把多个环境的 State 放在同一个 key 下,结果每次切换分支都会互相干扰,这属于典型的配置设计失误。

结尾:一点个人体会

从第一次手动建 VPC 到用 Terraform 一键拉起完整环境,你会发现真正的效率提升不是省去了点击鼠标的时间,而是建立起了一套“可讨论、可回滚、可重构”的基础设施管理方式。我在实际部署中最深刻的体会是:terraform plan是一次免费的“预演”,遇到任何不确定的变更都先跑一遍 plan,再决定要不要 apply,这比盯着控制台臆想结果可靠得多。最后再分享一个小技巧:把常用的plan、apply写成 shell 脚本或 Makefile 封装一下,比如统一加载环境变量、自动在命令后追加-no-color方便保存日志,这样团队里新手也能快速上手而不至于操作失误。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询