Code

33 件の記事

ルール

Avoid abbreviations, use full names (e.g., cgcoding_guidelines). However, abbreviations used as proper nouns (e.g., RDS, id) may be used.

(略語を避け、完全な名前を使用する(例: cgcoding_guidelines)。ただし、固有名詞として使用される略語(例: RDS, id)は使用可能)

解説

独自の略語は外部の人員や将来の担当者が意図を理解しにくくなるため極力利用を控えます。

ただし、RDSやidのような固有名詞に近いものは利用してかまいません。

サンプルコード

# 完全な名前を使用。ただしidのように半ば固有名詞として使われているものは可
variable "security_group_id" {
  type        = string
  description = "Security Group ID"
}

# このような略し方はNG
variable "sg_id" {
  type        = string
  description = "Security Group ID"
}

ルール

Do not use embedded attributes as much as possible. (e.g., ingress/egress within security groups)

(埋め込み属性をできるだけ使用しない(例: セキュリティグループ内の ingress/egress))

解説

埋め込み属性は可読性が悪い・ループなどの操作がしづらい・更新時に一時的に削除されることがあるなど、使いづらさが目立つため、極力使わないようにします。

サンプルコード

resource "aws_security_group" "web" {
  name        = "${var.environment}-${var.project}-web-sg"
  description = "Webサーバー用セキュリティグループ"
  vpc_id      = var.vpc_id
}

resource "aws_security_group_rule" "web_https_ingress" {
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.web.id
}

resource "aws_security_group_rule" "web_egress" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.web.id
}

参考リンク

ルール

Do not use variable blocks unless strictly necessary. They may be used only when there are actual parameter differences between environments (e.g., development vs. production).

(変数ブロックは厳密に必要な場合のみ使用する。環境間で実際にパラメータ差異がある場合(例:開発環境と本番環境)にのみ使用可能)

解説

生成AIはvariable blocksを必要もなく使いたがる傾向があるため、濫用を禁止しています。

ただし環境ごとに異なる値(CPUサイズ、インスタンス数など)を表現するためには必要ですから、全面禁止にはしていません。

サンプルコード

# 開発環境と本番環境でCPUサイズが異なる場合のみ変数化
variable "cpu_size" {
  type        = number
  description = "ECS task CPU size"
}

resource "aws_ecs_task_definition" "main" {
  family = "api"
  cpu    = var.cpu_size  # 環境により 512 または 2048
  memory = 1024

  # その他の固定値は直接記述
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
}

ルール

Define calls to other modules in main.tf.

(他モジュールの呼び出しはmain.tfに定義する)

解説

AWSの推奨に従っています。

モジュール呼び出しは必ずmain.tfにある、とわかっていれば、モジュールの呼び出し部分を迷わず見つけることが可能です。

サンプルコード

# main.tf
module "networking" {
  source = "./modules/networking"
  vpc_cidr = var.vpc_cidr
}

module "database" {
  source = "./modules/database"
  vpc_id = module.networking.vpc_id
  db_subnet_ids = module.networking.private_subnet_ids
}

module "application" {
  source = "./modules/application"
  vpc_id = module.networking.vpc_id
  db_endpoint = module.database.endpoint
}

参考リンク

ルール

Include units in numerical variable names (e.g., size_gb, ram_size_gb).

(数値変数名には単位を含める)

解説

単位を変数名に含めることで、単位の取り違えによるバグを防止します。

AWS推奨の設定方法です。

サンプルコード

variable "bandwidth_limit_mb" {
  type        = number
  description = "Bandwidth limit in MB per second"
}

参考リンク

ルール

Name boolean variables affirmatively (e.g., enable_external_access).

(真偽値変数は肯定形で命名する)

解説

コードの可読性を向上させるため、変数は肯定形で記載します。

否定形を使用した二重否定(e.g., disable_logging = false) は読みづらいため避けます。

サンプルコード

variable "enable_external_access" {
  type        = bool
  description = "Enable external access to the resource"
}

参考リンク

ルール

Do not reference variable blocks as values in output blocks.

(出力ブロックで変数ブロックを参照しない)

解説

AWSの推奨に従っています。

出力ブロックに変数をそのまま格納するのは無駄ですから普通しないと思いますが、念のため残しています。

サンプルコード(悪い例)

variable "name" {
  description = "Resource name"
  type = string
}

