使用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 可管理的格式。

以下是必要的步骤。

    1. 将 *.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代码管理的合适解决方案。

如果对于有相同烦恼的人们能够成为某种参考,我将感到非常幸运。

广告
将在 10 秒后关闭
bannerAds