综合练习
用 TF 描述 MySQL + 安全组最小栈。
综合练习:Spring Boot 上云全流程
这一课你将能做到
- 独立搭建一个分层 Terraform 项目:根模块组装 + network/compute 子模块 + 双环境 tfvars
- 跑通 init → fmt → validate → plan → apply → 验证 → destroy 完整闭环
- 处理练习中常见的三类问题:参数替换、state 迁移与销毁残留,说出生产化还缺什么
为什么要学
前 11 课是分解动作,这一课是整套套路。真实工作里没人让你单独写一个 VPC 资源交差,任务永远是:「给订单服务开一套环境,周三要」。这一课把目录结构、模块代码、环境参数、执行命令、验收方法、销毁清单一次走通,建议你亲手敲一遍而不是只看——Terraform 是手上功夫,看懂和敲通之间隔着三个报错。
人话:前面学的是传球、运球、射门的单项训练,这一课是踢一场完整的练习赛。目标不是完美,是从无到有交付一套环境,再把它干干净净拆掉。
先建立图像
最终项目结构:
final-practice/
├── versions.tf
├── main.tf
├── outputs.tf
├── envs/
│ ├── dev.tfvars
│ └── prod.tfvars
└── modules/
├── network/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── compute/
├── main.tf
├── variables.tf
└── outputs.tf
交付物验收标准:
□ terraform 输出无报错,资源全部创建
□ 拿到 EC2 公网 IP,8080 健康检查可通(应用自备)
□ state 在远程后端(可选,练习也可本地)
□ destroy 后控制台无残留计费资源
跟着做
步骤 1:版本声明
versions.tf:
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
步骤 2:network 模块
modules/network/variables.tf:
variable "env" { type = string }
variable "cidr" { type = string }
variable "az" { type = string }
modules/network/main.tf:
resource "aws_vpc" "app" {
cidr_block = var.cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "springboot-${var.env}-vpc"
Env = var.env
}
}
resource "aws_internet_gateway" "app" {
vpc_id = aws_vpc.app.id
tags = { Name = "springboot-${var.env}-igw" }
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.app.id
cidr_block = cidrsubnet(var.cidr, 8, 1)
availability_zone = var.az
map_public_ip_on_launch = true
tags = { Name = "springboot-${var.env}-public" }
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.app.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.app.id
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
cidrsubnet(var.cidr, 8, 1) 从 VPC 网段自动切子网地址,不用手算,是模块里非常实用的函数。
modules/network/outputs.tf:
output "vpc_id" { value = aws_vpc.app.id }
output "subnet_id" { value = aws_subnet.public.id }
步骤 3:compute 模块
modules/compute/variables.tf:
variable "env" { type = string }
variable "instance_type" { type = string }
variable "subnet_id" { type = string }
variable "vpc_id" { type = string }
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
}
resource "aws_security_group" "app" {
name = "springboot-${var.env}-sg"
vpc_id = var.vpc_id
ingress {
from_port = 8080
to_port = 8080
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"]
}
}
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
subnet_id = var.subnet_id
vpc_security_group_ids = [aws_security_group.app.id]
user_data = <<-EOF
#!/bin/bash
dnf install -y java-17-amazon-corretto
EOF
tags = {
Name = "springboot-${var.env}-app"
Env = var.env
}
}
modules/compute/outputs.tf:
output "public_ip" { value = aws_instance.app.public_ip }
步骤 4:根模块组装
provider "aws" {
region = "ap-northeast-1"
}
module "network" {
source = "./modules/network"
env = var.env
cidr = var.cidr
az = "ap-northeast-1a"
}
module "compute" {
source = "./modules/compute"
env = var.env
instance_type = var.instance_type
vpc_id = module.network.vpc_id
subnet_id = module.network.subnet_id
}
output "app_ip" {
description = "应用服务器公网 IP"
value = module.compute.public_ip
}
根模块需要 env、cidr、instance_type 三个输入,根目录补一个极简 variables.tf:
variable "env" { type = string }
variable "cidr" { type = string }
variable "instance_type" { type = string }
步骤 5:环境参数
env = "dev"
cidr = "10.10.0.0/16"
instance_type = "t3.micro"
env = "prod"
cidr = "10.20.0.0/16"
instance_type = "t3.small"
注意 CIDR 不能相同——若两套环境同时存在同一账号又有互通需求,地址重叠会出问题。
步骤 6:执行闭环
terraform init
terraform fmt -recursive
terraform validate
terraform plan -var-file=envs/dev.tfvars -out=dev.tfplan
terraform apply dev.tfplan
terraform output app_ip
# 机器就绪后(本机或部署好应用时):
curl http://<app_ip>:8080/actuator/health
想看 prod 的计划但不执行,练手「双环境差异审查」:
terraform plan -var-file=envs/prod.tfvars
步骤 7:销毁与残留检查
terraform plan -destroy -var-file=envs/dev.tfvars
terraform destroy -var-file=envs/dev.tfvars
aws ec2 describe-instances --filters "Name=tag:Env,Values=dev"
aws ec2 describe-vpcs --filters "Name=tag:Env,Values=dev"
确认没有残留的 VPC、IGW、SG、密钥对以外的快照和弹性 IP。SG 如果提示「being used」,说明关联资源尚未删完,等一分钟再试。
步骤 8(进阶):迁移远程 state
若你已按第 5 课建好 S3 后端,加 backend.tf 后 terraform init 迁移,再重复一次 plan 确认无差异——这是把练习项目变成团队项目的最后一步。
讲清楚要点
- 根模块只组装,不写资源细节:资源全部沉在模块里,根模块表达「network 与 compute 的接线关系」,一眼能读懂架构。
- 命名全部带 env 前缀:资源名、标签都由 var.env 派生,双环境并存不会撞名。
- 改环境 = 换 tfvars,不改代码;要变架构才改模块,这是「配置」和「代码」的分层。
- apply 顺序自动推导:compute 依赖 module.network 的输出,Terraform 自动先建网络,不需要你手工排顺序。
- 练习到工作的差距清单:多 AZ 与私有子网、NAT/ALB、RDS 与备份、密钥管理(Secrets Manager)、CI 流水线与审批、漂移检测、监控告警——这些在前面课程都已覆盖。
你可能会懵的地方
-
apply 报子网 CIDR 无效?
cidrsubnet 的新增位和网段容量要算对;用标准私网段 /16 加 8 位切 /24 是安全组合,照抄即可。
-
两个环境能同时 apply 吗?
本地 state 不行(state 只有一份,会互相覆盖);远程 state 时若用同一个 key 也不行。用不同 state key(或每个环境独立目录)才能并存。
-
compute 模块里能再加数据库吗?
能,但建议拆独立 database 模块,RDS 生命周期和备份策略与 EC2 不同,绑在一起会互相拖累。
-
验证健康检查不通怎么办?
本练习只装了 JDK,并没有部署你的应用。先验证 SG 和机器(SSH 上去 curl localhost),再让应用流水线把 jar/镜像部署上去。
-
destroy 失败后 state 还在,资源删了一半?
修好报错(常见是依赖顺序、网络资源未空),重新 destroy 即可,已删的不会重来。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 根模块里手写一套资源又同时调模块 | 重复/冲突 | 要么全模块,要么全手写 |
| dev/prod 用同一 CIDR 又长期并存 | 互通冲突、环境混淆 | 规划不重叠网段 |
| 不看 destroy 计划直接确认 | 误删共享资源 | 销毁前 plan -destroy 核对 |
| 模块变量不传全,靠默认值碰运气 | 环境行为不可预测 | 每环境显式 tfvars |
| 手工删了云上资源但不更新 state | plan 报 NotFound | state rm 摘除对应地址 |
卡点急救
- 权限不足(AccessDenied on xxx):给执行身份补最小权限集(ec2 vpc/subnet/igw/route/sg/instance + ec2:DescribeImages)。
- 资源配额(VPC/弹性 IP 上限):清理不用的资源,或申请提升配额;这是新账号常见硬限制。
- state 与云端对不上:
terraform state list 与控制台逐个比对,缺的 import、多(野资源)则决定纳管或删除,别带着糊涂账继续 apply。
面试常问
-
如何用 Terraform 管理多环境?说说你的目录设计。
根模块只做组装,资源沉到 network/compute/database 等模块;命名与标签由 env 变量派生;每环境独立 tfvars;state 按环境隔离(S3 key/前缀),配合独立流水线和审批;强隔离场景直接用独立配置目录。
-
一次 Terraform 变更的完整操作流程是什么?
pull 最新代码,fmt/validate,init,plan 带 -var-file 并审查(尤其重建与销毁项),apply 计划文件,验证输出与健康检查;需要回滚则反向 apply 或重建,全部记录在 Git 和流水线日志中。
-
Terraform 项目交付前你会检查什么?
版本约束与 lock、密钥是否落盘、state 后端与锁、命名标签规范、destroy/快照策略、CI 门禁与漂移检测,以及资源是否符合最小权限和多 AZ 要求。
自测三问
- 不看教程,你能默写出这个项目的目录树和每个文件的职责吗?
- 从 apply 成功到 destroy 干净,中间有哪几个必须执行的验证动作?
- 如果这套环境明天要上真实流量,按优先级列出你要补的 6 项能力。
小结一句话
综合练习把「分层模块 + 双环境参数 + 标准工作流」跑成肌肉记忆:代码管架构、tfvars 管环境、plan 管安全、destroy 管成本。
延伸