TerragruntはTerraformのState格納用S3バケットを自動作成してくれる

2022.07.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Stateファイル格納用S3バケットどうやって作成するか問題

AWSリソース群をプロビジョニングするのにTerraformを活用する場合、(Terraform Cloudではないのであれば)StateファイルはS3バケットに格納するケースが殆どかと思います。この場合にTerraform初学者が疑問に思うのが以下の点。

?「このStateファイル格納用S3バケットもTerraformでプロビジョニングする?」

  • 同じルートモジュール内でプロビジョニングする?しようと思ったら、最初 local backendで始めて、S3バケットが作成できてからS3 backendに切り替える?
    • (※ルートモジュールというのはplan,applyなど各種Terraformコマンドを実行する場所以下のことを指します。言い換えるとリソースが同じStateファイル内で管理される場所(範囲)です)
    • 誤ってそのS3バケットをプロビジョニングしているコードを削除してapplyしたら…?
    • → 上記の様にややこしい&危険なのでこの方法はおすすめしません。
  • 別途「Stateファイル格納用S3バケットをプロビジョニングするためのルートモジュール」を作成する?
    • そのモジュールのStateを格納するS3バケットのプロビジョニングはどうするのよ…
  • Terraform管理外で、AWSマネジメントコンソールやCLIで作る(私はコレが多いです)
  • CFnテンプレートを用意しておく
    • マルチアカウントでTerraformを使いたい場合はCFn StackSetsでアカウント作成と同時にプロビジョニングできる
  • State Locking機能を使うんだったら、DynamoDBテーブルについても同様に考えないといけない

Terragruntは自動で作ってくれるらしい

Terragruntのドキュメントを眺めていると、以下の記述を発見しました。

Terragrunt will automatically create the configured DynamoDB tables and S3 buckets for storing remote state if they do not already exist.
(AWS Authより引用)

ほう、これは便利そうですね。早速試してみたいと思います。

Terragruntとは

TerragruntはTerraformのラッパーツールで、TerraformのコードをDRY(Don't Repeat Yourself、つまりコードの重複を減らす、無くす)にかつメンテしやすくすることができます。特に大規模にTerraformを使う場合に効果を発揮します。

やってみた

失敗: Terraformでbackendを定義する

まずは以下のTerraformコードを用意しました。

main.tf

terraform {
  required_version = "= 1.2.5"

  backend "s3" {
    bucket = "kazue-bucket-not-created"
    key    = "test.tfstate"
    region = "ap-northeast-1"

    dynamodb_table = "kazue-ddb-table-not-created"
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "4.22.0"
    }
  }
}

これに対して以下Terragrunt設定ファイルを同じディレクトリに配置します。Terragruntでは何もしていませんね。

terragrunt.hcl

terraform {
  source = "./"
}

initしてみましょう。

% terragrunt init 
WARN[0001] No double-slash (//) found in source URL /xxxx/20220718/terraform. Relative paths in downloaded Terraform code may not work. 

Initializing the backend...
╷
│ Error: Failed to get existing workspaces: S3 bucket does not exist.
│ 
│ The referenced S3 bucket must have been previously created. If the S3 bucket
│ was created within the last minute, please wait for a minute or two and try
│ again.
│ 
│ Error: NoSuchBucket: The specified bucket does not exist
│       status code: 404, request id: CF94DVV33ZVAGRXZ, host id: yy2ZWfGtoaQtIFhPOVVqfmBXT7TUIe4kJ+y6QyZgeMZYzJyrsblNxAPxYHftcQUBzFZOIqnxWwE=
│ 
│ 
│ 
╵

INFO[0003] Encountered an error eligible for retrying. Sleeping 5s before retrying.  prefix=[/xxxx/20220718/terraform] 

Initializing the backend...
╷
│ Error: Failed to get existing workspaces: S3 bucket does not exist.
│ 
│ The referenced S3 bucket must have been previously created. If the S3 bucket
│ was created within the last minute, please wait for a minute or two and try
│ again.
│ 
│ Error: NoSuchBucket: The specified bucket does not exist
│       status code: 404, request id: R589F35QHY0KHX7W, host id: lGk/Ygv7qkBO+ESmLjAWqwTa2lpenFN9Pm0fZENkPu5hjmLcZ5/vJJk2v8RnaqzrKzbKGrjgueo=
│ 
│ 
│ 
╵

INFO[0011] Encountered an error eligible for retrying. Sleeping 5s before retrying.  prefix=[/xxxx/20220718/terraform] 

Initializing the backend...
╷
│ Error: Failed to get existing workspaces: S3 bucket does not exist.
│ 
│ The referenced S3 bucket must have been previously created. If the S3 bucket
│ was created within the last minute, please wait for a minute or two and try
│ again.
│ 
│ Error: NoSuchBucket: The specified bucket does not exist
│       status code: 404, request id: QV0Y2WPT44RXP0DH, host id: Juj61uZ5VQbWhY89UFqKTP19qLaKVInjhT37N0JjtZpDg25S0r2injvruqgFy12JrbfUrNPMyRE=
│ 
│ 
│ 
╵

INFO[0018] Encountered an error eligible for retrying. Sleeping 5s before retrying.  prefix=[/xxxx/20220718/terraform] 
ERRO[0023] 1 error occurred:
        * Exhausted retries (3) for command terraform init
 
ERRO[0023] Unable to determine underlying exit code, so Terragrunt will exit with error code 1

S3バケットが無い!とエラーになりました。自動作成はしてくれませんでしたね。またAuto-retryが走って3回実行されることがわかりました。

成功:Terragrunt上でbackendを定義する

Terraform上からbackendの設定を削除して、

main.tf

  terraform {
    required_version = "= 1.2.5"
  
-  backend "s3" {
-    bucket = "kazue-bucket-not-created"
-    key    = "test.tfstate"
-    region = "ap-northeast-1"
-    
-    dynamodb_table = "kazue-ddb-table-not-created"
-  }
(略)  
  }

代わりにTerragrunt設定ファイルに書きます。

terragrunt.hcl

  terraform {
    source = "./"
  }

+ remote_state {
+   backend = "s3"
+   config = {
+     bucket = "kazue-bucket-not-created"
+     key    = "test.tfstate"
+     region = "ap-northeast-1"
+     
+     dynamodb_table = "kazue-ddb-table-not-created"
+   }
+ }

この状態でコマンド実行してみましょう。

 % terragrunt init
WARN[0001] No double-slash (//) found in source URL /xxxx/20220718/terraform. Relative paths in downloaded Terraform code may not work. 
WARN[0001] Encryption is not enabled on the S3 remote state bucket kazue-bucket-not-created. Terraform state files may contain secrets, so we STRONGLY recommend enabling encryption!  prefix=[/xxxx/20220718/terraform] 
Remote state S3 bucket kazue-bucket-not-created does not exist or you don't have permissions to access it. Would you like Terragrunt to create it? (y/n) y


Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v4.22.0...
- Installed hashicorp/aws v4.22.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

╷
│ Warning: Missing backend configuration
│ 
│ -backend-config was used without a "backend" block in the configuration.
│ 
│ If you intended to override the default local backend configuration,
│ no action is required, but you may add an explicit backend block to your
│ configuration to clear this warning:
│ 
│ terraform {
│   backend "local" {}
│ }
│ 
│ However, if you intended to override a defined backend, please verify that
│ the backend configuration is present and valid.
│ 
╵

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

「Would you like Terragrunt to create it?」と訊かれ(4行目)、yで答えると作成されたように見えます。

作成されたリソースを見てみる

S3バケット

作成されていました。 kazue-bucket-not-created

各設定がどうなっているのか確認しました。

  • バケットのバージョニング: 有効
  • デフォルトの暗号化: 有効
    • SSE-KMS
      • AWS 管理キー (aws/s3)
  • ブロックパブリックアクセス: すべて ブロック
  • バケットポリシー: 転送中の暗号化の強制と、ルートアカウントによるアクセスの許可が設定されています。
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "EnforcedTLS",
                "Effect": "Deny",
                "Principal": "*",
                "Action": "s3:*",
                "Resource": [
                    "arn:aws:s3:::kazue-bucket-not-created",
                    "arn:aws:s3:::kazue-bucket-not-created/*"
                ],
                "Condition": {
                    "Bool": {
                        "aws:SecureTransport": "false"
                    }
                }
            },
            {
                "Sid": "RootAccess",
                "Effect": "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::123456789012:root"
                },
                "Action": "s3:*",
                "Resource": [
                    "arn:aws:s3:::kazue-bucket-not-created",
                    "arn:aws:s3:::kazue-bucket-not-created/*"
                ]
            }
        ]
    }
  • ACL: 有効
    • バケット所有者 (AWS アカウント)
      • オブジェクトリスト、書き込み有効
      • バケットACLの読み取り、書き込み有効
  • オブジェクト所有者: オブジェクトライター

DynamoDBテーブル

作成されていました。 kazue-ddb-table-not-created

こちらも設定内容を確認します。

  • パーティションキーがLockID (String)、ソートキーなしです。これはDynamoDBテーブルをState Locking用途で使う際の必須要件なので当然ですね。
  • キャパシティーモード: オンデマンド
  • 保管時の暗号化: Amazon DynamoDB が所有
    • S3バケットと比べると緩いと思うのですが、まあState Lockingの情報はそれほどクリティカルではないという判断なのでしょうか。

destroy時は併せて削除される?

% terragrunt destroy
WARN[0001] No double-slash (//) found in source URL /xxxx/20220718/terraform. Relative paths in downloaded Terraform code may not work. 
WARN[0001] Encryption is not enabled on the S3 remote state bucket kazue-bucket-not-created. Terraform state files may contain secrets, so we STRONGLY recommend enabling encryption!  prefix=[/xxxx/20220718/terraform] 

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v4.22.0

╷
│ Warning: Missing backend configuration
│ 
│ -backend-config was used without a "backend" block in the configuration.
│ 
│ If you intended to override the default local backend configuration,
│ no action is required, but you may add an explicit backend block to your
│ configuration to clear this warning:
│ 
│ terraform {
│   backend "local" {}
│ }
│ 
│ However, if you intended to override a defined backend, please verify that
│ the backend configuration is present and valid.
│ 
╵

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

No changes. No objects need to be destroyed.

Either you have not created any objects yet or the existing objects were
already deleted outside of Terraform.

Destroy complete! Resources: 0 destroyed.

コンソールに特に表記は無いですね。

その後AWSマネジメントコンソールで確認しましたがS3バケット、DynamoDBテーブルともに削除されていませんでした。確かに改めて考えてみると、このS3バケットやDynamoDBテーブルは他の箇所(例:別のTerraformルートモジュール)でも使われている可能性があるので、削除するのはマズイですね。

まとめ

TerragruntではStateファイル格納用のS3バケット、Stateファイルロック用のDynamoDBテーブルを自動作成してくれるということで、その挙動を確認しました。設定を確認したところベストプラクティスに沿っていると言える構成になっているので、プロジェクトのポリシーとしてこのS3バケットとDynamoDBテーブルをIaC管理下に置くか置かないかという点を議論して、置かなくて良いとなった場合はガンガン利用すれば良いと思います。 できればこの機能、Terraform本家にも欲しい!