output "name" {
  description = "Name of Resource"
  value       = var.name
}

参考リンク

ルール

Implementation should consider the AWS Well-Architected Framework, though strict compliance is not required.

(実装はAWS Well-Architected Frameworkを考慮する。ただし厳格な準拠は要求しない)

解説

AWS Well-Architected Frameworkは大変優れた設計原則であり、何をおいてもこの考え方が設計の基本となります。 ただしその方針を具体化したルールの中には準拠が難しいものも含まれるため、参考に止めればよいとしています。

ルール

Fixed values may be coded directly in resource definitions. (e.g., t4g.micro)

(固定値はリソース定義に直接記述してよい(例:t4g.micro))

解説

生成AIはむやみやたらとvariablesを使いたがる傾向があります。そこで明示的にそれを抑制しています。

サンプルコード

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t4g.micro"  # 固定値を直接記述

  tags = {
    Name = "web-server"
  }
}

ルール

Common tags such as Environment and Service must be set only in aws_default_tags. Do not define them in any individual resource block.

EnvironmentServiceなどの共通タグはaws_default_tagsのみで設定する。個別のリソースブロックで定義しない)

解説

すべてのAWSリソースに共通して付与すべきタグはaws_default_tagsで一元管理します。個々のリソースブロックで設定してはならないのですが、AIはやりがちのため、明示的に禁止しています。

サンプルコード

# providers.tf
provider "aws" {
  region = "ap-northeast-1"

  default_tags {
    tags = {
      Environment = var.environment
      Service     = var.service
    }
  }
}

# main.tf - 個別リソースでは共通タグを定義しない
resource "aws_s3_bucket" "main" {
  bucket = "${var.environment}-${var.service}-data"
  # Environment、Serviceタグは自動的に付与される
}

ルール

Do not use variable blocks for values that remain the same across environments.

(環境間で同一の値には変数ブロックを使用しない)

解説

生成AIはvariable blocksを必要もなく使いたがる傾向があるため、濫用を禁止しています。

サンプルコード

# 全環境で同じ値は直接記述
resource "aws_ecs_task_definition" "main" {
  family                   = "api"
  network_mode             = "awsvpc"  # 全環境で同じ
  requires_compatibilities = ["FARGATE"]  # 全環境で同じ

  # 環境ごとに異なる値のみ変数化
  cpu    = var.cpu_size
  memory = var.memory_size
}

ルール

Use variable blocks when parameter values differ between environments (e.g., production → CPU 4096, development → CPU 1024).

(環境間でパラメータ値が異なる場合にvariableブロックを使用する(例: 本番環境 → CPU 4096、開発環境 → CPU 1024))

解説

生成AIはvariable blocksを必要もなく使いたがる傾向があるため、濫用を禁止しています。しかし全く使わないとそれはそれでおかしなコードができあがるため、利用して良いケースを明示しています。

サンプルコード

# variables.tf
variable "cpu_units" {
  type        = number
  description = "CPU units for ECS task"
}

ルール

Do not use plural forms in resource names.

(リソース名に複数形を使用しない)

解説

特にこだわる所ではないため、素直にAWSの推奨に従っています。

なぜこのようになっているかは存じませんが、リソースによって複数形だったり単数形だったりするよりは、片方だけに統一されているほうが好ましいと考えます。

サンプルコード

# 良い例: 単数形を使用
resource "aws_instance" "web_server" {
  count         = 3
  instance_type = "t3.micro"
  ami           = var.ami_id
}

参考リンク

ルール

Do not include the resource type name in the resource name (e.g., avoid aws_instance.ec2_instance).

(リソース名にリソースタイプ名を含めない)

解説

二つの理由により、リソース名にリソースタイプ名を含めるべきではありません。

  1. リソースタイプを見ればわかることは書くべきでない
  2. 命名が雑になりやすい

悪い例としてはaws_instance.ec2_instance のような命名が該当します。

AWS公式のベストプラクティスでもこの命名規則が推奨されています。

サンプルコード

# 良い例: リソースタイプを重複させない
resource "aws_instance" "web_server" {
  instance_type = "t3.micro"
  ami           = var.ami_id
}

resource "aws_security_group" "api" {
  name   = "${var.environment}-${var.project}-api"
  vpc_id = aws_vpc.main.id
}

参考リンク

