cloudplz

本页是译文,以英文原文为准。 阅读原文

写作

一次 terraform apply,开出 20+ 个 AWS 服务 —— 全部按新的 Free Plan 调好

AWS 新的 Free Plan 到处是雷:现在每个 ElastiCache 节点都烧 credits,DynamoDB 按需模式绕开 Always Free。一个模块,约束带校验,覆盖 20+ 个服务。

AWS 新的 free plan 送 $200 credits($100 基础 + $100 奖励)。前提是五项活动都得单独激活。 这一步很容易漏掉,我自己就差点漏了。

第一次给个人项目起 ElastiCache 集群,我选了 cache.t4g.micro。Graviton 机型, 看上去就是那个显而易见的现代选择。问题在后面:旧的 free tier 只覆盖 cache.t3.micro, 新的 Free Plan 干脆两种机型都不给额度,全烧 credits。等我发现,已经白烧了几天 — — 那个 cache 我根本没在用。这个模块就是为防住这类错误写的。

我写了一个 Terraform 模块。五项赚 credits 活动,它自动满足其中四项;开通 20+ 个服务; 而且在结构上让你很难把自己配出账单来。模块已发布在 Terraform Registry:cloudplz/free-tier/aws。

「直接用 free tier 就行了」的问题

不靠 Terraform 摸 AWS free tier,就是在读脚注。几个容易栽跟头的地方:

  • ElastiCache 现在每种节点类型都烧 credits。 旧的 12 个月 free tier 里,cache.t3.micro 有 750 小时/月的额度, cache.t4g.micro 没有。新的 Free Plan 下两种都没有额度 —— 都消耗 credits。 旧规则还散落在各种老博客里,我就中招了:选了 Graviton 的 t4g,以为旧的那张表还算数。
  • DynamoDB 按需计费会吃掉 free tier。 25 RCU/WCU 的 Always Free 额度只认 provisioned capacity 模式。 切到 on-demand,就是按请求付费。
  • 公网 IPv4 地址 $0.005/小时(约 $3.65/月),2024 年调价之后就是这个价。每个 IP、 每个资源都算。前 12 个月里每月前 750 小时免费 —— 刚好够一台常开实例—— 但每多一个 IP(第二台实例、 一个 NAT gateway、一个 ALB),立刻开始计费。
  • KMS customer-managed key 不免费。 SSE-S3 免费,AWS 托管密钥也不收存储费。 但很多安全指南建议给 S3 桶默认上 customer-managed KMS key —— 那就每个 key 每月 $1, 还要加上超出每月 20K 免费请求之后的按次调用费。

上面这些,模块全部编码成带校验的约束。你没法不小心配出一个不免费的 ElastiCache 节点类型: validation 块在 plan 阶段就拒掉,任何资源都还没创建。

模块开通了什么

模块把资源分成两类:一类始终部署,是核心服务;一类靠 features 对象开关,可选。

核心服务(始终开启):

资源配置为什么是这个具体设置
VPC/16 CIDR,public + private 子网不上 NAT gateway —— 省约 $32/月
EC2t4g.micro,gp3 30GBt-family 由 validation 强制
公网 IPv4EC2 上 1 个地址由 750 小时/月的免费额度覆盖(前 12 个月);额外的 IP 按 $0.005/小时计费
Lambda128MB,Function URL + API Gateway128MB 让免费 GB-秒最大化
DynamoDBPROVISIONED 25 RCU/WCUon-demand 会绕过 Always Free
SQSStandard queue + DLQFIFO 烧请求更快
SNSStandard topic每月 1M 次发布免费
CloudWatch2 个 alarm,保留 7 天保持在 Always Free 的 10 个 alarm 上限之内
EventBridgeScheduler 按 rate(5 min)每月 14M 次 Scheduler 调用免费
Budgets零支出告警拿到一项能赚 $20 credits 的活动
Secrets Manager已启用数据库的凭证$0.40/secret/月 —— 故意算核心
IAMEC2、Lambda、Step Functions 的角色最小权限访问所必需

Secrets Manager 是有意为之。数据库凭证属于密钥库,不该躺在 Terraform state 或环境变量里。 $0.40/secret/月,这笔常驻成本不免费,我主动接受。IPv4 地址不一样:只要就这一个, 就被免费额度覆盖。

可选服务(feature 开关):

