只用 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,则可以轻松解决。