使用Terragrunt的实际Terraform目录结构

首先

我打算写一下在使用Terraform建立AWS环境时进行试错的目录结构。最后还附上了一些有用的信息,希望其中的任何一点能对您有所帮助。

我在Github上上传了这个项目的简单示例,如果有帮助的话,希望你也能参考一下。

 

关于Terraform和Terragrunt的概述和详细用法,我将省略不提。请参考其他人的文章和官方文档。

 

前提 tí) – prerequisite/condition/premise

    • 中規模のシステムを想定

 

    • 数個のサービスしか使わない小規模システムや、マイクロサービス構成のような大規模システムには合わないかと思います。

 

    二つ以上の環境(dev, staging, prodなど)を作成

目录结构

目录结构如下。

.
├── envs
│   ├── dev
│   │   ├── app
│   │   │   ├── 〇〇.tf
│   │   │   └── terragrunt.hcl
│   │   ├── cicd
│   │   ├── log
│   │   ├── network
│   │   ├── operation
│   │   ├── routing
│   │   ├── security
│   │   ├── storage
│   │   └── terragrunt.hcl
│   └── prod
├── modules
│   ├── acm
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   └── ...
└── shared
    ├── provider.tf
    ├── variables.tf
    └── version.tf        

环境目录

在envs文件夹下,按照环境分别建立了文件夹,然后在每个环境文件夹下,再根据不同的类别存放tf文件。
可以随意创建类别文件夹,但我个人的创建方式如下:

    • app

 

    • アプリケーション関連サービス

 

    • (ECS, ECR, Lambda, Cognito, SES, S3)

 

    • cicd

 

    • Github Actions用のIDプロバイダーやIAMロール、ecspressoから参照するリソースなど

 

    • log

 

    • ログ関連サービス

 

    • (S3, CloudWatch Logs, Kinesis Firehose, Athena)

 

    • network

 

    • ネットワーク関連サービス

 

    • (VPC, Internet Gateway, NAT Gateway)

 

    • operation

 

    • 運用系サービス

 

    • (SNS, Chatbot, CloudWatch Alerm, Config, Cloudtrail, Budgets)

 

    • routing

 

    • ルーティング関連サービス

 

    • (ALB, CloudFront, ACM, Route53)

 

    • security

 

    • セキュリティ関連サービス

 

    • (Security Hub, GuardDuty, KMS)

 

    • storage

 

    • ストレージ関連サービス

 

    (RDS, S3)

有时候,一个服务可能会包含在多个类别中,就像S3一样。这可以根据需求和规模对其用途进行分割,比如用于日志存储、前端应用程序或文件存储等。分类和细分程度可以根据系统的需求和规模进行更改。

模块目录

我们正在按照以下方针创建模块。

    • AWSサービスごとの単位で作成する

 

    • variables.tf, main.tf, outputs.tfファイルを必ず配置する(必要に応じてREADME.md)

 

    • 単一リソースのモジュールは作らない

 

    • 無駄にモジュールを作りすぎるのを防ぐため。

 

    • 例外として、複数リージョンにデプロイしたいサービスに関してはその限りではありません。

 

    例えばConfigやGuardDutyなどは全リージョンで有効化することが推奨されているため、以下のようにモジュールの呼び出し側でリージョンを選択するように実装しています。
provider "aws" {
  alias  = "us-east-2"
  region = "us-east-2"
  default_tags {
    tags = local.tags
  }
}

module "guardduty-us-east-2" {
  source = "../../../modules/guardduty"

  providers = {
    aws = aws.us-east-2
  }
}

共享目录

将存储在这个目录中的tf文件复制到每个类别的目录中并使用。
编辑环境中共享使用的变量和提供程序的设置等。

