只用 Terraform 是困难模式,所以我们要使用 Terragrunt

使用Terraform进行管理很困难。

大家都在使用IaC(基础设施即代码)吗?如果要将公共云进行IaC,Terraform非常方便!

但是,一旦正式开始使用,便会迅速出现这样的问题。

    • 複数環境の楽な分け方を知りたい

ワークスペースはなんか嫌だ
とはいえ、環境間で共通するボイラープレートをどうにかしたい

環境内で適用するモジュールを細分化・分岐したいけど面倒

環境ごとに使うモジュールを切り替えたい

テスト環境はAuroraではなく安いRDSにしたい

モジュール(tfstate)を分割して小さい範囲で適用したい

大きなモジュールは影響範囲がわからないし、差分計算にそれなりに時間がかかってしまう

分けたモジュールを一括適用するのが面倒

モジュール間の依存関係がわからない

モジュール(tfstate)間での値参照が面倒

terraform_remote_state の呪文が冗長すぎて厳しい

環境ごとに適用するモジュールのバージョンを固定化したい

開発環境は常に最新、本番は特定バージョンのモジュール定義参照で固定したい

在定期的技术博客上,经常会讨论Terraform环境差异管理的方法。然而,如果使用Terragrunt,这个问题可以相对轻松地解决。

Terragrunt 是什么?

Terragrunt 是 Gruntwork 公司维护的 Terraform 的包装器,它为 Terraform 添加了一些缺少的宏功能。

该软件在GitHub上以MIT许可证进行公开。

由于安装程序已经存在于主要仓库中,因此您可以使用以下方式进行安装。

brew install terragrunt (macOS)

scoop install terragrunt (Windows)

只需简单地将terraform命令替换为terragrunt命令即可。在内部,Terragrunt会进行一些准备工作,最终仍会调用Terraform。

只要你了解Terraform的基本用法,Terragrunt本身并不复杂。

Terragrunt项目的基本目录结构

我們希望一起查看實際的Terragrunt專案的目錄結構。

modules/ の下にモジュールごと(e.g., vpc, db など)ディレクトリを掘る

モジュール作り方は通常のterraformモジュールと同じ

envs/ の下に環境ごと(e.g., stg, prod など)ディレクトリを掘る

直下に環境全体の変数定義を収める env.hcl を置く
参照するモジュールのディレクトリをそれぞれ下に掘って、その中にモジュールの参照定義となる terragrunt.hcl を作成する

modules/
  modA/
    *.tf
  modB/
  modC/
envs/
  terragrunt.hcl
  stg/
    env.hcl
    modA/
      terragrunt.hcl
    modB/
    modC/
  prod/
    modA/
    modB/
    modC/

让我们分别查看每个的详细内容吧。