ルール

The order of resource block attributes should be as follows: 1. If present, The count or for_each meta-argument, 2. Resource-specific non-block parameters, 3. Resource-specific block parameters, 4. If required, a lifecycle block, 5. If required, the depends_on parameter. No other constraints apply.

(リソースブロック属性の順序は以下の通りとする:1. 存在する場合、countまたはfor_eachメタ引数、2. リソース固有の非ブロックパラメータ、3. リソース固有のブロックパラメータ、4. 必要な場合、lifecycleブロック、5. 必要な場合、depends_onパラメータ。その他の制約は適用しない)

解説

Terraform公式スタイルガイドに従っています。

決めの問題ですが、ある程度フォーマットされているだけでも読みやすさが上がります。

サンプルコード

resource "aws_instance" "web" {
  # 1. メタ引数
  count = var.instance_count

  # 2. 非ブロックパラメータ
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t4g.micro"
  subnet_id     = aws_subnet.main.id

  # 3. ブロックパラメータ
  tags = {
    Name = "web-server-${count.index}"
  }

  # 4. lifecycle
  lifecycle {
    create_before_destroy = true
  }

  # 5. depends_on
  depends_on = [aws_security_group.web]
}

参考リンク

ルール

Multiple resources within a module should be named according to their role (e.g., primary, read_replica).

(モジュール内の複数リソースは役割に応じて命名する)

解説

モジュール内に同じタイプのリソースが複数存在する場合は、primaryread_replica のようにそのリソースの役割を示す名称をつけます。

なお単一リソースの場合は mainとします。

サンプルコード

# modules/database/main.tf
# データベースの役割に応じて命名
resource "aws_db_instance" "primary" {
  identifier     = "${var.environment}-${var.project}-primary"
  engine         = "postgres"
  instance_class = "db.t3.medium"
}

resource "aws_db_instance" "read_replica" {
  identifier          = "${var.environment}-${var.project}-replica"
  replicate_source_db = aws_db_instance.primary.id
  instance_class      = "db.t3.small"
}

参考リンク

ルール

A single resource within a module should be named main (e.g., resource "aws_vpc" "main").

(モジュール内の単一リソースは main と命名する)

解説

モジュール内に特定のリソースタイプが一つしかない場合はリソース名を main とします。

採用の理由は「それがAWS推奨の手法だから」ですが、モジュール内の主要なリソース、またはそのリソースのための補助的な宣言であることが理解しやすいため従っておけばよいと考えます。

サンプルコード

# modules/vpc/main.tf
# VPCリソースが一つだけの場合は main と命名
resource "aws_vpc" "main" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = true
  enable_dns_support   = true
}

参考リンク

ルール

Use MiB/GiB for storage units, and MB/GB (decimal units) for others.

(ストレージ単位にはMiB/GiBを使用し、その他にはMB/GBを使用する)

解説

ストレージ容量(ディスク/ボリューム/スナップショット等)はバイナリ単位を使用します。それ以外のメトリクス(ネットワーク転送量、帯域など)は10進数の単位を使用します。単位の微妙な違いによる勘違いを防止します。

サンプルコード

# ストレージには2進数(GiB/MiB)を使用
variable "ebs_volume_size_gib" {
  type        = number
  description = "EBS volume size in GiB"
}

# それ以外は10進単位を使用
variable "cw_alarm_network_out_threshold_mb" {
  type        = number
  description = "CloudWatch Alarm threshold for NetworkOut in MB"
}

参考リンク

ルール

Do not include provider or terraform in submodules.

(サブモジュールにproviderterraformブロックを含めない)

解説

プロバイダー設定はルートモジュールで一括管理します。そのためサブモジュールでproviderやterraformブロックを利用してはいけません。

Terraformの公式ベストプラクティスでも推奨されている設計原則です。

サンプルコード

# production/providers.tf(ルートモジュールで定義)
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

ルール

Do not add comments describing what is already clear from the code.

(コードから明らかな内容を説明するコメントを追加しない)

解説

コードを見れば分かる内容を繰り返すコメントは害のほうが大きいため禁止しています。

サンプルコード

# 良い例
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
  bucket = aws_s3_bucket.logs.id

  rule {
    id     = "delete_old_logs"
    status = "Enabled"

    # 監査要件により90日間保持
    expiration {
      days = 90
    }
  }
}

