AWS 资源示例
VPC、EC2、RDS 概念级 Demo。
AWS 资源实战
这一课你将能做到
- 用 Terraform 创建一套可运行的云上底座:VPC、子网、安全组、EC2
- 给 Spring Boot 应用配上 RDS(MySQL)和 S3(静态资源/备份)
- 拿到输出的连接地址,安全销毁整套环境,知道哪些配置必须替换(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 角色
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
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 桶
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:输出与执行
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。
讲清楚要点
- 网络边界:演示图省事全放公有子网;生产 EC2 应在私有子网,经 ALB/NAT 对外、多 AZ 部署。
- SG 默认拒绝:只放行显式端口,数据库仅接受 app SG 流量;RDS 创建需 5–15 分钟,中断后可重跑。
- 三处必替换:密钥对、唯一桶名、AMI(用 data 动态查询)。
- 成本意识: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 |
卡点急救
- InvalidKeyPair.NotFound:密钥对是区域级资源,在目标区域建好再填名字。
- Subnets do not span enough AZ:子网组需至少两个 AZ,补建子网并更新 db_subnet_group。
- 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。
自测三问
- 如果让你给这套演示「生产化」,你会改哪 5 个地方?
- app SG 和 db SG 的流量来源为什么写法不同?
- destroy 之后还可能留下哪些持续计费的东西?怎么排查?
小结一句话
VPC 打底、SG 收口、EC2 挂角色跑应用、RDS 躲在私有子网、S3 默认私有——五类资源构成 Spring Boot 上云的最小完整环境,练完即毁。
延伸