这种结构的优点和缺点 (Zhè de hé

好处

    • planやapplyの時間の短縮

 

    • 変更した部分のみplanやapplyをすることで、リソースが多くなってきても処理時間が増えにくくなります。

 

    • 規模が大きくなってもどこにリソースが存在するか把握しやすい

 

    • カテゴリ分けを適切に行うことで、どのディレクトリに何のサービスが記述されているかが把握しやすくなります。

 

    • ライフサイクルが違うリソースを分けることができる

 

    頻繁に変更が入りやすいアプリケーションなどのリソースと、ほとんど変更されないDBやネットワークなどのリソースを分けることで、意図しない変更が加わるのを防ぐことができます。

不利之處

sharedディレクトリのファイルを各ディレクトリに配置するのが面倒
おそらくシンボリックリンクを使うのが一般的だと思いますが、ディレクトリを細かく分けるとシンボリックリンクを作るのもかなり面倒になってきます。

依存関係の管理の負担
他のtfstate内の値を読み取る必要がある場合、選択肢としてはハードコーディングするか、terraform_remote_stateを使用することになると思います。
ハードコーディングが大変なのは勿論、terraform_remote_stateを使用する場合でもS3バケットの指定などが必要なためかなり大変になると思います。
また、うまく管理しないとどのモジュールがどのモジュールに依存しているかの把握が難しくなります。

ディレクトリごとにplan, applyするのが面倒
変更するたびに依存関係を考慮しながら順番にapplyしていくのはかなり骨が折れると思います。

モジュールごとにbackendの設定を記述するのが手間
ディレクトリごとにtfstateを格納するS3バケットやtfstateファイルのキーを指定する手間がかかります。

通过引入Terragrunt来解决劣势

我們將引入Terragrunt並解釋如何解決上述提到的缺點。

    • sharedディレクトリのファイルを各ディレクトリに配置するのが面倒

 

    • Terragruntのgenerateブロックを使用することで、指定したファイルを各ディレクトリに自動で生成することができます。

 

    以下のように書くと、_provider.tfという名前でsharedディレクトリ内のprovider.tfファイルがコピーされます。
generate "provider" {
  path      = "_provider.tf"
  if_exists = "overwrite_terragrunt"
  contents  = file("../../shared/provider.tf")
}
    • 依存関係の管理の負担

 

    • dependencyブロックを使用することで、モジュールの実行順序の制御や、モジュール間の値の受け渡しを管理することができるようになります。S3バケットの指定などは必要なく、依存するモジュールのパスを指定するだけなので比較的楽だと思います。

 

    • また、mock_outputs_merge_strategy_with_stateにshallowを設定することで、実際に依存先のアウトプットがある場合はそちらを参照し、apply前でまだアウトプットがない場合はmock_outputsの値を参照するようになります。

 

    terragrunt graph-dependenciesコマンドを実行することでモジュールの依存関係を確認することもできます。
# 依存先のlogモジュールからログ保管用S3バケットのIDを取得
dependency "log" {
  config_path = "../log"

  mock_outputs_merge_strategy_with_state = "shallow"
  mock_outputs = {
    log_s3_bucket_id = "mock-s3-bucket"
  }
}

inputs = {
  log_s3_bucket_id = dependency.log.outputs.log_s3_bucket_id
}
$ terragrunt graph-dependencies
digraph {
        "app" ;
        "app" -> "log";
        "app" -> "routing";
        "app" -> "security";
        "cicd" ;
        "cicd" -> "app";
        "cicd" -> "log";
        "cicd" -> "network";
        "cicd" -> "routing";
        "cicd" -> "storage";
        "log" ;
        "log" -> "security";
        "network" ;
        "network" -> "log";
        "operation" ;
        "operation" -> "app";
        "operation" -> "log";
        "operation" -> "routing";
        "operation" -> "security";
        "operation" -> "storage";
        "routing" ;
        "routing" -> "log";
        "routing" -> "network";
        "security" ;
        "storage" ;
        "storage" -> "network";
        "storage" -> "security";
}

ディレクトリごとにplan, applyするのが面倒
run-allコマンドを使用することで、複数モジュールの依存関係を自動で解決した上でplanやapplyコマンドを一回のコマンドで実行することができます。

モジュールごとにbackendの設定を記述するのが手間
今回の構成では、各環境のディレクトリに配置しているterragrunt.hclにtfstateを格納するS3バケットやロック用のDynamoDBなどの設定を書いて、その配下のディレクトリではそれをincludeブロックで読み込むように設定することで設定値を複数回書く手間を省いています。

remote_state {
  backend = "s3"
  generate = {
    path      = "_backend.tf"
    if_exists = "overwrite"
  }
  config = {
    bucket               = "${get_env("SYSTEM")}-${get_env("ENVIRONMENT")}-terraform-state-${get_env("AWS_ACCOUNT_ID")}-s3-bucket"
    key                  = "${path_relative_to_include()}/terraform.tfstate"
    region               = get_env("AWS_REGION")
    encrypt              = true
    bucket_sse_algorithm = "AES256"
    dynamodb_table       = "${get_env("SYSTEM")}-${get_env("ENVIRONMENT")}-terraform-lock-dynamodb-table"
    s3_bucket_tags = {
      "Terraform"   = "true"
      "Environment" = get_env("ENVIRONMENT")
      "System"      = get_env("SYSTEM")
    }
    dynamodb_table_tags = {
      "Terraform"   = "true"
      "Environment" = get_env("ENVIRONMENT")
      "System"      = get_env("SYSTEM")
    }
  }
}
include "root" {
  path = find_in_parent_folders()
}

总结

希望通过介绍一种实用的目录结构,并详细分解模块的结构,以及通过Terragrunt解决相关问题的方法来引入,同时希望您能够体验到Terragrunt带来的好处。

额外礼品

我打算随意列举一些有用的技巧。

推荐使用direnv。

由于每个目录的环境可能不同,因此需要重新设置环境变量,非常麻烦。
因此,使用 direnv,可以为每个目录定义环境变量,并且在切换到目标目录时会自动加载环境变量。我建议您使用这个工具来减少工作量和减少错误。

 

清水的推荐

大家是怎样进行Terraform和Terragrunt的版本管理的呢?
我觉得可以选择使用tfenv、tgenv等工具,或者使用Docker。
当使用Terraform时,可能还会需要其他一些CLI工具,比如tfsec、tflint,以及之前提到的direnv,我认为如果能够统一管理这些工具的版本会更方便。

因此,我推荐使用aqua。通过使用aqua,您可以轻松进行上述工具的版本管理。另外,它还支持Github Actions和Renovate,因此只需使用这一个工具就可以很大程度上解决工具版本管理的问题。

这是我的 aqua 的配置文件。

---
# aqua - Declarative CLI Version Manager
# https://aquaproj.github.io/
# checksum:
#   # https://aquaproj.github.io/docs/reference/checksum/
#   enabled: true
#   require_checksum: true
#   supported_envs:
#   - all
registries:
- type: standard
  ref: v4.23.0 # renovate: depName=aquaproj/aqua-registry
packages:
- name: aquasecurity/tfsec@v1.28.1
- name: hashicorp/terraform@v1.5.2
- name: gruntwork-io/terragrunt@v0.48.0
- name: terraform-linters/tflint@v0.47.0
- name: reviewdog/reviewdog@v0.14.2
- name: suzuki-shunsuke/tfcmt@v4.4.2
- name: direnv/direnv@v2.32.3

 

VSCode的扩展功能

我正在使用以下的擴充功能。

HashiCorp.terraform
Terraformの公式拡張機能。コード補完機能や自動フォーマット機能などがあります。

hashicorp.hcl
こちらもHashiCorpが出している拡張機能です。hclファイルにシンタックスハイライトがつきます。

tfsec.tfsec
tfsecでのスキャンを実行することができます。コマンドでやるよりもチェック結果や修正方法が確認しやすいです。

fredwangwang.vscode-hcl-format
hclファイルの自動フォーマットができるようになります。

{
  "recommendations": [
    "HashiCorp.terraform",
    "hashicorp.hcl",
    "tfsec.tfsec",
    "fredwangwang.vscode-hcl-format"
  ]
}

VSCode的设置

通过设置文件中的`files.exclude`来排除自动生成的文件,可以使侧边栏的显示更整洁。另外还可以设置自动格式化选项,以便在保存文件时自动进行格式化。

{
  "files.exclude": {
    "**/_*.tf": true,
    "**/.terraform": true
  },
  "[terraform]": {
    "editor.defaultFormatter": "hashicorp.terraform",
    "editor.formatOnSave": true,
    "editor.formatOnSaveMode": "file"
  },
  "[terraform-vars]": {
    "editor.defaultFormatter": "hashicorp.terraform",
    "editor.formatOnSave": true,
    "editor.formatOnSaveMode": "file"
  },
}

注册命令别名

因为每次输入terraform和terragrunt的命令太麻烦了,所以我在.zshrc中注册了以下别名。

alias tf="terraform"
alias tg="terragrunt"

跳过确认的方法

无论是使用Terraform还是Terragrunt在执行apply的时候,都会要求确认。但是在学习过程中,这可能会成为干扰。可以使用以下选项跳过确认。

$ terragrunt run-all apply --terragrunt-non-interactive
$ terraform apply -auto-approve

获取AWS帐户ID和区域的方法

可能因为经常意外使用,记住这个可能会很有用。

# アカウントID
data "aws_caller_identity" "current" {}
data.aws_caller_identity.current.account_id

# リージョン
data "aws_region" "current" {}
data.aws_region.current.name

关于init的-migrate-state、-reconfigure、-upgrade。

當我剛開始使用Terraform時,我不理解這三個選項的含義,當我執行init時,會在錯誤訊息中看到它們,然後就像被告知般執行。我已經整理了這三個選項的含義以及適用的時機。

    • -migrate-state

backendの設定が変更された時に実行する
-reconfigureとどちらか一方を選択する
既存のstateの内容を可能な限り新しいbackendにコピーする

-reconfigure

backendの設定が変更された時に実行する
-migrate-stateとどちらか一方を選択する
既存の設定を無視し、既存のstateの移行は行わない

-upgrade

インストール済みのすべてのmoduleを最新のソースコードに更新したい場合
pluginのバージョンを変更した場合

新しいバージョンのpluginがインストールされ、dependency lock fileが更新される

请参阅以下文档以获取更详细信息。

 

请看题下。