- Terraformコードスタイル一覧
- Terraformのコードフォーマット
- コード検証
- ファイル名
- コメント
- リソース命名規則
- リソース順序
- 変数(パラメーター)
- Outputs
- Local values
- プロバイダーエイリアシング
- count, for_each
- .gitignore
参考 : https://developer.hashicorp.com/terraform/language/style
Terraformの設計上の決定は組織のニーズや好みに依存しますが、採用することをお勧めするいくつかの一般的なパターンがあります。 これをスタイルガイドと呼びます スタイルガイドを採用してコーディングすると、テラフォームコードが読みやすく、スケーラブルで、保守可能になります。
この記事では、組織のスタイルガイドを作成する際に、ベストプラクティスといくつかの考慮事項について留意すべきいくつかの考慮事項について説明します。 最初は、フォーマットやリソース組織などのコードスタイルの推奨事項について説明します。 次はメタアーグメント、バージョン化、敏感なデータ管理を介したライフサイクル管理などの操作とワークフローの推奨事項について説明します。
Terraformコードスタイル一覧
一貫したスタイルでTerraformコードを書くことで、読みやすく維持しやすくなります。次のセクションでは、コードスタイルの推奨事項について説明します。
- コードをコミットする前に、
terraform fmt
とterraform validate
検証します。 - TFLintなどの
linter
を使用して、組織自身のコーディングベストプラクティスを実施します。 - 単一およびマルチラインのコメントに
#
を使用できます。 - リソース名に名詞を使用し、名前にリソースタイプを含めないでください。
- 別のリソースから参照する際には、
.
繋がりでリソースタイプを明記することができるためです。
- 別のリソースから参照する際には、
- アンダースコアを使用して、名前を分離します。 リソースのタイプと名前をリソース定義で二重引用符で巻き付けます。
- 変数の過剰使用は避けてください。
- 必ずデフォルトのプロバイダー構成を含めます。
- countを使用して、
for_each
構文の過剰使用は避けてください。
以下はこれらのスタイルガイドを守ったS3とIAMユーザーを作成するTerraformコードです。
# デフォルトのAWSプロバイダー設定 provider "aws" { region = "us-west-2" } # S3バケットの作成 resource "aws_s3_bucket" "my_bucket" { bucket = "my-unique-bucket-name" acl = "private" tags = { Name = "MyBucket" Environment = "Dev" } } # IAMユーザーの作成 (countを使用して複数のユーザーを作成) resource "aws_iam_user" "app_user" { count = 2 # 2つのユーザーを作成 name = "app_user_${count.index + 1}" path = "/" force_destroy = false tags = { Name = "AppUser${count.index + 1}" Environment = "Dev" } } # IAMユーザー用のアクセスキーを作成 (for_eachを使用して各ユーザーにアクセスキーを作成) resource "aws_iam_access_key" "app_user_key" { for_each = aws_iam_user.app_user user = each.value.name } # S3バケットへのアクセスを許可するポリシーの作成 resource "aws_iam_policy" "s3_access_policy" { name = "S3AccessPolicy" path = "/" policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = ["s3:ListBucket"] Effect = "Allow" Resource = [aws_s3_bucket.my_bucket.arn] }, { Action = ["s3:GetObject", "s3:PutObject"] Effect = "Allow" Resource = ["${aws_s3_bucket.my_bucket.arn}/*"] } ] }) } # IAMポリシーをIAMユーザーにアタッチ (for_eachを使用して各ユーザーにポリシーをアタッチ) resource "aws_iam_user_policy_attachment" "app_user_policy_attachment" { for_each = aws_iam_user.app_user user = each.value.name policy_arn = aws_iam_policy.s3_access_policy.arn }
Terraformのコードフォーマット
各ネストレベルには、2つのスペースのインデントを推奨します。
引数の値はタブを使用して高さを揃えましょう。
- NG
ami = "abc123" instance_type = "t2.micro"
- GOOD
ami = "abc123" instance_type = "t2.micro"
引数とブロックの位置
- 引数とブロックが同一のブロックボディ内部に一緒に書き込む際には、引数をまとめて上部に配置し、その下にネストされたブロックを配置します。
- また、引数群とブロックの間には1つの空白行を使用して、引数をブロックから分離します。
空の行を使用して、ブロック内の引数の論理グループを分離しましょう。
NG
resource "aws_instance" "example" { ami = "ami-12345678" instance_type = "t2.micro" tags = { Name = "example-instance" } network_interface { device_index = 0 network_interface_id = "eni-12345678" } }
- GOOD
resource "aws_instance" "example" { ami = "ami-12345678" instance_type = "t2.micro" tags = { Name = "example-instance" } network_interface { device_index = 0 network_interface_id = "eni-12345678" } }
メタ引数の
- 引数と「メタ引数(countやfor_each、depends_on)」の両方を含むブロックについては、最初にメタ引数を配置し、1つの空白行で他の引数から分離します。
resource "aws_instance" "example" { # meta-argument first count = 2 ami = "abc123" instance_type = "t2.micro" network_interface { # ... } # meta-argument block last lifecycle { create_before_destroy = true } }
- ブロックタイプがセマンティクスによって定義されてファミリを形成する場合を除き、同じタイプの複数のブロックを異なるタイプの他のブロックとグループ化しないでください。(例:root_block_device、ebs_block_device、ephemeral_block_device on aws_instanceのawsブロックデバイスを説明するブロックタイプのファミリを形成するため、一緒にグループ化して混合できます。)
erraform fmt
コマンドは、Terraform構成を上記の推奨事項フォーマット編集します。
デフォルトでは、terraform fmt
コマンドは、それを実行するディレクトリ内のTerraformコードのみを変更しますが、すべてのサブディレクトリのコードを変更するための-recursive
フラグを含めることができます。
Terraformコードを編集したあと新バージョンにコミットする前に、terraform fmt
を実行することをお勧めします。
git pre-commit hooksなどのメカニズムを使用して、コードをコミットするたびにこのコマンドを自動的に実行できます。
VSCodeを使用する場合は、terraform vscode拡張機能を使用して、構文の強調表示と検証、自動コードの書式設定などの機能を有効にします。 開発環境またはテキストエディターが言語サーバープロトコルをサポートしている場合、Terraform Language Serverを使用してVSCode拡張機能のほとんど全てを使用できます。
コード検証
terraform validate
は、ソースコードが構文的に有効であり内部的に一貫していることを検証するコマンドチェックです。
ただしこの検証コマンドは、引数値が特定のプロバイダーに対して有効かどうかまでは確認しません。
terraform validate
コマンドは、自動的かつ頻繁に実行できます。
テキストエディターを設定して、このコマンドをポストセーブチェックとして実行したり、gitリポジトリの事前コミットフックとして定義したり、CI/CDパイプラインのステップとして実行したりできます。
詳細については、Terraformの検証ドキュメントを参照してください。
ファイル名
Terraformでは次のファイルの命名規則をお勧めします。
- バックエンド構成を含む
backend.tf
ファイル。(構成内の複数のTerraFormブロックを定義して、バックエンド構成をTerraformおよびプロバイダーバージョン構成から分離できます。) - すべてのリソースとデータソースブロックを含む
main.tf
ファイル。 - すべての出力ブロックをアルファベット順に含む
outputs.tf
ファイル。 - すべてのプロバイダーブロックと構成を含む
providers.tf
ファイル。 - アルファベット順にすべての変数ブロックを含む変数。
variables.tf
ファイル。 - ローカル値を含む
locals.tf
ファイル。 - 構成のオーバーライド定義を含む
override.tf
ファイル。これらのオーバーライドにより、コードの推論するのが難しくなるため過度な使用はお控えください。
コードベースが成長するにつれて、Terraformコードの管理をこれらのファイルだけに制限することは、維持が困難になる可能性があります。 コードがサイズのためにナビゲートしにくい場合は、論理グループごとに別のファイルでリソースとデータソースを整理することをお勧めします。
たとえば、Webアプリケーションにネットワーキング、ストレージ、およびコンピューティングリソースが必要な場合、次のファイルを作成できます。
- VPC、サブネット、ロードバランサー、およびその他のすべてのネットワーキングリソースを含む
network.tf
ファイル。 - オブジェクトストレージと関連する権限構成を含む
storage.tf
ファイル。 - コンピューティングインスタンスを含む
compute.tf
ファイル。
コメント
基本はコメントではなく、簡単に理解できるようにコードを書いてください。必要な場合にのみ、コメントを使用して、他のメンテナーの複雑さを軽減しましょう。
単一行コメントと複数業コメントの両方に#
を使用します。
//
および /* */
コメント構文は慣用的ではありませんが、Terraformは、以前のバージョンのHCLと後方互換性を維持することをサポートしています。
# Each tunnel is responsible for encrypting and decrypting traffic exiting # and leaving its associated gateway.resource "google_compute_vpn_tunnel" "tunnel1" { ## ...
リソース命名規則
Terraform構成内のすべてのリソースには、一意の名前が必要です。 一貫性と読みやすさのために、説明的な名詞を使用し、アンダースコアのある単語を分離します。 ただし、リソースアドレスはすでに含まれているため、リソース識別子にリソースタイプを含めないでください。 リソースの種類と名前を二重引用符で包みます。
- NG:
resource aws_instance webAPI-aws-instance {...}
- Good:
resource "aws_instance" "web_api" {...}
リソース順序
コード内のリソースとデータソースの順序は、Terraformがそれらを構築する方法に影響しないため、読みやすくするためにリソースを整理してください。 Terraformは、リソース間の依存関係に基づいて作成順序を決定します。
リソースの順番のベストプラクティスは、コードのサイズと複雑さに大きく依存しますが、データソースを参照するリソースと一緒にデータソースを定義することをお勧めします。 読みやすさのために、Terraformコードは「それ自体に基づいて構築する」必要があります。それを参照するリソースの前にデータソースを定義する必要があります。
次の例では、2つのデータソース、aws_ami
とaws_availability_zones
に依存するaws_instance
を定義しています。
読みやすさと連続性のために、aws_instance
リソースの前にデータソースを定義します。
data "aws_ami" "web" { ##... } data "aws_availability_zones" "available" { ##... } resource "aws_instance" "web" { ami = data.aws_ami.web.id availability_zone = data.aws_availability_zones.available.names[0] ##... }
変数(パラメーター)
変数はモジュールをより柔軟にしますが、変数を過剰に使用すると、コードが理解しにくい場合があります。 リソース設定の変数を公開するかどうかを決定するときは、そのパラメーターが展開先で変更されるかどうかを考慮するべきです。
- すべての変数にはタイプと説明を定義します。
- 変数がオプションの場合、妥当なデフォルトを定義します。
- パスワードやプライベートキーなどの機密変数の場合、
sensitive
をTRUEに設定しましょう。 Terraformは、この値をプレーンテキストに保存していることを念頭においておいてください。 ただし、terraform plan
またはterraform apply
を実行してもsensitive
を設定した値は表示されなくなります。 変数ブロックの記述の順番は次のようにすることをお勧めします。
Type
- Description
- Default (optional)
- Sensitive (optional)
- Validation blocks
Outputs
Outputを適切に設定することによって、ユーザーはコマンドラインでインフラストラクチャに関するデータを表示することができ、他のテラフォーム構成で簡単に参照できます。
Outputパラメーターには、次の順番での使用を勧めします。
- Description
- Value
- Sensitive (optional)
すべての変数とOutputには一意の名前が必要です。 Output名には一貫性と読みやすさのために、説明的な名詞を使用し、アンダースコアで別々の単語を使用することをお勧めします。
variable "db_disk_size" { type = number description = "Disk size for the API database" default = 100 } variable "db_password" { type = string description = "Database password" sensitive = true } output "web_public_ip" { description = "Public IP of the web instance" value = aws_instance.web.public_ip }
Local values
Terraformではローカルの値により、式または値を複数回参照できます。 ローカル値も過剰使用がコードの複雑化につながるため、ローカルの価値を控えめに使用します。
たとえば、ローカル値を使用して、リージョンと環境(devやtestなど)のサフィックスを作成し、複数のリソースに追加できます。
locals { name_suffix = "${var.region}-${var.environment}" } resource "aws_instance" "web" { ami = data.aws_ami.ubuntu.id instance_type = "t3.micro" tags = { Name = "web-${local.name_suffix}" } }
2つの場所のいずれかでローカル値を定義することをお勧めします。
- 複数のファイルでローカル値を参照する場合は、
locals.tf
という名前のファイルで定義します。 - ローカル値が一つのファイルに固有の場合は、そのファイルの上部に定義します。
プロバイダーエイリアシング
プロバイダーエイリアシングでは、同じTerraformプロバイダーの複数のプロバイダーブロックを定義できます。 例えば、awsの複数リージョンでサービスをデプロイしたい時、あるいはawsリソースが特定のリージョンにしか対応していない時はプロバイダーエイリアシングが活用できます。
providers.tf
provider "aws" { region = "us-east-1" } provider "aws" { alias = "west" region = "us-west-2" }
上記のproviders.tf
ファイルでプロバイダーを複数選択しています。
main.tf
resource "aws_instance" "example" { provider = aws.west # ... } module "aws_vpc" { source = "./aws_vpc" providers = { aws = aws.west } }
どのプロバイダーを使用するかは各リソースのprovider
引数で指定できます。
count, for_each
for_eachおよびカウントメタアーグメントを使用すると、実行時の条件に応じて、単一のリソースブロックから複数のリソースを作成できます。これらのメタアーグメントを使用して、コードを柔軟にし、重複するリソースブロックを削減できます。リソースがほぼ同一の場合は、カウントを使用してください。いくつかの引数には、整数から派生できない明確な値が必要な場合は、for_eachを使用してください。
for_each meta-argumentはマップまたはセット値を受け入れ、Terraformは提供する値の各要素のそのリソースのインスタンスを作成します。次の例では、terraformは、web_instances変数で定義されている各文字列のaws_instanceを作成します: "ui"、 "api"、 "db"、 "metrics"。この例では、各インスタンスに各インスタンスに一意の名前を付けてください。web_private_ips出力はexpressionを使用してインスタンス名とそのプライベートIPアドレスのマップを作成し、web_ui_public_ip出力はキー「UI」を直接使用してインスタンスをアドレス指定します。
variable "web_instances" { type = list(string) description = "A list of instances for the web application" default = [ "ui", "api", "db", "metrics" ]}resource "aws_instance" "web" { for_each = toset(var.web_instances) ami = data.aws_ami.webapp.id instance_type = "t3.micro" tags = { Name = "web_${each.key}" }}output "web_private_ips" { description = "Private IPs of the web instances" value = { for k, v in aws_instance.web : k => v.private_ip }}output "web_ui_public_ip" { description = "Public IP of the web UI instance" value = aws_instance.web["ui"].public_ip}
上記の例では、次の出力が作成されます。
web_private_ips = { "api" = "172.31.25.29" "db" = "172.31.18.33" "metrics" = "172.31.26.112" "ui" = "172.31.20.142"}web_ui_public_ip = "18.216.208.182"
その他の例については、for_eachメタアーグメントドキュメントを参照してください。
Count Meta-Argumentを使用すると、単一のリソースブロックからリソースの複数のインスタンスを作成できます。例については、Count Meta-Argumentドキュメントを参照してください。
条件付きリソースを作成する一般的な慣行は、条件付き式でカウントメタアーミングを使用することです。次の例では、var.enable_metricsが真である場合にのみ、terraformがAWS_INSTANCEを作成します。
variable "enable_metrics" { description = "True if the metrics server should be deployed" type = bool default = true}resource "aws_instance" "web" { count = var.enable_metrics ? 1 : 0 ami = data.aws_ami.webapp.id instance_type = "t3.micro" ##...}
メタアーグメントはコードを簡素化しますが、複雑さを追加するため、適度に使用してください。メタアーチュメントの効果がすぐに明らかでない場合は、明確化のためにコメントを使用してください。
これらのメタアーグメントの詳細については、for_eachおよびカウントドキュメントを参照してください。
.gitignore
リポジトリの.gitignore
ファイルを定義して、stateファイルなどのプロジェクトバージョンコントロールに公開すべきではないファイルを除外しましょう。
- 以下のコードは絶対にコミットしないでください。
- 常にコミット:
- すべてのTerraformコードファイル
.terraform.lock.hcl
: 依存関係ロックファイル- コード、入力変数、および出力を説明するreadme.md
以下は理想的な.gitignore
ファイルの例です。
# Local .terraform directories **/.terraform/* # .tfstate files *.tfstate *.tfstate.* # Crash log files crash.log crash.*.log # Exclude all .tfvars files, which are likely to contain sensitive data, such as # password, private keys, and other secrets. These should not be part of version # control as they are data points which are potentially sensitive and subject # to change depending on the environment. *.tfvars *.tfvars.json # Ignore override files as they are usually used to override resources locally and so # are not checked in override.tf override.tf.json *_override.tf *_override.tf.json # Ignore transient lock info files created by terraform apply .terraform.tfstate.lock.info # Include override files you do wish to add to version control using negated pattern # !example_override.tf # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan # example: *tfplan* # Ignore CLI configuration files .terraformrc terraform.rc
参考 : https://github.com/github/gitignore/blob/main/Terraform.gitignore
page:https://minegishirei.hatenablog.com/entry/2024/06/11/202050