Feature创建什么月成本
rds = trueRDS PostgreSQL db.t4g.micro,20GB约 $13.98
aurora = true ⚠️Aurora Serverless v2,0–4 ACU(自动暂停)在 Free Plan 限额内由 credits 覆盖 —— 可能需要 Paid Plan(见上方警告)
elasticache = trueElastiCache Valkey cache.t3.micro约 $12.41
cloudfront = trueCloudFront PriceClass_100 + S3 originAlways Free
cognito = trueCognito User PoolAlways Free(10K MAU)
step_functions = trueStep Functions STANDARD 状态机Always Free(每月 4K 次状态转换)
bedrock_logging = trueBedrock 调用日志写入 CloudWatch配置免费;盯住每月 5GB 的 CloudWatch Logs 额度

2026 年三月那次调整分量不轻:Aurora PostgreSQL Serverless v2 进了 Free Plan。 credits 有效期内,每个集群最多 4 ACU、1 GiB 存储。公告文章略过了两个关键细节。 第一,「covered by the plan」说的是由 credits 出资,不是 Always Free: credits 一过期就开始计费。 第二,自动暂停(auto-pause)只在 min_capacity = 0时才生效。旧默认值是 0.5 ACU, 那个值下集群从不暂停,空闲也要约 $44/月,成本模型整个被悄悄破坏掉。模块现在把 aurora_min_capacity 默认设为 0(自动暂停打开, 空闲时计算 ≈ $0;存储仍按 $0.10/GB-月累计,算在 1 GiB 额度内)。依赖暂停之前, 有两点要知道。恢复时首次连接有几秒冷启动延迟。另外,只要有东西一直占着连接,或者某个不兼容的 feature 在起作用, 暂停就会被静默禁用 —— AWS 不报错,集群一直热着。还有存储,Terraform 根本管不住 — — Aurora 卷以 10 GiB 为增量增长。所以 1 GiB 这个 plan 上限,要你自己盯用量,不是强制。

五项赚 credits 活动

五项活动,AWS 每项给 $20,在 $100 基础额度之上再补 $100 奖励 credits。四项靠terraform apply 加一点收尾就能拿到, 一项得在控制台上手动操作:

活动Terraform 开通什么手动步骤
EC2aws_instance.web (t4g.micro)终止实例一次(该活动要求启动并终止)
RDSaws_db_instance.postgres (db.t4g.micro)无
Lambdaaws_lambda_function_url.handler无 —— URL 本身就是触发器
Budgetsaws_budgets_budget.zero_spend无
Bedrock调用日志配置在控制台里启用模型访问 + 发 1 条 prompt

跑一遍 terraform apply,把 EC2 实例终止一次(活动校验启动和终止 —— 之后再 terraform apply 把它拉回来) ,然后进 Bedrock 控制台,启用任意一个模型,发一条 prompt。五项活动就齐了 —— $100 奖励 credits。 一个提醒:这些活动的完成情况,AWS 没提供遥测记录。算之前,先在控制台 Explore AWS 组件里确认每一项都显示已完成。

Credit 预算这笔账

总共 $200($100 基础 + $100 奖励),不同配置下能撑多久,看下面这张表。按模块默认值aurora_min_capacity = 0 跑, Aurora 空闲时自动暂停,也就不再计入每月消耗 —— 空闲计算 ≈ $0,存储只花几美分, 还在 1 GiB 额度内。整张表有一个硬约束:credits 在账号创建后 12 个月过期。 哪种配置都撑不过这个点。

场景每月消耗$200 能撑
全默认(RDS + ElastiCache + Secrets Manager)约 $39.79约 5 个月
拿到 $20 credit 后关掉 RDS约 $25.41约 7.9 个月
关掉 RDS + ElastiCache约 $12.60约 15.9 个月 → 封顶 12

成本最优策略:第一个月全默认跑,拿满五个 $20 credit,然后关掉 RDS 和 ElastiCache。 按约 $12.60/月算,纸面上能撑 15.9 个月 —— 但 12 个月过期才是硬天花板。除非你多留几个服务常开, $200 里大概只用得到 $150。

几个值得理解的设计决定

拿 validation 当成本护栏

跟一份纯 README.md 实例类型推荐清单比,模块的价值就在这里:validation 块让你在结构上很难把自己配出费用:

variable "ec2_instance_type" {
  default = "t4g.micro"
  validation {
    condition     = can(regex("^t[0-9]+[a-z]*\\.", var.ec2_instance_type))
    error_message = "ec2_instance_type must be a t-family instance type (e.g., t4g.micro, t3.micro)."
  }
}

