CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Terraform 基础设施即代码/课时/综合练习
RawBlame
课时目录12 节

Terraform 基础设施即代码

  1. 01Terraform 是什么
  2. 02HCL 语法
  3. 03init 与 Provider
  4. 04plan 与 apply
  5. 05State 管理
  6. 06变量与输出
  7. 07Module 复用
  8. 08AWS 资源示例
  9. 09Terraform 与 K8s
  10. 10CI 中的 Terraform
  11. 11Terraform vs Helm
  12. 12综合练习

综合练习

用 TF 描述 MySQL + 安全组最小栈。

40 分钟

综合练习:Spring Boot 上云全流程

这一课你将能做到

  1. 独立搭建一个分层 Terraform 项目:根模块组装 + network/compute 子模块 + 双环境 tfvars
  2. 跑通 init → fmt → validate → plan → apply → 验证 → destroy 完整闭环
  3. 处理练习中常见的三类问题:参数替换、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 }

modules/compute/main.tf:

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:根模块组装

main.tf:

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
}

outputs.tf:

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:环境参数

envs/dev.tfvars:

env           = "dev"
cidr          = "10.10.0.0/16"
instance_type = "t3.micro"

envs/prod.tfvars:

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

销毁后检查(控制台或 CLI):

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 确认无差异——这是把练习项目变成团队项目的最后一步。

讲清楚要点

  1. 根模块只组装,不写资源细节:资源全部沉在模块里,根模块表达「network 与 compute 的接线关系」,一眼能读懂架构。
  2. 命名全部带 env 前缀:资源名、标签都由 var.env 派生,双环境并存不会撞名。
  3. 改环境 = 换 tfvars,不改代码;要变架构才改模块,这是「配置」和「代码」的分层。
  4. apply 顺序自动推导:compute 依赖 module.network 的输出,Terraform 自动先建网络,不需要你手工排顺序。
  5. 练习到工作的差距清单:多 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
手工删了云上资源但不更新 stateplan 报 NotFoundstate rm 摘除对应地址

卡点急救

  1. 权限不足(AccessDenied on xxx):给执行身份补最小权限集(ec2 vpc/subnet/igw/route/sg/instance + ec2:DescribeImages)。
  2. 资源配额(VPC/弹性 IP 上限):清理不用的资源,或申请提升配额;这是新账号常见硬限制。
  3. 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 要求。

自测三问

  1. 不看教程,你能默写出这个项目的目录树和每个文件的职责吗?
  2. 从 apply 成功到 destroy 干净,中间有哪几个必须执行的验证动作?
  3. 如果这套环境明天要上真实流量,按优先级列出你要补的 6 项能力。

小结一句话

综合练习把「分层模块 + 双环境参数 + 标准工作流」跑成肌肉记忆:代码管架构、tfvars 管环境、plan 管安全、destroy 管成本。

延伸

  • Terraform AWS Get Started 官方入门
  • Module Structure 模块组织建议
  • AWS Well-Architected(架构完善)
  • 课程收官:建议把本练习代码整理进你的 GitHub,作为 IaC 实战作品;并可继续学习本站 terraform-roadmap-essentials 加深架构与原理。
加载本节练习…
← 上一课Terraform vs Helm