Terraform / OpenTofu、AWS CloudFormation / CDK、Pulumi、Bicep などのツールに共通して使える原則をまとめています。具体例は主に Terraform で示します。


目次

  1. 基本原則
  2. リポジトリとディレクトリ構成
  3. モジュール設計
  4. ステート管理
  5. 環境分離
  6. バージョン管理と依存関係の固定
  7. シークレット管理
  8. セキュリティとポリシー
  9. CI/CD パイプライン
  10. テスト
  11. ドリフト検知と運用
  12. 命名規則・タグ付け
  13. コード品質
  14. アンチパターン
  15. 導入チェックリスト

1. 基本原則

原則内容
宣言的に書く「どうやって作るか」ではなく「最終的にどうあるべきか」を記述する
冪等性何度適用しても同じ結果になるようにする
すべてをコードに手作業(ClickOps)での変更を禁止し、変更は必ずコード経由で行う
Single Source of TruthGit リポジトリを唯一の正とする
イミュータブル既存リソースを書き換えるより、新しいものを作って置き換える方針を優先する
小さく変更する1 回の変更範囲(blast radius)を小さく保つ
レビュー可能にする変更は必ず Pull Request と plan 結果を伴ってレビューする

2. リポジトリとディレクトリ構成

推奨構成例(Terraform)

infra/
├── modules/                 # 再利用可能なモジュール
│   ├── network/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   ├── versions.tf
│   │   └── README.md
│   └── database/
├── envs/                    # 環境ごとのルートモジュール
│   ├── dev/
│   │   ├── network/
│   │   └── app/
│   ├── stg/
│   └── prod/
├── policies/                # OPA / Sentinel などのポリシー
├── .tflint.hcl
├── .pre-commit-config.yaml
└── README.md

ポイント

  • ルートモジュールを小さく分割する: ネットワーク、データベース、アプリケーションなど、ライフサイクルや変更頻度が異なるものはステートを分ける。
  • ファイルを役割で分ける: main.tf / variables.tf / outputs.tf / versions.tf / providers.tf / locals.tf。
  • モノレポかポリレポか: 小〜中規模ならモノレポで十分。共通モジュールを多チームで使う場合は、モジュール専用リポジトリに分けてバージョンタグで配布する。

3. モジュール設計

原則

  • 単一責任: 1 モジュールは 1 つの論理的なまとまり(例: VPC 一式、RDS クラスタ一式)を担当する。
  • 薄すぎるラッパーを作らない: リソース 1 つを包むだけのモジュールは価値が低い。
  • 入力は最小限、デフォルトは安全側: 暗号化有効、パブリックアクセス無効などをデフォルトにする。
  • 出力を明示する: 他モジュールが必要とする ID や ARN を outputs で公開する。
  • 入力バリデーションを書く。
variable "environment" {
  type        = string
  description = "デプロイ先の環境名"
 
  validation {
    condition     = contains(["dev", "stg", "prod"], var.environment)
    error_message = "environment は dev / stg / prod のいずれかを指定してください。"
  }
}
  • プロバイダ設定はモジュール内に書かない: プロバイダはルートモジュールで設定し、モジュールへは providers 引数で渡す。
  • README と例を付ける: terraform-docs で入出力の表を自動生成する。
  • 公開モジュールは鵜呑みにしない: 使う場合もバージョンを固定し、中身をレビューする。

4. ステート管理

ステートファイルには機密情報が含まれることがあり、壊れると復旧が困難です。最も慎重に扱うべき資産です。

  • リモートバックエンドを使う(S3、GCS、Azure Blob、Terraform Cloud / HCP など)。ローカルステートは個人の検証用途に限る。
  • ロックを有効にする(S3 のネイティブロック、DynamoDB、GCS のロック機能など)。
  • 暗号化とバージョニングを有効にする: 誤った上書き・削除から戻せるようにする。
  • アクセスを最小限にする: ステートの読み取り権限は CI と一部の管理者に限定する。
  • ステートを分割する: 1 つの巨大なステートは plan が遅く、影響範囲も大きい。
  • 手動でのステート操作は記録を残す: state mv / state rm / import は手順書化し、可能であれば moved / import / removed ブロックでコードとして表現する。
moved {
  from = aws_instance.web
  to   = module.web.aws_instance.this
}
  • 他スタックの値は terraform_remote_state よりもデータソースや SSM Parameter Store 経由で参照することを検討する(ステート全体への読み取り権限が不要になる)。

5. 環境分離

  • 環境ごとにステートを分ける。可能ならクラウドアカウント/プロジェクト/サブスクリプション自体も分ける。
  • 同じモジュールを環境間で使い、差分は変数で表現する。環境ごとにコードをコピペしない。
  • 本番に近い検証環境を用意する: stg と prod で構成差が大きいと検証の意味が薄れる。
  • Workspaces の扱い: Terraform CLI の workspace は手軽だが、権限やバックエンドを分けられないため、本番を含む環境分離にはディレクトリ分割(または Terragrunt 等)が推奨されることが多い。
  • プロモーションの流れを決める: dev → stg → prod の順で、同じコミット・同じモジュールバージョンを適用する。