variable "elasticache_node_type" {
  default = "cache.t3.micro"
  validation {
    condition     = var.elasticache_node_type == "cache.t3.micro"
    error_message = "elasticache_node_type is pinned to cache.t3.micro to keep credit burn minimal (~$12.41/mo)."
  }
}

variable "aurora_max_capacity" {
  default = 4.0
  validation {
    condition     = var.aurora_max_capacity <= 4.0
    error_message = "aurora_max_capacity must be <= 4.0 to stay within the free plan cap."
  }
}

ElastiCache 被钉死,是因为现在挑节点类型要算的是「烧多少 credits」,不是「免费还是不免费」 。新的 Free Plan 下每种节点类型都消耗 credits,模块就选合法默认值里最便宜的那个, 让「想用 Graviton」的直觉没法悄悄把消耗翻倍。cache.t4g.micro看着像 cache.t3.micro 的现代等价物, 其实并不更便宜。plan 阶段就拦下来,让你不用靠账单才发现这件事。

用 optional object type 做 feature 开关

features 变量用了 Terraform optional() 类型约束,调用方随便省掉几个 key, 都不会报错:

variable "features" {
  type = object({
    rds             = optional(bool, true)
    aurora          = optional(bool, true)
    elasticache     = optional(bool, true)
    cloudfront      = optional(bool, true)
    cognito         = optional(bool, true)
    step_functions  = optional(bool, true)
    bedrock_logging = optional(bool, true)
  })
  default = {}
}

default = {} 意味着整块都能省 —— 所有 feature 默认打开。map(bool) 那种常见写法逼你把想要的每个 key 都列出来, 这个友好得多。用 optional(),只声明你改的那几个:

# Disable only the expensive ones
features = {
  rds         = false
  elasticache = false
}

这里有个设计说明。模块早期版本用一条 plan 时校验,把 ElastiCache 和 RDS/Aurora 绑在一起。 前提假设就错了:以为 ElastiCache 共用 RDS 的 DB subnet group。其实不共用 —— ElastiCache 有自己的 aws_elasticache_subnet_group 资源, 模块本来就在创建。那条耦合校验是过期残留,它让「只开 cache」这种配置根本没法用, 当前版本已经删掉。现在每个服务各管各的:elasticache = true 配 rds = false是合法配置。

不上 NAT gateway

VPC 里有 public 和 private 子网,但没有 NAT gateway。这省下约 $32/月($0.045/小时) 。private 子网里需要出网的资源,走 AWS 服务的 VPC endpoint (S3、Secrets Manager、 SSM),要么根本不需要出网。EC2 放在 public 子网。个人学习环境够用了:可以用 security group 锁住 SSH, 或者干脆不用 SSH,改用 SSM Session Manager。

DynamoDB:PROVISIONED 还是 on-demand

Always Free 给 DynamoDB 的额度是 25 RCU、25 WCU、25 GB。只认 provisioned capacity 模式。 on-demand 定价不参与 Always Free。模块默认建一张 provisioned 表,恰好 25/25。 改成 on-demand,每一次读和写都立刻开始烧 credits。

发布到 Terraform Registry

Terraform Registry 对模块名有严格约定:terraform-<PROVIDER>-<NAME>。 GitHub 仓库 cloudplz/terraform-aws-free-tier 会自动映射成 registry 里那个cloudplz/free-tier/aws。 没有申请流程 —— 把 GitHub 账号接到 registry,选中仓库就行。之后带语义化版本号 tag 的 release(v1.0.0、 v1.1.0 等),registry 会自动收录。

CI 流水线在每次 push 和 PR 时运行:

# .github/workflows/ci.yml (abbreviated)
- terraform fmt -check -recursive
- terraform validate
- tflint --recursive
- trivy config .
- terraform test

terraform test 跑 tests/ 里的单元测试。测试校验变量默认值、feature 开关行为、 安全约束(没有公开的 S3 桶、没有未加密存储),还有跨变量校验 —— 全程不创建任何真实基础设施。

5 行 HCL

模块发布之后,用它就是 Terraform 里最精简的形态了。name 是唯一必填输入,其余全部有安全默认值。 把 70+ 个资源全写进自己那份配置,是 500+ 行 HCL —— AWS 每次调价、每次改服务行为, 你都得跟着维护更新。用发布好的模块,version = "~> 1.0" 一改就自动拿到修复。

module "free_tier" {
  source  = "cloudplz/free-tier/aws"
  version = "~> 1.0"

  name = "myproject"
}

发现某个本该 Always Free 却没被正确处理的服务?开个 issue —— AWS 一调限额, 成本表就跟着漂。