# 悪い例(参考)
# resource "aws_s3_bucket" "logs" {  # <- S3バケットを作成
#   bucket = "${var.environment}-${var.project}-logs"  # <- バケット名を設定
# }

ルール

Define variable blocks in variables.tf.

(variableブロックはvariables.tfに定義する)

解説

variableブロックの置き場所を定めておくことで可読性を向上させます。

サンプルコード

# variables.tf
variable "environment" {
  type        = string
  description = "Environment name (e.g., production, staging)"
}

variable "vpc_cidr" {
  type        = string
  description = "CIDR block for VPC"
}

variable "enable_nat_gateway" {
  type        = bool
  description = "Whether to create NAT Gateway for private subnets"
}

参考リンク

ルール

The order of variable block attributes should be as follows: 1. Type, 2. Description, 3. Default (optional), 4. Sensitive (optional), 5. Validation blocks

(variableブロックの属性順序は次の通り: 1. type、2. description、3. default(オプション)、4. sensitive(オプション)、5. validationブロック)

解説

HashiCorp公式の推奨に従っています。

統一された記述順序は可読性を向上させます。

サンプルコード

variable "instance_type" {
  type        = string
  description = "EC2インスタンスタイプ"
  default     = "t3.micro"

  validation {
    condition     = can(regex("^t3\\.", var.instance_type))
    error_message = "インスタンスタイプはt3系を指定してください"
  }
}

variable "db_password" {
  type        = string
  description = "データベースパスワード"
  sensitive   = true
}

参考リンク

ルール

Include type definitions in variable blocks.

(variableブロックに型定義を含める)

解説

AWSの推奨に従っています。

変数に型を明示することで誤った値の代入を防ぐ他、変数の性格を推測しやすくなります。

サンプルコード

# variables.tf
variable "environment" {
  type        = string
  description = "Environment name"
}

variable "instance_count" {
  type        = number
  description = "Number of instances to create"
}

参考リンク

ルール

Do not set default values in variable blocks.

(variableブロックにdefault値を設定しない)

解説

デフォルト値があると設定漏れに気づかずに意図しない値でインフラが構築されることがあります。

ほんの少し楽をしようとして事故を起こすのは割に合わないため、全面的に禁止しています。

サンプルコード

# variables.tf (良い例)
variable "instance_type" {
  type        = string
  description = "EC2 instance type"
  # defaultは設定しない
}

ルール

Include descriptions in variable and output blocks.

(variableとoutputブロックにdescriptionを含めること)

解説

変数と出力値に説明を付けることで、各パラメータの目的を誤解しづらくなります。人間も助かりますが、なによりAIの助けになります。

サンプルコード

# variables.tf
variable "instance_type" {
  type        = string
  description = "EC2 instance type for application servers"
}

参考リンク

ルール

Use snake_case for resource names in terraform code (e.g., web_server).

(Terraformコードのリソース名はスネークケースを使用する)

解説

Terraformのリソース名には通常スネークケースが用いられます。

AWS公式のベストプラクティスでもスネークケースの使用が推奨されています。

サンプルコード

# リソース名にスネークケースを使用
resource "aws_instance" "web_server" {
  instance_type = "t3.micro"
  ami           = var.ami_id
}

参考リンク

ルール

Place terraform.tfvars.json in the root module (the module where terraform apply is executed) instead of submodules.

terraform.tfvars.jsonはルートモジュールに配置し、サブモジュールには配置しない)

解説

混乱防止のため、terraform.tfvars.jsonは必ずルートモジュールに置くルールです。

サンプルコード

# ディレクトリ構造
#
# terraform/
#   production/
#     main.tf
#     variables.tf
#     terraform.tfvars.json    # ← ここに配置
#   modules/
#     networking/
#       main.tf
#       variables.tf
#       # terraform.tfvars.json は配置しない

ルール

In the root module, ensure terraform.tfvars.json stores: 1) values that other modules might also reuse, 2) sensitive information that must never be hard-coded (e.g., private keys).

(ルートモジュールのterraform.tfvars.jsonには、1) 他のモジュールでも再利用する値、2) ハードコードしてはならない機密情報を格納する)

解説

平文の機密情報はなるべくTerraformを通さずに利用したいところですが、利用せざるを得ないケースもあります。その場合はコード内にハードコーディングせず、terraform.tfvars.jsonに記述してsopsで暗号化します。

