运维笔记

Terraform 根模块外访问文件夹?别再被 path.module 坑了,这才是正确姿势

Cloud & DevOps 技术可视化

症状:你的 Lambda 部署炸了

事情是这样的。上周五下午,我在部署一个 Lambda 函数,代码结构大概是这样的:

my-project/
├── terraform/
│   ├── main.tf
│   ├── modules/
│   │   └── lambda/
│   │       └── main.tf
│   └── lambda_code/
│       └── index.js

然后在 modules/lambda/main.tf 里,我用了个 archive_file 数据源去 zip 代码:

data "archive_file" "lambda_zip" {
  type        = "zip"
  source_dir  = "${path.module}/../../lambda_code"
  output_path = "${path.module}/lambda.zip"
}

结果呢?terraform plan 跑起来直接报错:

Error: Error reading source directory: open /some/absolute/path/terraform/modules/lambda/../../lambda_code: no such file or directory

我盯着路径看了五分钟,确认没写错。然后试了 path.root,还是不行。群里一问,发现踩这个坑的人比我想象的多得多。

根因分析:为什么 path.module 和 path.root 都不靠谱?

这里有个 Terraform 的经典认知误区。

path.module 指向的是当前模块文件所在的目录。在子模块 modules/lambda 里用 path.module,它解析的就是 /project/terraform/modules/lambda

所以 ../../lambda_code 会解析成 /project/terraform/lambda_code —— 这路径是对的。但问题在于:Terraform 在 apply 之前,会先检查路径是否存在。如果你的 terraform 目录不是从项目根目录 my-project 执行的,或者你的 CI/CD pipeline 把 .tf 文件复制到了临时目录,这个相对路径就彻底断掉了。

path.root 指向的是根模块的目录,也就是你执行 terraform apply 的那个目录。这听起来更靠谱?但它的坑在于:如果你的根模块不在项目根目录(比如很多人把 Terraform 配置放在 infra/ 子目录里),path.root 解析出来的路径依然不对。

最恶心的场景是 Terraform Cloud 远程执行 —— 你的代码被上传到 Terraform 的沙箱环境后,所有本地相对路径全部失效。这问题在 Reddit 和 HN 上被骂了不知道多少次。

解决方案:三种硬核姿势

方案一:用 path.cwd + 绝对化路径(最推荐)

path.cwd 指向你执行 terraform 命令时所在的当前工作目录。这玩意儿在本地和 CI 里表现一致,但有个前提:你必须从项目根目录执行 terraform

# modules/lambda/main.tf
data "archive_file" "lambda_zip" {
  type        = "zip"
  source_dir  = "${abspath("${path.cwd}/../lambda_code")}"
  output_path = "${path.module}/lambda.zip"
}

abspath() 函数会把相对路径转成绝对路径。这样无论 Terraform 怎么复制模块文件,只要执行目录正确,路径就能解析。

缺点:如果你的项目结构变了,或者有人从子目录执行 terraform,还是炸。

方案二:用 path.root + 项目根目录约束(结构清晰)

把 Terraform 根模块放在项目根目录,然后在所有子模块里统一用 path.root 做锚点:

my-project/
├── main.tf
├── modules/
│   └── lambda/
│       └── main.tf
└── lambda_code/
    └── index.js
# modules/lambda/main.tf
data "archive_file" "lambda_zip" {
  source_dir  = "${path.root}/lambda_code"
  output_path = "${path.module}/lambda.zip"
}

这方案最干净,但要求你强制团队遵循目录约定。大项目里容易出幺蛾子。

方案三:传变量,彻底解耦(最工程化)

把路径作为变量传入,让调用者决定:

# modules/lambda/variables.tf
variable "source_code_path" {
  type        = string
  description = "Absolute or relative path to lambda source code"
}

# modules/lambda/main.tf
data "archive_file" "lambda_zip" {
  source_dir  = var.source_code_path
  output_path = "${path.module}/lambda.zip"
}
# main.tf
module "my_lambda" {
  source           = "./modules/lambda"
  source_code_path = abspath("${path.module}/lambda_code")
}

这是最稳妥的做法。路径的解析责任完全交给了调用方,子模块不需要关心外面长什么样。

三种方案对比

方案适用场景优点坑点
path.cwd + abspath小型项目、快速原型改动最小,一行搞定依赖执行目录,CI 中容易翻车
path.root + 目录约束中大型项目,团队规范好路径清晰,可读性强强制目录结构,重构成本高
变量传参公共模块、多环境部署完全解耦,最稳健调用方需要多写几行代码

社区的真实反馈

Reddit 上有个帖子问 “What is the idiomatic way of importing data files outside module?",下面高赞回答直接开喷:“Terraform’s path handling is garbage for cross-module references. Just pass it as a variable and move on.”

还有一个兄弟在 HN 上吐槽,说他们的 Terraform Cloud pipeline 因为路径问题炸了三天,最后发现是 Terraform 把代码上传后,所有相对路径都变成了相对于沙箱根目录。解决方案?“We ended up writing a Makefile wrapper that copies the files into the module directory before running terraform.” —— 这操作属实硬核,但也说明这问题有多恶心。

我个人觉得,Terraform 在跨模块文件引用这块的设计确实拉胯。path.modulepath.root 的语义在子模块里经常让人迷惑,文档又写得很模糊。如果你在做一个会被别人复用的公共模块,千万别依赖任何外部路径,老老实实传变量。

FAQ

Q: 为什么 path.module 在子模块里指向的是模块目录而不是根目录? A: 因为 path.module 的设计意图就是让模块内部引用自己的文件,比如模板文件、脚本等。它从设计上就不应该知道外部目录的存在。这是有意为之,不是 bug。

Q: 在 Terraform Cloud 中如何解决外部文件引用? A: Terraform Cloud 远程执行时,你的代码被上传到临时沙箱。任何本地路径都会炸。解决方案:1) 使用 filebase64sha256() 等函数直接读取文件内容作为变量传递;2) 在 CI pipeline 中把文件复制到模块目录内再上传;3) 使用外部数据源(如 AWS S3)存储文件。

Q: path.cwd 和 path.root 有什么区别? A: path.cwd 是你执行 terraform 命令时的当前工作目录。path.root 是根模块的目录(即包含 main.tf 的那个目录)。如果你在根模块目录里执行 terraform,两者相同。如果从其他目录执行(比如 terraform -chdir=infra/),两者就不同了。

Q: 有没有办法让子模块自动检测项目根目录? A: 没有内置机制。Terraform 的模块系统故意不做这种 “向上查找” 的逻辑,因为这会破坏模块的可移植性和确定性。第三方工具如 Terragrunt 提供了 find_in_parent_folders() 函数,但那是 Terragrunt 的功能,不是 Terraform 的。

社区灵感与参考 (References & Community Insights)

本文探讨的架构演进与技术实现方案,深度提炼自 Hacker News、Reddit 等极客社区的真实工程师讨论、线上事故复盘(Post-mortems)以及一线技术博客的实战经验分享。

Elvin Hui

关于作者:Elvin Hui

Elvin 拥有 10+ 年企业级数据中心、云原生架构和网络安全经验。持有 CCNA、AWS 解决方案架构师认证。我致力于将一线的“踩坑”经验沉淀为真实、硬核的技术指南,拒绝空洞理论。