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

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

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

学习

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

更多

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

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Terraform 基础设施即代码/课时/AWS 资源示例
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综合练习

AWS 资源示例

VPC、EC2、RDS 概念级 Demo。

35 分钟

AWS 资源实战

这一课你将能做到

  1. 用 Terraform 创建一套可运行的云上底座:VPC、子网、安全组、EC2
  2. 给 Spring Boot 应用配上 RDS(MySQL)和 S3(静态资源/备份)
  3. 拿到输出的连接地址,安全销毁整套环境,知道哪些配置必须替换(AMI、密钥对、唯一桶名)

为什么要学

这是前 7 课语法的总验收。把 VPC、EC2、SG、RDS、S3 五类资源跑通,AWS 上大部分中小应用你都能接得住。注意配置会真实产生 AWS 费用,练习完务必 destroy;生产还需加备份、多 AZ、告警,结尾点明。

人话:这一课就是帮你的 Spring Boot 应用「安家」——小区围墙(VPC)、门禁规则(SG)、房子(EC2)、专用仓库(RDS)和储物间(S3),一次用代码盖齐。

先建立图像

资源依赖图(决定创建顺序),文件按 versions/network/compute/database/storage/outputs 拆分:

VPC ── 公有子网 ── 安全组 ── EC2(Spring Boot 运行处)
  │                  │
  └──────────────────┴── RDS 子网组 ── RDS(MySQL,私有)
S3(独立资源,EC2 用 IAM 角色访问)

跟着做

步骤 1:版本与 provider

versions.tf:

terraform {
  required_version = ">= 1.7.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

配好凭证(aws configure 或环境变量),用 aws sts get-caller-identity 确认身份。

步骤 2:VPC 与公有子网

network.tf:

resource "aws_vpc" "app" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "springboot-vpc"
  }
}

resource "aws_internet_gateway" "app" {
  vpc_id = aws_vpc.app.id

  tags = { Name = "springboot-igw" }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.app.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "ap-northeast-1a"

  tags = { Name = "springboot-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
}

enable_dns_hostnames 不开,EC2 没有 DNS 主机名、RDS 解析也受影响,是初学者常漏项。

步骤 3:安全组

写入 network.tf:

resource "aws_security_group" "app" {
  name        = "springboot-app-sg"
  description = "SSH 与应用端口"
  vpc_id      = aws_vpc.app.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  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_security_group" "db" {
  name        = "springboot-db-sg"
  description = "只允许应用服务器访问 MySQL"
  vpc_id      = aws_vpc.app.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.app.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

数据库 SG 用 security_groups 来源,只放行挂 app SG 的机器——最小权限基本姿势。

步骤 4:EC2 与 IAM 角色

compute.tf:

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-2023.*-x86_64"]
  }
}

resource "aws_iam_role" "app" {
  name = "springboot-ec2-role"

  assume_role_policy = jsonencode({
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "ec2.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "s3" {
  role       = aws_iam_role.app.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}

resource "aws_iam_instance_profile" "app" {
  name = "springboot-profile"
  role = aws_iam_role.app.name
}

resource "aws_instance" "app" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t3.micro"
  subnet_id     = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.app.id]
  iam_instance_profile   = aws_iam_instance_profile.app.name

  key_name = "你的密钥对名称"   # 先在目标区域建好密钥对

  user_data = <<-EOF
              #!/bin/bash
              dnf install -y java-17-amazon-corretto
              EOF

  tags = { Name = "springboot-app" }
}

要点:AMI 随时间区域变化,用 data 动态查询而非抄旧 ID;访问 S3 用 IAM 角色而非放 AK/SK。

步骤 5:RDS MySQL

database.tf:

resource "aws_db_subnet_group" "app" {
  name       = "springboot-db-subnet"
  subnet_ids = [aws_subnet.public.id]

  # 生产需至少两个跨 AZ 的私有子网;本演示仅练习,勿照搬
}

resource "aws_db_instance" "app" {
  identifier           = "springboot-mysql"
  engine               = "mysql"
  engine_version       = "8.0"
  instance_class       = "db.t3.micro"

  db_name  = "demo"
  username = "appuser"

  # 自动生成密码并存入 Secrets Manager(provider 5.x 参数)
  manage_master_user_password = true

  db_subnet_group_name   = aws_db_subnet_group.app.name
  vpc_security_group_ids = [aws_security_group.db.id]

  publicly_accessible = false
  skip_final_snapshot = true
  deletion_protection = false

  tags = { Name = "springboot-mysql" }
}

自动密码依赖 Secrets Manager;不用则改为 password = var.db_password(sensitive 变量经环境变量传入)。skip_final_snapshot 练习才用。

步骤 6:S3 桶