なおこのアプローチをとる場合、そのリポジトリでCursorやClaudeCodeなどクラウドタイプのAIを利用してはいけません。

サンプルコード

# terraform.tfvars.json(良い例)
{
  "environment": "production",
  "project": "myapp",
  "region": "ap-northeast-1",
  "db_password": "encrypted_password_here",
  "ssl_certificate_private_key": "encrypted_key_here"
}

# variables.tf
variable "environment" {
  type        = string
  description = "Environment name"
}

variable "project" {
  type        = string
  description = "Project name"
}

variable "db_password" {
  type        = string
  sensitive   = true
  description = "Database password"
}

# main.tf
module "database" {
  source = "../modules/database"

  environment = var.environment
  project     = var.project
  password    = var.db_password
}

module "networking" {
  source = "../modules/networking"

  environment = var.environment
  project     = var.project
}

ルール

Always set Name tags for resources that use Name tags as identifiers.

(Nameタグを識別子として使用するリソースには必ずNameタグを設定する)

解説

EC2インスタンスなど、Nameタグが事実上のリソース名として用いられるリソースがいくつかあります。

これらはNameタグが適切につけられていないと管理運用が難しくなるため、必ずセットします。

サンプルコード

resource "aws_instance" "api" {
  tags = {
    Name = "${var.environment}-${var.project}-api"
  }
}

参考リンク

ルール

Minimize the use of locals blocks. If used, define them in locals.tf.

localsブロックの使用を最小限にする。使用する場合はlocals.tfに定義する)

解説

AWSの推奨に従っています。

使用する際はlocals.tfに集約することで、ローカル変数の定義場所を一元化し可読性を維持します。

サンプルコード

# locals.tf
locals {
  # 複数の変数を組み合わせた共通タグ
  common_tags = {
    Environment = var.environment
    Project     = var.project
  }

  # 計算が必要な値
  common_prefix = "${var.environment}-${var.project}-"
}

参考リンク

ルール

Minimize the use of data blocks. If used, place them immediately above the resource block that references them.

dataブロックの使用を最小限にする。使用する場合は、参照するリソースブロックの直前に配置する)

解説

dataブロックは便利なのですが、AWS APIへの問い合わせが行われるため、多用するとTerraformの実行が遅くなります。 そのため使わなくてもよい場面ではDataブロックではなくvariablesを利用します。

ただし禁止ではないため、使用することもあります。その場合はなるべく読みやすいよう参照するリソースの直前に配置します。

サンプルコード

# 使用するリソースの直前にdataブロックを配置
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]  # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
}

参考リンク

ルール

Name AWS resource names (Name tags) in the format [environment]-[project]([-additional_info_if_any]) (e.g., production-practitioner-api). In HCL, use variables like ${var.environment}-${var.project}-api. ‘api’ is just an example, so be sure to replace it appropriately.

(AWSリソース名(Nameタグ)は[環境名]-[プロジェクト名][-追加情報]の形式で命名する)

解説

これはプロジェクト独自の命名規則です。

環境名を先頭に配置することですぐにどの環境のリソースかわかるようにし、環境の取り違えを防止します。

またどのリソース名でも同一の命名規則を徹底することで、スクリプトなどを使った一括処理を行いやすくします。

サンプルコード

resource "aws_instance" "api" {
  ami           = "ami-12345678"
  instance_type = "t3.micro"

  tags = {
    Name = "${var.environment}-${var.project}-api"
  }
}

参考リンク

ルール

*Keep comments in .tf files to a minimum. Avoid adding header blocks or documentation-level specifications in comments.

(*.tfファイルのコメントを最小限に保つ。ヘッダーブロックの追加禁止。またドキュメントに記載すべき仕様も書いてはならない)

解説

生成AIはしばしば大量のコメントやヘッダーブロックをコード内に埋め込もうとします。しかし過剰なコメントは可読性を落とし、コメントの保守の手間を増やし、トークン効率を悪化させます。そのため禁止しています。

サンプルコード

# 良い例: 最小限のコメント、自己文書化されたコード
variable "instance_type" {
  type        = string
  description = "EC2インスタンスタイプ。本番環境ではt3.mediumを推奨"
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  # セキュリティ要件により、IMDSv2を強制
  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required"
  }
}