6. バージョン管理と依存関係の固定

  • ツール本体のバージョンを固定する
terraform {
  required_version = "~> 1.9"
 
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
  • ロックファイル(.terraform.lock.hcl)をコミットする。
  • モジュールはタグやコミットハッシュで参照する(?ref=v1.2.0)。ブランチ参照は避ける。
  • ツールバージョンをチームで統一する: tfenv、mise、asdf、.terraform-version などを使う。
  • 依存更新を自動化する: Renovate / Dependabot でプロバイダやモジュールの更新 PR を作り、plan 結果を確認してからマージする。

7. シークレット管理

  • シークレットをコードや .tfvars に直接書かない。
  • シークレットマネージャを使う: AWS Secrets Manager、GCP Secret Manager、Azure Key Vault、HashiCorp Vault など。
  • IaC ではシークレットの「入れ物」だけ作り、値は別経路で投入する方法も有効。
  • sensitive = true を付ける(ただし出力の非表示であり、ステートには平文で残る点に注意)。
  • ephemeral な値(一時的な値)をサポートするツール・バージョンでは活用し、ステートにシークレットを残さないようにする。
  • CI の認証は OIDC で短命なクレデンシャルを使う: 長期のアクセスキーを CI に保存しない。
  • シークレットスキャンを導入する: gitleaks、trufflehog などを pre-commit と CI で実行する。
  • .gitignore を整備する: *.tfstate、*.tfstate.*、.terraform/、*.tfvars(シークレットを含む場合)など。

8. セキュリティとポリシー

最小権限

  • IaC を実行するロールは必要な権限のみに絞る。
  • plan 用(読み取り)と apply 用(書き込み)でロールを分ける。PR 時は plan ロール、main マージ後のみ apply ロールを使う。
  • 本番 apply ロールは、保護されたブランチ/環境からしか引き受けられないようにする。

静的セキュリティスキャン

ツール用途
Checkov多くの IaC 形式に対応した設定ミス検出
Trivy(旧 tfsec 統合)IaC の設定ミス・脆弱性スキャン
KICS多形式対応のセキュリティスキャン
cfn-nag / cfn-guardCloudFormation 向け

Policy as Code

  • OPA / Conftest、Sentinel、AWS CloudFormation Guard などで組織ルールをコード化する。
  • 例: 「S3 バケットはパブリック禁止」「特定リージョン以外へのデプロイ禁止」「必須タグの付与」「高額インスタンスタイプの禁止」。
  • plan 結果(JSON)に対してポリシーを評価し、違反時は CI を失敗させる。

削除保護

  • 重要リソースには prevent_destroy やクラウド側の削除保護を設定する。
resource "aws_db_instance" "main" {
  # ...
  deletion_protection = true
 
  lifecycle {
    prevent_destroy = true
  }
}

9. CI/CD パイプライン

推奨フロー

PR 作成
  ├─ fmt チェック
  ├─ validate
  ├─ lint(tflint など)
  ├─ セキュリティスキャン
  ├─ plan(結果を PR にコメント)
  ├─ ポリシーチェック(plan JSON に対して)
  └─ コスト見積もり(Infracost など)
      ↓ レビュー・承認
main へマージ
  └─ apply(plan を保存したファイルで適用)
      ↓
  本番は手動承認ゲートを挟む

ポイント

  • ローカルからの本番 apply を禁止する: apply は CI からのみ実行する。
  • plan ファイルを保存して、その plan を apply する: レビューした内容と実際の適用内容のズレを防ぐ。
  • 並列実行を防ぐ: 同じステートに対するジョブは直列化する(ステートロックに加え、CI の concurrency 制御も使う)。
  • 変更のあったディレクトリだけ plan/apply する: モノレポではパスフィルタを使う。
  • PR ベースの運用ツール: Atlantis、Terraform Cloud / HCP、Spacelift、env0、Digger など。
  • 破壊的変更を目立たせる: plan で destroy / replace が含まれる場合は警告や追加承認を必須にする。

10. テスト

テストはコストと効果のバランスで段階的に導入します。

レベル内容ツール例
静的解析構文・フォーマット・リントterraform fmt, validate, tflint
セキュリティ/ポリシー設定ミス・規約違反Checkov, Trivy, OPA/Conftest
ユニットテストplan に対するアサーション(実リソース不要)terraform test(command = plan), CDK の assertions
結合テスト実際にリソースを作って検証し、破棄するterraform test(command = apply), Terratest
E2E / スモークテストデプロイ後のアプリ疎通確認任意のテストフレームワーク
# tests/network.tftest.hcl
run "subnet_count" {
  command = plan
 
  assert {
    condition     = length(aws_subnet.private) == 3
    error_message = "プライベートサブネットは 3 つ必要です。"
  }
}
  • 共有モジュールには優先してテストを書く: 影響範囲が広いため。
  • 結合テストは専用のアカウント/プロジェクトで実行し、確実に後片付けする。

11. ドリフト検知と運用

  • 定期的に plan を実行してドリフトを検知する(例: 毎日 terraform plan -detailed-exitcode)。差分があれば通知する。
  • ドリフトの原因を潰す: 手動変更を防ぐため、本番環境のコンソール書き込み権限を絞る(ブレークグラス用アカウントは別途用意)。
  • 既存リソースは import ブロックでコードに取り込む。
  • 外部から変更される属性は ignore_changes で明示する(オートスケーリングの台数など)。ただし多用しない。
  • 変更履歴を追えるようにする: Git の履歴、CI のログ、クラウドの監査ログ(CloudTrail など)を紐付けられるようにする。
  • ロールバック手順を用意する: 基本は「前のコミットを再 apply」。データを伴うリソースは別途バックアップ/リストア手順を用意する。

12. 命名規則・タグ付け

命名規則

  • 一貫したパターンを決める: {システム}-{環境}-{用途}-{連番} など。
  • Terraform のリソースローカル名は snake_case、リソース種別名を繰り返さない(aws_s3_bucket.logs、aws_s3_bucket.logs_bucket は冗長)。
  • 1 つしかない場合は this や main を使う慣習もある。

タグ付け

  • 必須タグを定義する: Environment、Owner、Project、CostCenter、ManagedBy = terraform など。
  • プロバイダの default tags を使う:
provider "aws" {
  region = "ap-northeast-1"
 
  default_tags {
    tags = {
      Environment = var.environment
      Project     = "example"
      ManagedBy   = "terraform"
    }
  }
}
  • ポリシーでタグの付与を強制する: コスト配分や棚卸しに必須。

13. コード品質

  • フォーマッタを強制する: terraform fmt -check を CI で実行する。
  • pre-commit を導入する: pre-commit-terraform で fmt / validate / tflint / docs / シークレットスキャンをコミット前に実行。
  • ハードコードを避ける: リージョン、AMI ID、アカウント ID などは変数やデータソースから取得する。
  • count より for_each を優先する: 要素の追加・削除でインデックスがずれて、意図しない再作成が起きるのを防ぐ。
  • locals で計算ロジックをまとめる: 複雑な式をリソース定義に直接書かない。
  • 動的ブロックは必要な場合だけ: 可読性が下がるため乱用しない。
  • コメントは「なぜ」を書く: 何をしているかはコードで分かるので、設計判断や制約の理由を残す。
  • depends_on は最小限に: 参照で暗黙の依存関係が作れる場合はそちらを使う。

14. アンチパターン

アンチパターン問題対策
コンソールでの手動変更ドリフト、再現不能書き込み権限を絞る、ドリフト検知
ローカルステート/ステートの Git コミット競合、シークレット漏えいリモートバックエンド+ロック
巨大な単一ステートplan が遅い、影響範囲が大きいライフサイクル単位で分割
環境ごとのコピペ差分が管理不能になる共通モジュール+変数
バージョン未固定ある日突然壊れるバージョン制約+ロックファイル
シークレットのハードコード漏えいシークレットマネージャ
個人 PC からの本番 apply監査不能、事故CI からのみ apply
plan を見ずに apply(-auto-approve の乱用)予期しない削除plan レビューと承認ゲート
ラッパーだけの浅いモジュール抽象化コストだけ増える意味のある単位でまとめる
ignore_changes = all の乱用IaC で管理していないのと同じ対象属性を限定する

15. 導入チェックリスト

最低限(まず整えるもの)

  • リモートバックエンド+ステートロック+暗号化+バージョニング
  • ツール・プロバイダのバージョン固定とロックファイルのコミット
  • .gitignore の整備とシークレットのコード外管理
  • PR で fmt / validate / plan を自動実行し、結果をレビュー
  • 環境ごとのステート分離

推奨(次に整えるもの)

  • apply を CI からのみ実行、本番は承認ゲート付き
  • CI 認証を OIDC 化し、plan 用/apply 用ロールを分離
  • tflint・セキュリティスキャン・シークレットスキャンの導入
  • 必須タグとプロバイダの default tags
  • 重要リソースの削除保護
  • 共通モジュールの整備とバージョン管理

発展(成熟度を上げるもの)

  • Policy as Code による組織ルールの強制
  • モジュールのユニットテスト・結合テスト
  • 定期的なドリフト検知と通知
  • コスト見積もりの PR 表示
  • Renovate / Dependabot による依存の自動更新
  • クラウドアカウント単位での環境分離

0件のページ