Terraform实战:在Ubuntu 22.04上用代码自动化管理AWS云资源
如果你还在用AWS控制台一个个点鼠标创建EC2、VPC、安全组那你一定受够了那份繁琐。资源一多除了手工重复劳动最怕的就是改错配置后忘了改回去结果月末账单出来的时候血压飙升。Terraform就是来解决这个问题的它把云端基础设施当作代码来管理你在Ubuntu 22.04上写一份声明式配置文件通过一套标准化命令就能完成AWS资源的创建、更新、删除整个过程可复现、可审计、可版本化。这篇教程适合正在使用AWS、希望摆脱手动部署、想让团队协作更顺畅的运维或开发工程师。我会从环境准备讲起一步步带你完成从代码编写到实际部署的完整闭环并分享一些常规文档里不会写的踩坑经验。1. 环境准备Ubuntu 22.04 上安装 Terraform 与配置 AWS 凭证1.1 为什么是 Ubuntu 22.04 和 TerraformUbuntu 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-autocompleteinstall-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 标准凭证体系。首先要安装awsclisudo 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.tfvariable region { description AWS region type string default us-east-1 }输出定义outputs.tfoutput 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 规划你的基础设施在写代码之前先在纸上把拓扑理清楚。我们要创建一个 VPCCIDR 使用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.idTerraform 会自动分析出依赖关系按顺序创建。你不需要显式写depends_on只有遇到循环依赖的时候才需要手动处理。user_data那段脚本是用来在实例启动时安装 nginx 的这就是“自动化部署”的雏形。如果你想让实例真正提供 Web 服务建议配合aws_ami数据源去查最新 Ubuntu AMI而不是硬编码一个可能过期的 ID。3.3 使用 terraform init 初始化写完文件后进入项目目录执行terraform initinit会下载 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 -outplan.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 Nametag:Name,Valuesterraform-demo-web或者直接从outputs里获取信息terraform output instance_public_ip4.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 引用 BB 又引用了 ATerraform 会报 “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方便保存日志这样团队里新手也能快速上手而不至于操作失误。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →