目的
Terraformを使いこなす!を目標に、学びとTipsを整理します。
環境
$ terraform -version
Terraform v1.9.6
on linux_amd64
Tips
Terraformで変数を扱う
環境変数にTF_VAR_
プレフィックスをつけた変数を登録することで、variable
に値を渡すことができます。
$ printenv |grep TF_VAR
TF_VAR_your_secret=yyyyyy
# 環境変数から`your_secret`に値を渡すことができる。
variable "your_secret" {
type = string
}
terraform_remote_stateとその管理を考える
terraform_remote_state を使うと、他のモジュールの状態ファイルから情報を参照できます。
モジュールが多くなり依存関係の管理が複雑になる場合、ディレクトリ構成を工夫することで煩雑さを解消できそうです。
module3
が他のモジュールの出力値を参照したい場合の例で考えてみます。
remote_state
で各モジュールの出力値を一元管理します。すると、参照する状態ファイルは固定できるため、依存関係が整理しやすくなります。
$ tree
.
├── module1
│ ├── backend.tf
│ └── data.tf
├── module2
│ ├── backend.tf
│ └── data.tf
├── module3
│ ├── backend.tf
│ └── data.tf
└── remote_state
├── backend.tf
├── data.tf
└── outputs.tf
module1
でremote_state
に出力値を登録します。
# module1配下のtfstateで出力値を管理する
variable "bucket" {
type = string
}
data "terraform_remote_state" "module1" {
backend = "gcs"
config = {
bucket = var.bucket
prefix = "module1"
}
}
output "module1_value" {
value = "module1_value"
}
remote_stateフォルダで各モジュールからの出力値を受け取ります。
# module1とmodule2の出力値にアクセス
variable "bucket" {
type = string
}
data "terraform_remote_state" "module1" {
backend = "gcs"
config = {
prefix = "module1"
bucket = var.bucket
}
}
data "terraform_remote_state" "module2" {
backend = "gcs"
config = {
prefix = "module2"
bucket = var.bucket
}
}
remote_stateフォルダ配下のoutputs.tf
で、再度各モジュールから受け取った値を出力します。
# 各モジュールの出力値を一元管理する
output "terraform_remote_state_output_all" {
value = {
# module1の出力値
module_1_output = data.terraform_remote_state.module1.outputs.module1_value
# module1の出力値
module_2_output = data.terraform_remote_state.module2.outputs.module2_value
}
}
出力値を受け取りたいmodule3
はremote_state
で管理されている値を参照します。
variable "bucket" {
type = string
}
# remote_stateで管理されている状態ファイルにアクセス
data "terraform_remote_state" "env" {
backend = "gcs"
config = {
prefix = "remote_state"
bucket = var.bucket
}
}
output "module1_value_used_in_module3" {
value = data.terraform_remote_state.env.outputs.terraform_remote_state_output_all.module_1_output
}
Custom Validation Rules
特定の変数のカスタム検証ルールを指定できます
この機能は、Terraform CLI v0.13.0 で導入されました。
インフラリソースが特定の値の範囲や形式を必要とする場合に、事前検証する上で役立ちます。
値にdev
かprd
が含まれないとエラーを出力するケースを例にとります。
シンプルなコーディング規約のように使えそうです。
variable "environment" {
validation {
condition = contains(["dev", "prod"], var.environment)
error_message = "The environment must be dev or prod."
}
}
$ terraform apply
var.environment
Enter a value: a
╷
│ Error: Invalid value for variable
│
│ on variables.tf line 1:
│ 1: variable "environment" {
│ ├────────────────
│ │ var.environment is "a"
│
│ The environment must be dev or prod.
│
│ This was checked by the validation rule at variables.tf:2,3-13.
TFLint
TFlintとはTerraformのためのLinterで、構文やパラメータがルールに違反していないかをチェックしてくれるツールです。
開発ルールを明確化し適用することで、潜在的な問題の早期発見や、ルールに則った規則正しいコードの品質を担保できるメリットがあります。
インストール
curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
.tflint.hclファイル
# Terraformプロジェクトルートに本ファイルを作成する
config {
}
plugin "google" {
enabled = true
version = "0.27.1"
source = "github.com/terraform-linters/tflint-ruleset-google"
}
# variableブロックやoutputブロックはvariables.tfやoutputs.tfに定義する
rule "terraform_standard_module_structure" {
enabled = true
}
# プロバイダのバージョン記載を必須とする
rule "terraform_required_version" {
enabled = true
}
上記のテストを次の構成のTerraformプロジェクトについて実行してみます。
$ tree
.
└── storage
├── main.tf
├── provider.tf
└── variables.tf
結果
すると2件のワーニングから次のことがわかります。
-
provider "random"
がterraform_required_providers
制約に抵触している -
outputs.tf
fileが存在しないことにより、terraform_standard_module_structure
制約に抵触している
$ tflint
2 issue(s) found:
Warning: Missing version constraint for provider "random" in `required_providers` (terraform_required_providers)
on main.tf line 1:
1: resource "random_id" "default" {
Reference: https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.10.0/docs/rules/terraform_required_providers.md
Warning: Module should include an empty outputs.tf file (terraform_standard_module_structure)
on outputs.tf line 1:
(source code not available)
Reference: https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.10.0/docs/rules/terraform_standard_module_structure.md
修正
outputs.tfファイルの追加とprovides.tfファイルを修正することでワーニングは消えます。
$ tree
.
└── storage
├── main.tf
├── provider.tf
├── outputs.tf # add
└── variables.tf
terraform {
required_version = "1.7.5"
required_providers {
google = {
source = "hashicorp/google"
version = "5.24.0"
}
}
# add
required_providers {
random = {
source = "hashicorp/random"
version = "3.6.3"
}
}
}
Terraform test
Terraform テストを使用すると、作成者はモジュール構成の更新によって重大な変更が発生しないことを検証できます。
このテスト フレームワークは、Terraform v1.6.0 以降で使用できます。
構文
Terraform は、ファイル拡張子:
.tftest.hcl
または.tftest.json
に基づいてテストファイルを検出します.
各テストファイルには次の属性とブロックが含まれます
- 1 つ以上の run ブロック
- variables ブロック (省略可)
- provider ブロック (省略可)
早速やってみる!
$ tree
.
├── storage.tf
└── storage.tftest.hcl
resource "google_storage_bucket" "test_bucket" {
name = "example-bucket-2"
location = "ASIA"
project = var.project
storage_class = "MULTI_REGIONAL"
versioning {
enabled = true
}
uniform_bucket_level_access = true
public_access_prevention = "enforced"
}
run "check_gcs_object_name" {
command = plan
assert {
condition = google_storage_bucket_object.test_bucket.name == "example-bucket"
error_message = "Object name is not example-bucket"
}
}
run "check_bucket_location" {
command = plan
assert {
condition = google_storage_bucket.main.location == "ASIA-NORTHEAST1"
error_message = "Bucket location is not ASIA-NORTHEAST1"
}
}
結果
期待通りのエラーメッセージが発生していますね!
- "Object name is not example-bucket"
- "Bucket location is not ASIA-NORTHEAST1"
$ terraform test
storage.tftest.hcl... in progress
run "check_gcs_object_name"... fail
╷
│ Error: Test assertion failed
│
│ on storage.tftest.hcl line 5, in run "check_gcs_object_name":
│ 5: condition = google_storage_bucket.test_bucket.name == "example-bucket"
│ ├────────────────
│ │ google_storage_bucket.test_bucket.name is "example-bucket-2"
│
│ Object name is not example-bucket
╵
run "check_bucket_location"... fail
╷
│ Error: Test assertion failed
│
│ on storage.tftest.hcl line 14, in run "check_bucket_location":
│ 14: condition = google_storage_bucket.test_bucket.location == "ASIA-NORTHEAST1"
│ ├────────────────
│ │ google_storage_bucket.test_bucket.location is "ASIA"
│
│ Bucket location is not ASIA-NORTHEAST1
╵
storage.tftest.hcl... tearing down
storage.tftest.hcl... fail
Failure! 0 passed, 2 failed.
TFlintとTerraform testの整理
特徴 | TFLint | Terraform Test |
---|---|---|
目的 | コードの静的解析(リソース作成前にコード品質を確認) | モジュールの動作確認やリソース作成後のテスト |
タイミング | 開発中やコードレビュー時の静的解析 | リソース適用後や CI/CD のテストプロセスで使用 |
チェック対象 | - Terraform の構文エラー - ベストプラクティス違反 - プロバイダー固有のエラー |
- モジュールやリソースの期待される出力のテスト - 実際に作成されたリソースの動作確認 |
主な利用シーン | - コードの初期開発 - プルリクエストレビュー - チーム標準の設定適用 |
- 新規モジュールの動作検証 - リソース変更の影響確認 - CI/CD のリリーステスト前確認 |
利用シーンをもう少し深堀して考えてみる
個人的な整理ですが、次のような観点で整理してみると、それぞれが効果を発揮してくれる利用シーンが見えてきそうです。
-
TFLint
-
規模の大きなチーム開発
- コードスタイルを統一し、レビュー負担を減らす
-
チーム内で議論して規約を決定
- 強制力のある規約は、チーム全体で議論し合意を得たものにする
-
要不要を整理
- 規約を段階的に分け、必須事項のみエラーとして扱い、推奨・任意は警告または情報として表示するなど対応を分ける
-
規模の大きなチーム開発
-
Terraform test
-
モジュールの再利用性が高い場合
- 頻繁に変更が加えられる部分を対象とする
- 変更が既存の挙動を壊さないことをテストする
-
リスクの高いリソース変更がある場合(クリティカルパス)
- VPCやセキュリティグループのように、ミスがシステム全体に影響を及ぼすリソースについて重点的にテストする
-
モジュールの再利用性が高い場合
最後に
Terraformの運用についてはまだまだ勉強中です。
今後も実践で得られた学びを発信していけたらと思います。
リファレンスとスペシャルサンクス
執筆にあたり数多くのサイトを参考にさせてもらいました。ありがとうございました。