使用Terragrunt将Terraform代码“适度地”DRY化
概述
我們將驗證一個能夠保持 Terraform 代碼的 DRY 特性和簡化 CLI 執行的工具,名為 Terragrunt。作為使用案例,我們將考慮將已經有 workspace 管理的 Terraform 環境移植到 Terragrunt 的情況。
Terraform的工作区功能是什么?
这是Terraform的一个标准功能,可以用于在多个环境中共享相同的Terraform代码的解决方案。
通过自动管理后端以及使用`${terraform.workspace}`变量将环境名称注入到Terraform代码中,可以高效地编写具有高度可复用性的Terraform代码。
Terragrunt是什么
听起来像是一种能够让Terraform代码DRY并且易于维护的工具。本次将验证与backend.tf等通用模板文件相关的DRY功能。
动力
以汉语原生方式转述以下内容,只提供一种选项:
我将提供有关在什么动机下要摆脱以workspace为基础、采用DRY方法构建的Terraform环境的前提。
尽管使用workspace的方式可以灵活运用,但过度追求DRY往往会导致以下负面影响。
-
- 特定の環境だけ少し構成を変更することが難しい
terraform コマンドの実行ディレクトリが環境ごとに分離されておらず、CI/CDツールとの相性が悪い
这是我的观点,Terraform实际上是一个用于声明式地描述基础架构的工具,与应用程序代码不同,它不需要过度追求对不同环境的DRY,而是更倾向于在一定程度上容忍代码的重复管理,以便可以灵活地管理。
考虑以这种动机,摆脱过度依赖DRY的Terraform环境,并引入能够适度管理代码的terragrunt。
搭建基本环境
创建Terraform项目
在移植到terragrunt之前,我们会构建一个作为基准的Terraform环境。
要求如下:
-
- 本番環境と開発環境を env 配下の tfvars ファイルで分離
-
- backendはS3を指定
-
- 基本となるリソースはプロジェクト直下の main.tf と、そこから呼び出される modules ディレクトリ配下で定義
簡単のため、作成するリソースはS3のみにしています
我撰写的代码如下所示。
https://github.com/kzk-maeda/terraform-ci/commit/69451d2eb449be25b352a0ab24897e28e18a9a96
├── Taskfile.yml
├── env
│ ├── dev
│ │ └── terraform.tfvars
│ └── prod
│ │ └── terraform.tfvars
├── module
│ └── s3
│ ├── main.tf
│ ├── output.tf
│ ├── provider.tf
│ └── variables.tf
├── backend.tf
├── main.tf
├── provider.tf
└── variable.tf
在每个环境中应用
首先,使用terraform workspace new命令创建所需的工作区。
terraform workspace new dev
terraform workspace new prod
计划/应用命令被整理在 Taskfile.yml 中,但基本上是通过选择工作区并传递环境特定的 terraform.tfvars 参数来实现的。
terraform plan -var-file ./env/`terraform workspace show`/terraform.tfvars
terraform apply -var-file ./env/`terraform workspace show`/terraform.tfvars
然后,对于在bachend.tf中定义的桶进行对应,将在远程后端创建以下方式的状态文件。
s3://env:/dev/path/to/terraform.tfstate
s3://env:/prod/path/to/terraform.tfstate
这里的重点是,terraform workspace 自动将与环境对应的环境替换为 :env/dev 或 :env/prod 这样的键。
使用 terraform workspace,可以以这种方式对多个环境的状态文件进行分割管理。
这个工作区管理的Terraform项目已经创建完毕,接下来我们将把它转换为terragrunt管理的项目。
安装Terragrunt
安装
请参考以下网站上的安装步骤,并根据您的环境进行安装。
- https://terragrunt.gruntwork.io/docs/getting-started/install/
基本上因为是由包管理器进行管理,所以应该不会很难。
移动state文件
将由 terraform workspace 创建的状态文件移动到可以由 terragrunt 管理的位置。
如前所述,在 terraform workspace 中,会在以下 Key 下创建 state 文件。
s3://env:/dev/path/to/terraform.tfstate
s3://env:/prod/path/to/terraform.tfstate
如果使用terragrunt,这个env:dev层次结构会变得棘手,所以无法直接使用此键。因此,将状态文件移动到以下键下。
s3://env/dev/path/to/terraform.tfstate
s3://env/prod/path/to/terraform.tfstate
环境已更改为env。在terragrunt中,使用在后续配置文件中定义的 ${path_relative_to_include()} 函数,将其替换为环境/ dev 等环境特定层级来引用状态文件,因此需要根据该形式移动状态文件。
对支持terragrunt的代码进行修正(开发版)
接下来,我们将把已经创建的 Terraform 代码转换为 Terragunt 可管理的格式。
以下是必要的步骤。
-
- 将 *.tf 文件移动到环境目录中
在 *.tf 文件中使用 terraform.workspace 来填充值的位置,通过 vars 转接实现注入
将 terragrunt 的 backend.tf 和 provider.tf 文件添加到 git ignore 中,以实现DRY(不重复编写)的管理
创建并部署 terragrunt 配置文件。
将*.tf文件移动到相应环境的目录中。
将上述的目录结构改为以下方式。
├── Taskfile.yml
├── env
│ ├── dev
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terragrunt.hcl
│ │ └── terraform.tfvars
│ └── prod
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terragrunt.hcl
│ │ └── terraform.tfvars
├── module
│ └── s3
│ ├── main.tf
│ ├── output.tf
│ ├── provider.tf
│ └── variables.tf
├── .gitignore
└── terragrunt.hcl
请将 main.tf 等文件移动到每个环境的子目录下,就像后面将要讨论的 terragrunt.hcl 文件一样,请认识这个层次结构中会创建这样的文件。
在*.tf文件中,通过使用terraform.workspace来填充值的部分,需要通过vars来注入进行修正。
在原始的 main.tf 文件中,可以看到以下类似的描述。
# main.tf
resource "aws_s3_bucket" "this" {
bucket = "${terraform.workspace}-${var.bucket_name}-${data.aws_caller_identity.current.account_id}"
}
在${terraform.workspace}中,指定的工作区名称(例如dev)被用作参数值,但是在摆脱工作区的情况下,该参数将无法使用。
(严格来说,它在backend中引用了env: Key下的值)
我們將以vars方式進行變更以獲取這個。
# main.tf
resource "aws_s3_bucket" "this" {
bucket = "${var.env}-${var.bucket_name}-${data.aws_caller_identity.current.account_id}"
}
只需修改 variables.tf 文件,通过 vars 来注入环境变量就可以了。
使用terragrunt可以将backend.tf和provider.tf文件管理得更加符合DRY原则,并将它们添加到.gitignore文件中。
在接下来的步骤中,将引入 terragrunt,但是当 terragrunt 执行时,它会在目标目录中创建 backend.tf 和 provider.tf 文件。
由于我们正在尝试将这些文件以 DRY(Don’t Repeat Yourself)的方式进行管理,所以如果每次创建时都将它们放在git管理下,最终管理文件的数量将会增加,无法享受到优势。
所以,我会在.gitignore文件中将这些文件设为忽略。
# .gitignre
backend.tf
provider.tf
4. 创建和放置 terragrunt 配置文件
在项目的根目录和每个环境的目录中,都要放置 terragrunt 的配置文件。
这两个配置文件的文件名都是 terragrunt.hcl。
首先,项目底下的配置文件可以按照以下方式进行记录。
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
bucket = "kzk-sandbox-terraform-tfstate"
key = "${path_relative_to_include()}/terragrunt/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
region = "ap-northeast-1"
version = "~>4.0"
shared_credentials_file = ".aws/credential"
profile = "ci"
}
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
EOF
}
在 remote_state 块中,编写用于 backend.tf 文件的模板内容。
请注意 “${path_relative_to_include()}/” 这个路径。
这个路径将会被替换为前面提到的 env/dev 等键。
在”provider”方块中,我们将内容写入 provider.tf 文件中。
接下来,我们将描述环境特定目录下的配置文件。
include "root" {
path = find_in_parent_folders()
}
通过配置这两个设置文件,可以将分散的terraform代码按照环境划分,并以DRY的方式编写可供各个环境共享的部分。
指定一个 state 文件并执行计划。
使用terragrunt命令可以像terraform一样操作,它能够执行特定的命令。
在每个环境的目录中切换,并执行terragrunt命令。
cd path/to/env/dev/
terragrunt init
terragrunt plan
terragrunt apply
通过运行 terragrunt init 命令,您将看到在 env/dev 目录下生成 backend.tf 和 provider.tf 文件。接下来,执行 plan / apply 命令来应用差异到环境中。
我认为你能够按照这个步骤应用terragrunt。
参考差异在这里。
最后
我已经记录了从terraform工作区迁移到terragrunt的步骤。
为了对多个环境的Terraform代码进行DRY管理,最开始的步骤通常是引入模块,接着使用工作空间。我认为,虽然这种做法在一定程度上可以满足需求,但过度地进行DRY描述会带来问题,因此我想摆脱对工作空间的依赖。然而,我又不想重复管理相同的文件,所以我尝试了使用terragrunt作为解决方案。
根据Terraform的最佳实践进行管理仍在我个人中学习阶段,我认为此次尝试的Terragrunt是一个可以保证环境灵活性并且在一定程度上实现DRY代码管理的合适解决方案。
如果对于有相同烦恼的人们能够成为某种参考,我将感到非常幸运。