模块定义(模块/*/*.tf)

通常的Terraform模块定义与一般情况相同。在我的情况下,我经常将文件划分如下。

input.tf: 入力パラメータ

output.tf: 出力値

機能名.リソース種別.tf: e.g., featureA.route53.tf

该模块的目录可以从 envs/*/*/terragrunt.hcl(如下所述)中引用。

环境全体的定义(envs/terragrunt.hcl)

我们将放置以下类型的整个环境定义文件。在实际应用每个环境的模块时将被执行。

做的事情很简单,只是为每个环境的提供商和Terraform本身的配置文件1定义元模板。

# 各環境ごとの *.tfstate の入れ方についての定義
remote_state {
  backend = "s3"

  config = {
    bucket = "foobar-tfstate"

    # 下記のようにしておくと、例えば
    # `envs/stg/modA` の tfstate は
    # `foobar/stg/modA.tfstate` に格納されるようになる
    key    = "foobar/${path_relative_to_include()}.tfstate"

    region = "ap-northeast-1"

    profile = "foobar-terraform"
  }

  generate = {
    path      = "backend.tf"
    if_exists = "overwrite"
  }
}

generate "provider" {
  path      = "provider.tf"
  if_exists = "overwrite_terragrunt"

  contents = <<EOF
terraform {
  required_version = ">= 1.3.7"

  required_providers {
    aws = {
      # See https://github.com/terraform-providers/terraform-provider-aws
      version = "~> 4.50.0"
    }
  }
}

provider "aws" {
  profile = "foobar-terraform"
  region  = "ap-northeast-1"

  default_tags {
    tags = {
      Project = "foobar"
    }
  }
}
EOF

}

环境定义(envs/*/env.hcl)

将跨越整个环境的定义(例如变量)放置。

# 検証環境

locals {
  # 環境名
  env = "stg"
  # バックアップ保持日数
  backup_retention_days = 3660
}

環境和每个模块的定义(envs/*/*/terragrunt.hcl)

将每个环境下所使用的模块调用定义放置在相应位置。

# 環境の定義 (`env.hcl`) を local.env.locals として参照できるようにする
locals {
  env = read_terragrunt_config(find_in_parent_folders("env.hcl"))
}

# 全環境の定義 (`envs/terragrunt.hcl`) をインクルードする
include {
  path = find_in_parent_folders()
}

# モジュールを参照する
terraform {
  source = "../../../modules//modA"
}

# 他のモジュールの出力値を参照する
# (素のTerraformだと面倒な作業の一つ)
dependency "modB" {
  config_path = "../modB"
}

# モジュールの入力値を指定する
inputs = {
  env                   = local.env.locals.env
  backup_bucket         = dependency.modB.outputs.backup_bucket
  backup_retention_days = local.env.locals.backup_retention_days
}

应用Terragrunt

当您准备好了类似上述的目录(以及相应的S3和IAM配置文件等),只需要使用以下命令应用 Terragrunt。

实际上,modA和modC依赖于modB,因此我们首先从modB开始应用。

$ cd envs/stg/modB
$ terragrunt apply

只需要执行 terragrunt apply ,而不是 terraform apply 。顺便提一下,由于 Terragrunt 会自动处理,所以不需要执行 terraform init 。除了可能会拼写错误的地方外,使用 Terragrunt 很简单。

虽然如此,每次都要打开模块文件夹进行这个操作很麻烦。

Terragrunt会自动计算每个模块的依赖顺序,因此,您不需要为每个环境逐个打开模块文件夹并进行应用操作,只需按照以下方式,在每个环境的目录下运行 terragrunt run-all apply,它将自动计算应用顺序并执行全部的初始化和应用操作。非常方便。

$ cd envs/stg
$ terragrunt run-all apply
The stack at /Users/ksaitou/foobar/envs/stg will be processed in the following order for command apply:
Group 1
- Module /Users/ksaitou/foobar/envs/stg/modB/

Group 2
- Module /Users/ksaitou/foobar/envs/stg/modA/
- Module /Users/ksaitou/foobar/envs/stg/modC/

Are you sure you want to run 'terragrunt apply' in each folder of the stack described above? (y/n)

只需键入”y”,它将按顺序对所有模块执行terraform init和terraform apply。

由于Terragrunt本质上是Terraform的宏和包装器,如果它的操作不像预期的那样,您可以查看 envs/stg/*/.terragrunt-cache 来确认生成源代码,然后通过对其进行故障排除来解决问题。

根据每个环境来固定模块的版本。

我认为在验证环境中,可以使用最新版本的Terraform模块进行开发,而在生产环境中,可以将其固定在旧版本直到发布时的情况下,Terragrunt也可以满足这样的需求。

    • stg: 常に main ブランチの内容を適用したい

 

    prod: 特定のタグの内容を適用したい

只需将 terragrunt.hcl 文件中 terraform { … } 模块路径引用部分根据不同环境进行修改,格式如下。

terraform {
  // 直接ディレクトリを参照する場合
  // source = "../../../modules//modB"

  // 特定のgitリポジトリを参照する場合 (リモートリポジトリの参照のみ可能)
  // source = "git::gitリポジトリURL//リポジトリ内のディレクトリパス?ref=コミットの参照"
  source = "git::ssh://git@github.com:ssc-ksaitou/foobar-modules.git//modules/modB?ref=1.0.0"
}

根据手册,对于 source = “…” 的路径,可以使用 Hashicorp 的 go-getter 进行获取,因此除了 git 之外,还可以将 Mercurial、zip 文件、Amazon S3 作为源进行使用(无论是否实际操作)。

我认为,尽管现在提到的go-getter不能参考本地git存储库,但其潜在需求是希望引用本地存储库(自身)的modules目录。所以,为了引用git的特定标签,需要提前准备好远程存储库,因为go-getter目前没有引用本地git存储库的功能。这可能有点麻烦,但最好事先考虑并制定一个可行的运行规则。

总结

只用Terraform就架設中大型基礎架構 (Infrastructure as Code, IaC) 会非常麻烦,但是如果使用Terragrunt,则可以轻松解决。

请问大家的代码仓库中是不是有 backend.tf、main.tf和provider.tf 文件呢?↩在terragrunt.hcl的依赖项中进行计算。↩

bannerAds