- DevOps
- 云原生
- 后端
【免费下载链接】digger
Digger is an open source IaC orchestration tool. Digger allows you to run IaC in your existing CI pipeline ⚡️
_envcommon目录是 Terragrunt "保持架构 DRY"(Keep Your Terragrunt Architecture DRY)实践中的关键一环:它集中存放各组件(component)跨环境共享的通用配置,再通过include块把每个文件合并进各环境的 live 配置中。本文以 digger 仓库内置的 terragrunt-infrastructure-live-example 测试示例为完整实例,结合 digger 的 terragrunt 配置解析源码(libs/digger_config/terragrunt/tac),带你理解_envcommon的运作机制,并看清 digger 如何识别_envcommon文件、将其自动纳入项目依赖与自动规划(autoplan)的触发条件。读完你将掌握一套可直接复制、可验证的多环境 Terragrunt 布局方案,以及从 Terragrunt 配置到 digger 项目清单的完整数据流。
一、_envcommon是什么:一处定义、处处合并
原文档对_envcommon目录的定位非常清晰:"This directory holds common configurations for each component that is shared across the environments. Each file will be included and merged into the live configuration for the service component in each environment."
翻译成工程语言就是三点:
- 按组件组织,而非按环境组织:
_envcommon下每个文件对应一个组件(如mysql.hcl、webserver-cluster.hcl),而不是每个环境一个文件。 - 跨环境共享:同一组件在不同环境(qa / stage / prod)中复用的参数集中定义在这里,避免在每份环境配置里重复粘贴。
- 通过 include 合并进 live 配置:各环境目录下的
terragrunt.hcl通过include块引用_envcommon中对应的组件文件,Terragrunt 在解析时自动完成配置合并。
在 digger 仓库中,这份示例位于 libs/digger_config/terragrunt/tac/test_examples/terragrunt-infrastructure-live-example/_envcommon/,目录下包含mysql.hcl与webserver-cluster.hcl两个组件配置,外加本 README。它同时是 digger 的 terragrunt→digger.yml 转换器(tac 包)的测试样例,因此既能作为 Terragrunt 用法示范,也能验证 digger 的解析行为。
二、整体目录结构:account → region → environment → resource
要理解_envcommon的定位,先看整个示例仓库的层级。示例 README 给出了经典四层组织方式:
account └ _global └ region └ _global └ environment └ resource对应到本仓库的实际目录:
- account(账号层):
non-prod/与prod/,每层都有 account.hcl,定义account_name、aws_account_id、aws_profile。 - region(区域层):如
us-east-1/,其 region.hcl 定义aws_region。 - environment(环境层):
qa/、stage/、prod/,其 env.hcl 定义environment = "qa"。 - resource(资源层):
mysql/、webserver-cluster/,每层一份 terragrunt.hcl。
而_envcommon与account.hcl、region.hcl、env.hcl平级地处于 "跨环境共享" 的位置——它是组件层面的公共区,配合根目录 terragrunt.hcl 的账号/区域/环境变量聚合,共同实现"Terraform 代码零重复"。
三、_envcommon组件文件剖析:以 mysql.hcl 为例
_envcommon/mysql.hcl是理解共享配置的样板文件,由三个部分构成。
1.terraform块:锁定模块来源与版本
terraform { source = "${local.base_source_url}?ref=v0.4.0" }source指向 Terraform 模块仓库中的//mysql子路径,?ref=v0.4.0锁定模块版本。注释明确指出:任何环境若需部署不同版本的模块,应在本环境重新定义该块并给出不同的 ref 来覆盖部署版本——这正是下文第 4 节要讲的覆盖机制在版本管理上的应用。
2.locals:读取父级变量并构造复用常量
locals { # Automatically load environment-level variables environment_vars = read_terragrunt_config(find_in_parent_folders("env.hcl")) # Extract out common variables for reuse env = local.environment_vars.locals.environment # Expose the base source URL so different versions of the module can be deployed in different environments. base_source_url = "git::git@github.com:gruntwork-io/terragrunt-infrastructure-modules-example.git//mysql" }find_in_parent_folders("env.hcl")从当前目录向上查找最近的env.hcl,read_terragrunt_config读取并解析它,从而拿到当前环境名(qa/stage/prod);env提取环境名,用于构造带环境前缀的资源名(如mysql_qa);base_source_url是模块的基础 URL,配合上面terraform.source中的?ref=一起使用。这里之所以用local.引用而非直接写死,是为了让子配置(即各环境的terragrunt.hcl)在expose = true时可以读取并拼接自己的版本号。
3.inputs:跨环境通用的模块参数
inputs = { name = "mysql_${local.env}" instance_class = "db.t2.micro" allocated_storage = 20 storage_type = "standard" master_username = "admin" # TODO: To avoid storing your DB password in the code, set it as the environment variable TF_VAR_master_password }inputs是传给底层 Terraform 模块的变量。这里的参数是所有环境共用的基线:实例规格db.t2.micro、存储 20GB、类型standard、用户名admin。密码刻意留空,注释提示通过TF_VAR_master_password环境变量注入,避免把数据库口令写进代码。
webserver-cluster.hcl采用同样的三段式结构,inputs中定义 ASG 的min_size、max_size、server_port、elb_port等公共参数,base_source_url指向//asg-elb-service子路径。
四、环境侧如何"接收"共享配置:include 块
_envcommon的文件并不会被自动加载,必须由各环境的terragrunt.hcl显式引用。以 qa 环境的 mysql 为例,non-prod/us-east-1/qa/mysql/terragrunt.hcl 中有两个 include:
# Include the root `terragrunt.hcl` configuration. The root configuration contains settings that are common across all # components and environments, such as how to configure remote state. include "root" { path = find_in_parent_folders() } # Include the envcommon configuration for the component. The envcommon configuration contains settings that are common # for the component across all environments. include "envcommon" { path = "${dirname(find_in_parent_folders())}/_envcommon/mysql.hcl" expose = true }include "root":通过find_in_parent_folders()找到根目录 terragrunt.hcl,继承其中的远程状态、AWS provider 生成、账号/区域/环境变量聚合等全局设置;include "envcommon":用dirname(find_in_parent_folders())定位仓库根,再拼接/_envcommon/mysql.hcl,把组件共享配置合并进来;expose = true:把被包含文件中的locals暴露给本配置使用,这样本环境可以用include.envcommon.locals.base_source_url引用_envcommon里定义的模块地址。
同样地,该文件的terraform块覆盖了模块版本:
terraform { source = "${include.envcommon.locals.base_source_url}?ref=v0.4.0" }注释说明:此处仅为 QA 环境覆盖terraform块的 source 属性,以演示如何在不同环境部署不同版本的模块。_envcommon定义的是默认 URL 骨架,环境侧只需拼接自己的?ref=即可切换版本。
值得注意的是 qa 与 stage 的 mysql 配置略有差异:qa 版本带expose = true(因为要读取base_source_url),stage 版本 stage/mysql/terragrunt.hcl 与 prod 版本都只做普通 include,不覆盖任何参数或仅覆盖 inputs。三种写法并存,正好演示了 include 的灵活度。
五、覆盖机制:环境个性需求如何覆盖公共基线
共享配置的价值在于"默认统一、按需覆盖"。prod 环境的 mysql 是覆盖示例的最佳说明,prod/us-east-1/prod/mysql/terragrunt.hcl:
# For production, we want to specify bigger instance classes and storage, so we specify override parameters here. These # inputs get merged with the common inputs from the root and the envcommon terragrunt.hcl inputs = { instance_class = "db.t2.medium" allocated_storage = 100 }注释点明合并语义:本环境的inputs会与 root 配置和 envcommon 配置中的公共 inputs 合并。于是 prod 的 MySQL 在继承name = "mysql_prod"、storage_type = "standard"等公共参数的同时,把实例规格提升为db.t2.medium、存储提升为 100GB。这种"同键覆盖、异键保留"的合并行为,正是 Terragrunt include 机制的核心价值:既保证跨环境一致,又允许关键环境差异化。
同时,root 配置 terragrunt.hcl 里还有一个对 digger 至关重要的扩展点——extra_digger_dependencies:
locals { extra_digger_dependencies = [ find_in_parent_folders("account.hcl"), find_in_parent_folders("region.hcl"), find_in_parent_folders("env.hcl"), ] ... }这个自定义 local 会被 digger 解析器读取,把account.hcl、region.hcl、env.hcl显式声明为项目依赖,确保这些层级变量文件变更时也能触发对应项目的 plan/apply(详见第七节)。
六、根配置的全局职责:远程状态与 provider 生成
虽然_envcommon是组件公共区,但账号/区域/环境三级的"更全局"公共逻辑放在根 terragrunt.hcl,通过include "root"被所有组件继承,主要包括:
1. 自动加载三级变量:用read_terragrunt_config(find_in_parent_folders("account.hcl"))等方式读取账号、区域、环境变量,提取出account_name、account_id、aws_region。
2. 生成 AWS provider 块:
generate "provider" { path = "provider.tf" if_exists = "overwrite_terragrunt" contents = <<EOF provider "aws" { region = "${local.aws_region}" # Only these AWS Account IDs may be operated on by this template allowed_account_ids = ["${local.account_id}"] } EOF }generate块让 Terragrunt 在运行时生成provider.tf,allowed_account_ids从账号层变量取值,防止误操作其他账号。
3. 配置 S3 远程状态:
remote_state { backend = "s3" config = { encrypt = true bucket = "${get_env("TG_BUCKET_PREFIX", "")}terragrunt-example-terraform-state-${local.account_name}-${local.aws_region}" key = "${path_relative_to_include()}/terraform.tfstate" region = local.aws_region dynamodb_table = "terraform-locks" } generate = { path = "backend.tf" if_exists = "overwrite_terragrunt" } }后端配置同样被generate写成backend.tf。get_env("TG_BUCKET_PREFIX", "")支持通过环境变量给 bucket 名加前缀(因为 S3 bucket 名全局唯一,示例 README 也提示需要自行改 bucket 或设置TG_BUCKET_PREFIX)。
4. 全局 inputs 聚合:把账号、区域、环境三级的 locals 用merge()合并成全局inputs,向下传递给所有子模块——多账号场景下无需在每个模块里重复传terraform_remote_state所需的数据源参数。
七、源码视角:digger 如何把 terragrunt 配置变成项目清单
_envcommon共享配置对 digger 的意义在于:digger 需要知道每个项目"依赖哪些文件",当这些文件变更时自动触发对应项目的 plan。digger 的 terragrunt 解析器位于 libs/digger_config/terragrunt/tac(基于 terragrunt-atlantis-config 思想实现,见 tac/readme.md),核心函数在 generate.go 的getDependencies中。
1. include 链被解析为依赖
从源码看,getDependencies会先调用parseModule(parse_hcl.go)判断模块是否为 parent、并抽取 include 列表;随后每个 include 的路径都会被追加进依赖集合:
if len(includes) > 0 { for _, includeDep := range includes { getDependenciesCache.set(includeDep.Path, getDependenciesOutput{nil, err}) dependencies = append(dependencies, includeDep.Path) } }也就是说,qa/mysql 的terragrunt.hcl中include "root"与include "envcommon"指向的两份文件(根terragrunt.hcl与_envcommon/mysql.hcl)都会被识别为依赖。这就是_envcommon文件被纳入 digger 项目触发条件的技术原理。
2. locals 中的扩展依赖
getDependencies还解析locals,读取ExtraDiggerDependencies字段(即extra_digger_dependencies):
if locals.ExtraDiggerDependencies != nil { dependencies = sliceUnion(dependencies, locals.ExtraDiggerDependencies) }这解释了第六节中extra_digger_dependencies的用途:account.hcl、region.hcl、env.hcl的变更也会触发依赖它们的项目。
3. 依赖去重、路径规范与缓存
getDependencies对相对路径做绝对化(makePathAbsolute)、统一为 Unix 分隔符(filepath.ToSlash)、过滤空串,并用singleflight.Group+ 内存缓存(GetDependenciesCache)避免重复解析;默认不开启级联依赖,cascadeDependencies由调用方通过 flag 控制。解析完成后,createBaseProject把依赖集写入项目的Autoplan.WhenModified,即"文件变更即触发自动规划"的白名单。多个 terragrunt 文件通过errgroup+ 信号量并发解析(Parse中sem := semaphore.NewWeighted(10)),兼顾效率与资源上限。
4. golden 文件验证生成结果
解析结果的"标准答案"固化在 tac/golden/infrastructureLive.yaml 中。可以看到,为non-prod/us-east-1/qa/mysql生成的when_modified列表为:
projects: - autoplan: enabled: false when_modified: - '*.hcl' - '*.tf*' - ../../../../terragrunt.hcl - ../../../../_envcommon/mysql.hcl - ../../../account.hcl - ../../region.hcl - ../env.hcl dir: non-prod/us-east-1/qa/mysql这张 golden 文件精确印证了上述机制:
'*.hcl'、'*.tf*'是本目录自身变更的匹配规则;../../../../_envcommon/mysql.hcl正是include "envcommon"指向的共享配置——_envcommon文件一旦修改,qa、stage、prod 三个环境的 mysql 项目都会触发;../../../../terragrunt.hcl对应include "root";account.hcl、region.hcl、env.hcl对应extra_digger_dependencies。
类似地,webserver-cluster项目的when_modified中对应的是../../../../_envcommon/webserver-cluster.hcl。这份 golden 文件既是转换器的测试基准,也是理解 digger 与_envcommon联动关系的最直接证据。
八、环境变量与版本选择:三个必须知道的细节
模块版本由环境自行决定:
_envcommon中source带?ref=v0.4.0只是默认值,任何环境都可以在自身terragrunt.hcl中重写terraform.source(配合expose = true读取base_source_url)选择不同版本——多环境灰度模块升级无需改动公共文件。数据库密码走环境变量:
master_username是公共参数,但密码必须通过TF_VAR_master_password注入,避免口令入库。这也是 digger/CI 场景下推荐的做法。S3 bucket 全局唯一:根配置中的 bucket 名必须全局唯一,示例 README 明确提示该名字已被占用,需要自行修改或通过
TG_BUCKET_PREFIX环境变量加前缀。
九、快速上手:部署与验证路径
结合示例 README 的操作指引,最小验证路径如下:
前置条件:Terraform 0.13.0+、Terragrunt v0.32.0+;修改根terragrunt.hcl中的 S3 bucket 或设置TG_BUCKET_PREFIX;配置 AWS 凭证;在prod/account.hcl与non-prod/account.hcl中填入真实的 AWS 账号 ID。
部署单个模块:
cd non-prod/us-east-1/qa/mysql export TF_VAR_master_password='(your-password)' terragrunt plan terragrunt apply部署区域内全部模块:
cd non-prod/us-east-1 export TF_VAR_master_password='(your-password)' terragrunt plan-all terragrunt apply-all验证效果:部署后可通过输出的url访问 ASG 服务,或用endpoint、db_name连接 MySQL 实例。示例 README 提供了完整的输出样例。
若要在 digger 中落地,可参考上述 golden 文件的生成逻辑:digger 会自动为每个terragrunt.hcl生成一个项目,并把_envcommon等依赖文件写入when_modified,实现"改公共配置、全环境联动"的自动化效果。
十、小结
_envcommon是 Terragrunt DRY 架构中承上启下的组件公共区:向上通过find_in_parent_folders读取账号/区域/环境变量,向下通过 include 被各环境 live 配置合并,侧向通过expose暴露 locals 供环境侧拼接模块版本。它把"每个组件跨环境不变的参数"收敛到单一来源,配合根配置的 provider 生成、远程状态与全局 inputs 聚合,让多账号、多区域、多环境的基础设施代码做到真正的一次定义、处处复用。
而对 digger 而言,_envcommon并非旁路文件——解析器(tac 包)通过 include 链与extra_digger_dependencies两套机制识别出这些共享配置,并将其写入项目的when_modified,最终固化在 infrastructureLive.yaml 这类 golden 文件中。理解这条从 HCL 到项目清单的数据流,你就能精准控制"什么文件变更触发哪些环境的重建",这也是把 Terragrunt 仓库接入 digger 自动化的关键一步。
- DevOps
- 云原生
- 后端
【免费下载链接】digger
Digger is an open source IaC orchestration tool. Digger allows you to run IaC in your existing CI pipeline ⚡️
相关推荐
Nuxt目录结构终极指南:srcDir配置与共享目录最佳实践
Nuxt目录结构终极指南:srcDir配置与共享目录最佳实践 你是否曾经被Nuxt项目中混乱的目录结构困扰过?文件散落在各处,不知道应该放在哪里才能获得最佳的开
前端后端Web框架SSRFLIP.js结合GSAP:打造专业级Web动画的完整指南
FLIP.js结合GSAP:打造专业级Web动画的完整指南 FLIP.js是一款轻量级的Web动画辅助库,通过FLIP(First, Last, Invert,
BitCPM-CANN-1B-gguf社区实践指南:用户案例分享与常见问题解决方案 🚀
BitCPM CANN 1B gguf社区实践指南:用户案例分享与常见问题解决方案 🚀 BitCPM CANN 1B gguf 是首个基于华为昇腾 NPU 原
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考