プログラミング学習サイト

プログラミングの学習を開始される方を対象としたプログラミング入門サイトです。

Terraformスタイルガイド

参考 : https://developer.hashicorp.com/terraform/language/style

Terraformの設計上の決定は組織のニーズや好みに依存しますが、採用することをお勧めするいくつかの一般的なパターンがあります。 これをスタイルガイドと呼びます スタイルガイドを採用してコーディングすると、テラフォームコードが読みやすく、スケーラブルで、保守可能になります。

この記事では、組織のスタイルガイドを作成する際に、ベストプラクティスといくつかの考慮事項について留意すべきいくつかの考慮事項について説明します。 最初は、フォーマットやリソース組織などのコードスタイルの推奨事項について説明します。 次はメタアーグメント、バージョン化、敏感なデータ管理を介したライフサイクル管理などの操作とワークフローの推奨事項について説明します。

Terraformコードスタイル一覧

一貫したスタイルでTerraformコードを書くことで、読みやすく維持しやすくなります。次のセクションでは、コードスタイルの推奨事項について説明します。

  • コードをコミットする前に、terraform fmtterraform 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_amiaws_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パラメーターには、次の順番での使用を勧めします。

  1. Description
  2. Value
  3. 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.tfstateを含むterraform.tf.* ステートファイル。
    • .terraform.tfstate.lock.infoファイル。
      • Terraformは、terraform applyコマンドを実行すると、このファイルを自動的に作成および削除し、ロックを管理します。
    • .terraform ディレクトリ。Terraformはこのディレクトリ内にプロバイダーと子モジュールをダウンロードします。
    • 機密情報を含む.tfvarsファイル。
  • 常にコミット:
    • すべての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