storage.tf:

resource "aws_s3_bucket" "assets" {
  bucket = "springboot-assets-请替换为唯一名称"
  tags = {
    Project = "springboot-demo"
  }
}

resource "aws_s3_bucket_versioning" "assets" {
  bucket = aws_s3_bucket.assets.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_public_access_block" "assets" {
  bucket = aws_s3_bucket.assets.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

provider 4.0 之后,版本化、加密、访问拦截都拆成独立资源,不能再写在 aws_s3_bucket 内。

步骤 7:输出与执行

outputs.tf:

output "app_public_ip" {
  value = aws_instance.app.public_ip
}

output "rds_endpoint" {
  value     = aws_db_instance.app.endpoint
  sensitive = true
}

output "s3_bucket" {
  value = aws_s3_bucket.assets.bucket
}

执行:

terraform init
terraform fmt
terraform validate
terraform plan -out=tfplan
terraform apply tfplan

拿到 IP 后,Spring Boot 数据源据此填写:jdbc:mysql://<rds_endpoint 前半段>/demo,用户名 appuser,密码来自 Secrets Manager。

练习结束:

terraform destroy

讲清楚要点

  1. 网络边界:演示图省事全放公有子网;生产 EC2 应在私有子网,经 ALB/NAT 对外、多 AZ 部署。
  2. SG 默认拒绝:只放行显式端口,数据库仅接受 app SG 流量;RDS 创建需 5–15 分钟,中断后可重跑。
  3. 三处必替换:密钥对、唯一桶名、AMI(用 data 动态查询)。
  4. 成本意识:destroy 后检查快照、弹性 IP 等残留计费项。

你可能会懵的地方

  • RDS 子网组为什么报错? 生产要求至少两个跨 AZ 的子网;只建一个时部分账号会被拒,补建第二个子网即可。

  • EC2 能 SSH 但访问不了 8080? 三重检查:SG 放行、应用监听 0.0.0.0:8080 而非 localhost、先在机器上 curl localhost:8080 自测。

  • 桶名总报 BucketAlreadyExists? S3 桶名全局唯一(所有账号共享命名空间)。加随机后缀,如 springboot-assets-12345abc。

  • destroy 卡在 RDS? deletion_protection 或要求最终快照都会阻止删除,练习配置里两项都要放开。

  • 能在 Terraform 里部署 Spring Boot 的 jar 吗? 可用 provisioner 上传但不推荐。主流是构建镜像 + user data 拉取或走 EKS/ECS:Terraform 管环境,发布交给 CI/CD。

错法对照

错法问题正解
抄写几年前的 AMI ID镜像已不存在或有漏洞data 块动态查最新 AMI
RDS 对公网开放 3306数据库暴露私有 + SG 仅放行 app SG
在 user_data/配置里写死 AK/SK凭据泄露IAM 角色 + instance profile
生产库 skip_final_snapshot=true销毁即丢数据保留快照并开启备份
单 AZ 单子网就上生产无高可用多 AZ、私有子网、ALB

卡点急救

  1. InvalidKeyPair.NotFound:密钥对是区域级资源,在目标区域建好再填名字。
  2. Subnets do not span enough AZ:子网组需至少两个 AZ,补建子网并更新 db_subnet_group。
  3. apply 报 AccessDenied:按最小权限补 ec2/rds/s3/iam 相关 action,别给 AdministratorAccess。

面试常问

  • 如何保证 RDS 不被公网访问? 放入私有子网组、publicly_accessible=false、SG 只对应用层 SG 开 3306;凭证放 Secrets Manager 引用获取。

  • 创建 EC2 时怎么让应用能访问 S3? IAM 角色附加最小 S3 策略,经 instance profile 挂给 EC2;应用用默认凭证链自动获得临时凭证。

  • Terraform 管理的 VPC 包含哪些基本组件? VPC、IGW/NAT、子网、路由表及关联、安全组;生产还有 NACL、VPC Endpoint、对等连接/Transit Gateway。

自测三问

  1. 如果让你给这套演示「生产化」,你会改哪 5 个地方?
  2. app SG 和 db SG 的流量来源为什么写法不同?
  3. destroy 之后还可能留下哪些持续计费的东西?怎么排查?

小结一句话

VPC 打底、SG 收口、EC2 挂角色跑应用、RDS 躲在私有子网、S3 默认私有——五类资源构成 Spring Boot 上云的最小完整环境,练完即毁。

延伸

  • AWS Provider 教程
  • RDS aws_db_instance 参考
  • 下一课:Terraform 与 Kubernetes——用 Kubernetes Provider 管理集群对象。
加载本节练习…
← 上一课Module 复用下一课 →Terraform 与 K8s