LabHub

ブログ

Terraform & Ansible コマンド完全ガイド:IaCとConfiguration Managementのすべて

한국어English日本語中文


1. 序論:Infrastructure as Codeの時代

1.1 なぜIaCとConfiguration Managementなのか

クラウドネイティブ時代にインフラを手作業で管理することは、もはや選択肢ではない。数百台のサーバー、数十のVPC、複雑に絡み合ったセキュリティグループとIAMポリシーをコンソールのクリックで管理するなら、それは 「Snowflake Server」 — 雪の結晶のようにすべてが異なるサーバー — を作る近道だ。再現できず、監査証跡を追いにくく、一度のミスがインフラ全体を崩壊させかねない。

Infrastructure as Code(IaC)とConfiguration Management(CM)は、この問題に対する業界の答えだ。

この二つの領域の事実上の標準(de facto standard)が、まさに TerraformAnsible だ。

1.2 TerraformとAnsibleの役割分担

TerraformとAnsibleは競合ツールではなく 相互補完ツール だ。それぞれの責任領域が明確に異なる。

┌──────────────────────────────────────────────────────────────────┐
IaC + CM ワークフロー                          │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────────────┐       ┌─────────────────────────┐      │
│  │     Terraform        │       │       Ansible            │      │
  (Provisioning)      │──────▶│  (Configuration)         │      │
│  │                      │       │                          │      │
│  │  - VPC/Subnet 作成   │       │  - パッケージ導入         │      │
│  │  - EC2/RDS 作成      │       │  - Nginx/Apache 設定     │      │
│  │  - S3 バケット作成   │       │  - アプリデプロイ         │      │
│  │  - IAM Role 作成     │       │  - セキュリティ強化       │      │
│  │  - Security Group    │       │  - 監視エージェント導入    │      │
│  └─────────────────────┘       └─────────────────────────┘      │
│                                                                  │
│  「インフラを作る」              「インフラを構成する」            │
Declarative (宣言的)          Procedural + DeclarativeState ベース                    Agentless (SSH/WinRM)└──────────────────────────────────────────────────────────────────┘

1.3 本記事の構成

本記事は大きく三つのパートで構成される。

  1. Part 1 — Terraform: HCL文法から中核ワークフロー、State管理、Workspace、モジュール、高度な機能まで
  2. Part 2 — Ansible: インベントリ、アドホックコマンド、Playbook、Role、Vault、Galaxyまで
  3. Part 3 — 統合とチートシート: Terraform + Ansible連携、コマンドチートシート、トラブルシューティング

Part 1: Terraform完全ガイド


2. Terraformの紹介とアーキテクチャ

2.1 Terraformとは

TerraformはHashiCorpが2014年に発表した オープンソースのIaCツール だ。HCL(HashiCorp Configuration Language)という宣言的言語でインフラを定義すると、Terraformが現在の状態(State)と望ましい状態(Configuration)を比較し、差分の分だけ変更を適用する。

2024年8月、HashiCorpはIBMに買収され、Terraformのライセンスは BSL(Business Source License)に変更された。これに対するコミュニティの対応として OpenTofu(Linux Foundation傘下)が誕生した。本記事で扱うほとんどのコマンドはOpenTofuでも同じように動作する。

2.2 Terraformのアーキテクチャ

Terraformの中核アーキテクチャは Core + Providers + State の三要素で構成される。

┌──────────────────────────────────────────────────────────────┐
Terraform Architecture├──────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌──────────────┐                                            │
│  │  .tf FilesHCL Configuration  (desired     │                                           │
│  │   state)      │                                           │
│  └──────┬───────┘                                            │
│         │                                                    │
│         ▼                                                    │
│  ┌──────────────────────────────────────────┐                │
│  │           Terraform Core                  │                │
│  │                                           │                │
│  │  ┌───────────┐    ┌──────────────┐       │                │
│  │  │ Resource   │    │  Dependency   │       │                │
│  │  │ Graph      │    │  Resolution   │       │                │
│  │  └───────────┘    └──────────────┘       │                │
│  │                                           │                │
│  │  ┌───────────┐    ┌──────────────┐       │                │
│  │  │ Plan      │    │  Apply        │       │                │
│  │  │ Engine    │    │  Engine       │       │                │
│  │  └───────────┘    └──────────────┘       │                │
│  └─────────┬───────────────┬────────────────┘                │
│            │               │                                  │
│     ┌──────▼──────┐ ┌─────▼──────────┐                      │
│     │  Providers   │ │  State File     │                      │
│     │              │ │                 │                      │
│     │ - AWS        │ │ terraform.tfstate││     │ - Azure (JSON)          │                      │
│     │ - GCP        │ │                 │                      │
│     │ - Kubernetes │ │ Local / Remote  │                      │
│     │ - 3000+ (S3, GCS, etc.) │                      │
│     └──────────────┘ └─────────────────┘                     │
│                                                               │
└──────────────────────────────────────────────────────────────┘

2.3 Terraform vs OpenTofu vs Pulumi

項目TerraformOpenTofuPulumi
ライセンスBSL 1.1MPL 2.0 (OSS)Apache 2.0
言語HCLHCLPython/Go/TS/C#
状態管理terraform.tfstateterraform.tfstatePulumi Cloud
Providerエコシステム3,000+Terraform互換100+
運営主体HashiCorp(IBM)Linux FoundationPulumi Inc.
CLIコマンドterraformtofupulumi

3. Terraformのインストールと環境設定

3.1 tfenvによるバージョン管理

プロジェクトごとに異なるTerraformバージョンを使う必要がある場合が多いため、tfenv(Terraform Version Manager)の利用を強く推奨する。

# macOS (Homebrew)
brew install tfenv

# Linux (Git Clone)
git clone https://github.com/tfutils/tfenv.git ~/.tfenv
echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bashrc

# 利用可能なバージョン一覧を確認
tfenv list-remote

# 特定バージョンのインストール
tfenv install 1.9.8
tfenv install 1.10.3

# グローバルなデフォルトバージョンの設定
tfenv use 1.10.3

# プロジェクトごとのバージョン固定 (.terraform-version ファイル)
echo "1.9.8" > .terraform-version

# インストール済みバージョン一覧
tfenv list

# 現在のバージョン確認
terraform version

3.2 直接インストール

# macOS (Homebrew)
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# Linux (APT)
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

# バージョン確認
terraform version
# Terraform v1.10.3
# on darwin_arm64

3.3 補完とエディタの設定

# Bash/Zsh 補完
terraform -install-autocomplete

# VS Code 拡張機能
# - HashiCorp Terraform (公式)
# - Terraform Autocomplete

4. Terraformの中核ワークフロー

Terraformの中核ワークフローは Write → Plan → Apply の三段階だ。この三段階を支えるコマンドを詳しく見ていこう。

4.1 terraform init — プロジェクトの初期化

terraform init はTerraformプロジェクトの 最初のコマンド だ。Providerプラグインのダウンロード、モジュールのダウンロード、Backendの初期化を行う。

# 基本の初期化
terraform init

# 主なフラグ
terraform init -upgrade              # Provider/モジュールを最新の許容バージョンにアップグレード
terraform init -reconfigure           # Backend設定を再構成 (既存のstateを無視)
terraform init -migrate-state         # Backend変更時にstateを移行
terraform init -backend=false         # Backendの初期化をスキップ (検証用途)
terraform init -get=false             # モジュールのダウンロードをスキップ
terraform init -input=false           # 対話的入力を無効化 (CI/CD用)
terraform init -no-color              # カラー出力を無効化 (ログ解析用)
terraform init -lockfile=readonly     # .terraform.lock.hcl の変更を禁止 (CI用)

# Backend設定をCLIから渡す (CI/CDで有用)
terraform init \
  -backend-config="bucket=my-tf-state" \
  -backend-config="key=prod/terraform.tfstate" \
  -backend-config="region=ap-northeast-2" \
  -backend-config="dynamodb_table=tf-lock"

terraform init 実行後に生成されるファイル/ディレクトリ:

.terraform/              # Providerプラグイン、モジュールキャッシュ
.terraform.lock.hcl      # Providerバージョンのロックファイル (コミット対象)

4.2 terraform validate — 構文検証

# 構文の妥当性検査 (init後に使用可能)
terraform validate

# JSON出力 (CI/CDパイプライン用)
terraform validate -json

# 出力例 (成功)
# Success! The configuration is valid.

# 出力例 (失敗、JSON)
# {
#   "valid": false,
#   "error_count": 1,
#   "diagnostics": [
#     {
#       "severity": "error",
#       "summary": "Unsupported argument",
#       "detail": "An argument named \"vps_id\" is not expected here. Did you mean \"vpc_id\"?"
#     }
#   ]
# }

4.3 terraform fmt — コードフォーマット

# カレントディレクトリの .tf ファイルをフォーマット
terraform fmt

# 再帰的にすべてのサブディレクトリをフォーマット
terraform fmt -recursive

# 変更が必要なファイルのみ表示 (CIでのチェック用)
terraform fmt -check

# diff 出力
terraform fmt -diff

# CI/CDパイプラインでの活用
terraform fmt -check -recursive -diff
# 終了コード 0: フォーマットの変更なし
# 終了コード 3: フォーマットの変更が必要

4.4 terraform plan — 実行計画

terraform plan はTerraformの 最も重要なコマンド の一つだ。現在のStateとConfigurationを比較し、どんな変更が起きるかを事前に見せてくれる。実際のインフラには一切変更を加えない。

# 基本のPlan
terraform plan

# 主なフラグ
terraform plan -out=tfplan              # Planをファイルに保存 (applyで使用)
terraform plan -destroy                 # 削除計画の確認
terraform plan -target=aws_instance.web # 特定リソースのみPlan
terraform plan -var="instance_type=t3.large"  # 変数の受け渡し
terraform plan -var-file="prod.tfvars"  # 変数ファイルの指定
terraform plan -refresh=false           # Stateの再取得をスキップ (高速化)
terraform plan -parallelism=20          # 同時実行数 (デフォルト: 10)
terraform plan -compact-warnings        # 警告メッセージを簡潔に
terraform plan -no-color                # カラー出力を無効化
terraform plan -input=false             # 対話的入力を無効化
terraform plan -json                    # JSON出力 (自動化用)
terraform plan -detailed-exitcode       # 詳細な終了コード
# 終了コード 0: 変更なし
# 終了コード 1: エラー発生
# 終了コード 2: 変更あり

# CI/CDパイプライン推奨パターン
terraform plan -out=tfplan -input=false -no-color -detailed-exitcode

Plan出力の記号の読み方:

# + create    (リソースの作成)
# - destroy   (リソースの削除)
# ~ update    (リソースの修正、in-place)
# -/+ replace (リソースを削除してから再作成)
# <= read     (データソースの読み取り)

4.5 terraform apply — 変更の適用

# 基本のApply (Plan後に確認プロンプト)
terraform apply

# 保存したPlanファイルでApply (確認プロンプトなし)
terraform apply tfplan

# 自動承認 (CI/CD用、要注意!)
terraform apply -auto-approve

# 主なフラグ
terraform apply -target=aws_instance.web      # 特定リソースのみ適用
terraform apply -var="instance_type=t3.large"  # 変数の受け渡し
terraform apply -var-file="prod.tfvars"        # 変数ファイルの指定
terraform apply -parallelism=20                # 同時実行数
terraform apply -refresh=false                 # Stateの再取得をスキップ
terraform apply -replace=aws_instance.web      # リソースを強制的に再作成 (taintの代替)
terraform apply -lock=false                    # Stateロックを無効化 (非推奨)
terraform apply -lock-timeout=5m               # Stateロックの待機時間

# 安全なCI/CDパターン (Plan → Save → Apply)
terraform plan -out=tfplan -input=false
# ... レビュー ...
terraform apply tfplan

4.6 terraform destroy — インフラの削除

# すべてのリソースを削除 (確認プロンプト)
terraform destroy

# 自動承認
terraform destroy -auto-approve

# 特定リソースのみ削除
terraform destroy -target=aws_instance.web

# 変数ファイルの指定
terraform destroy -var-file="prod.tfvars"

# 削除Planの事前確認
terraform plan -destroy

4.7 terraform output — 出力値の参照

# すべてのOutput値を表示
terraform output

# 特定のOutput値
terraform output vpc_id

# Raw値 (引用符なし、スクリプト用)
terraform output -raw vpc_id

# JSON出力
terraform output -json

# 他のTerraformプロジェクトやAnsibleでの活用
VPC_ID=$(terraform output -raw vpc_id)
echo "VPC ID: $VPC_ID"

5. Terraform State管理

5.1 Stateとは何か

Terraform State(terraform.tfstate)は、Terraformが管理するインフラの 現在の状態を記録したJSONファイル だ。Terraformはこのファイルを通じて、Configuration(望ましい状態)と実際のインフラ(現在の状態)の差分を計算する。

Stateファイルにはリソース ID、属性値、メタデータが含まれ、機密情報(パスワード、アクセスキーなど)も平文で保存 されうるため、必ず暗号化されたリモートBackend(S3 + KMSなど)を使わなければならない。

5.2 Remote Backendの設定

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/network/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-lock"    # Stateロック用のDynamoDBテーブル
    kms_key_id     = "alias/terraform"   # KMS暗号鍵
  }
}

5.3 terraform state コマンド

# ── リソース一覧の参照 ──
terraform state list
# aws_vpc.main
# aws_subnet.public[0]
# aws_subnet.public[1]
# aws_instance.web
# aws_db_instance.main

# フィルタリング
terraform state list aws_subnet.*
terraform state list module.network

# ── リソース詳細の参照 ──
terraform state show aws_instance.web
# resource "aws_instance" "web" {
#     ami                    = "ami-0c55b159cbfafe1f0"
#     arn                    = "arn:aws:ec2:ap-northeast-2:123456789:instance/i-0abc123def456"
#     instance_type          = "t3.medium"
#     private_ip             = "10.0.1.50"
#     public_ip              = "54.180.xxx.xxx"
#     ...
# }

# ── リソースの移動 (名前変更/モジュール移動) ──
# リソース名の変更 (コード側も合わせて変更が必要)
terraform state mv aws_instance.web aws_instance.app_server

# モジュールへ移動
terraform state mv aws_vpc.main module.network.aws_vpc.main

# 別のStateファイルへ移動
terraform state mv -state-out=other.tfstate aws_instance.web aws_instance.web

# Dry-run (実際には変更せずに確認)
terraform state mv -dry-run aws_instance.web aws_instance.app

# ── Stateからリソースを除去 (実インフラは維持) ──
terraform state rm aws_instance.web
# Terraformはこれ以降このリソースを管理しない
# 実際のEC2インスタンスは削除されない

# ── Remote Stateのダウンロード ──
terraform state pull > terraform.tfstate.backup

# ── Local StateをRemoteへアップロード ──
terraform state push terraform.tfstate

# 強制アップロード (serial番号の衝突を無視、危険!)
terraform state push -force terraform.tfstate

# ── Stateロックの解除 ──
# 異常終了でロックが解放されなかったとき
terraform force-unlock LOCK_ID
# LOCK_IDはエラーメッセージに表示される

# 確認プロンプトなしで強制解除
terraform force-unlock -force LOCK_ID

5.4 terraform import — 既存リソースの取り込み

既存の手作業で作成したリソースをTerraformの管理下に置くには import を使う。

# 従来のCLI import (Terraform 1.5より前)
terraform import aws_instance.web i-0abc123def456
terraform import aws_vpc.main vpc-0abc123def
terraform import 'aws_subnet.public[0]' subnet-0abc123def
terraform import module.network.aws_vpc.main vpc-0abc123def

Terraform 1.5+ の import block(宣言的Import、推奨):

# import.tf
import {
  to = aws_instance.web
  id = "i-0abc123def456"
}

import {
  to = aws_vpc.main
  id = "vpc-0abc123def"
}
# import block ベースのPlan (コードの自動生成)
terraform plan -generate-config-out=generated.tf

# 生成されたコードを確認してからApply
terraform apply

6. Terraform Workspace

Workspaceは 同じConfigurationで複数の環境(dev/staging/prod)を管理 するときに有用だ。各Workspaceは独立したStateファイルを持つ。

# ── Workspace 一覧 ──
terraform workspace list
# * default
#   dev
#   staging
#   prod

# ── 新しいWorkspaceの作成 ──
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod

# ── Workspaceの切り替え ──
terraform workspace select prod

# ── 現在のWorkspaceの確認 ──
terraform workspace show
# prod

# ── Workspaceの削除 (空のStateのみ可能) ──
terraform workspace delete dev

# 強制削除 (Stateが残っていても削除)
terraform workspace delete -force dev

WorkspaceをHCLで活用するパターン:

# 環境ごとのインスタンスタイプの分岐
locals {
  instance_type = {
    dev     = "t3.micro"
    staging = "t3.small"
    prod    = "t3.large"
  }
}

resource "aws_instance" "app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = local.instance_type[terraform.workspace]

  tags = {
    Name        = "app-${terraform.workspace}"
    Environment = terraform.workspace
  }
}

7. Terraformのモジュール管理

7.1 モジュールの構造

modules/
├── network/
│   ├── main.tf       # VPC, Subnet, IGW, NAT
│   ├── variables.tf  # 入力変数
│   ├── outputs.tf    # 出力値
│   └── README.md
├── compute/
│   ├── main.tf       # EC2, ASG, ALB
│   ├── variables.tf
│   └── outputs.tf
└── database/
    ├── main.tf       # RDS, ElastiCache
    ├── variables.tf
    └── outputs.tf

7.2 モジュールのソース種別

# ローカルモジュール
module "network" {
  source = "./modules/network"
}

# Terraform Registry
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.16.0"
}

# GitHub
module "vpc" {
  source = "github.com/terraform-aws-modules/terraform-aws-vpc?ref=v5.16.0"
}

# S3 Bucket
module "vpc" {
  source = "s3::https://s3-ap-northeast-2.amazonaws.com/my-modules/vpc.zip"
}

# Git (SSH)
module "vpc" {
  source = "git::ssh://git@github.com/myorg/modules.git//network?ref=v1.0.0"
}

7.3 モジュール関連コマンド

# モジュールのダウンロード/更新
terraform init -upgrade

# モジュールが使用するProviderの確認
terraform providers

# Providerロックファイルの更新 (複数プラットフォーム対応)
terraform providers lock \
  -platform=linux_amd64 \
  -platform=darwin_arm64

# Providerミラー (エアギャップ環境)
terraform providers mirror /path/to/mirror

8. Terraformの高度な機能

8.1 terraform console — 対話的コンソール

# 対話的な式の評価
terraform console

# 使用例
> var.instance_type
"t3.medium"

> length(var.subnet_ids)
3

> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"

> formatdate("YYYY-MM-DD", timestamp())
"2026-03-01"

> [for s in var.subnet_ids : upper(s)]
["SUBNET-AAA", "SUBNET-BBB", "SUBNET-CCC"]

# 終了: Ctrl+D または exit

8.2 terraform graph — 依存関係グラフ

# DOT形式で依存関係グラフを出力
terraform graph

# Graphvizで画像を生成
terraform graph | dot -Tpng > graph.png
terraform graph | dot -Tsvg > graph.svg

# Planベースのグラフ
terraform graph -type=plan

# 特定リソースを中心にしたグラフ
terraform graph -draw-cycles

8.3 その他のユーティリティコマンド

# 現在の設定で使用するProviderツリーを表示
terraform providers

# ProviderスキーマをJSONで出力
terraform providers schema -json

# Terraformのバージョン確認
terraform version

# JSON出力
terraform version -json

# Terraform設定ファイルの場所を表示
terraform -help

# 特定コマンドのヘルプ
terraform plan -help

9. HCL文法チートシート

9.1 Variables (入力変数)

# variables.tf

# 基本型
variable "region" {
  description = "AWS Region"
  type        = string
  default     = "ap-northeast-2"
}

variable "instance_count" {
  description = "Number of instances"
  type        = number
  default     = 2
}

variable "enable_monitoring" {
  description = "Enable CloudWatch monitoring"
  type        = bool
  default     = true
}

# 複合型 - List
variable "availability_zones" {
  type    = list(string)
  default = ["ap-northeast-2a", "ap-northeast-2c"]
}

# 複合型 - Map
variable "instance_types" {
  type = map(string)
  default = {
    dev  = "t3.micro"
    prod = "t3.large"
  }
}

# 複合型 - Object
variable "database_config" {
  type = object({
    engine         = string
    engine_version = string
    instance_class = string
    multi_az       = bool
    storage_gb     = number
  })
  default = {
    engine         = "mysql"
    engine_version = "8.0"
    instance_class = "db.t3.medium"
    multi_az       = true
    storage_gb     = 100
  }
}

# 機密変数
variable "db_password" {
  description = "Database master password"
  type        = string
  sensitive   = true  # Plan/Apply の出力でマスキング
}

# Validation Rule
variable "instance_type" {
  type = string
  validation {
    condition     = can(regex("^t3\\.", var.instance_type))
    error_message = "Instance type must start with t3."
  }
}

# Nullable
variable "override_name" {
  type     = string
  default  = null
  nullable = true
}

変数値の渡し方 (優先順位の高い順):

# 1. CLI -var フラグ (最優先)
terraform apply -var="region=us-east-1"

# 2. -var-file フラグ
terraform apply -var-file="prod.tfvars"

# 3. *.auto.tfvars (自動ロード)
# terraform.tfvars, *.auto.tfvars

# 4. 環境変数 (TF_VAR_ 接頭辞)
export TF_VAR_region="us-east-1"
export TF_VAR_db_password="SuperSecret123!"

# 5. default 値

9.2 Locals (ローカル変数)

locals {
  project_name = "my-app"
  environment  = terraform.workspace

  common_tags = {
    Project     = local.project_name
    Environment = local.environment
    ManagedBy   = "terraform"
    Team        = "platform"
  }

  # 条件付きの値
  is_prod = local.environment == "prod"

  # 計算された値
  name_prefix = "${local.project_name}-${local.environment}"
}

resource "aws_instance" "app" {
  # ...
  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-app"
  })
}

9.3 Outputs (出力値)

# outputs.tf

output "vpc_id" {
  description = "The ID of the VPC"
  value       = aws_vpc.main.id
}

output "public_subnet_ids" {
  description = "List of public subnet IDs"
  value       = aws_subnet.public[*].id
}

output "db_endpoint" {
  description = "RDS endpoint"
  value       = aws_db_instance.main.endpoint
  sensitive   = true  # 機密の出力値をマスキング
}

# 他のモジュールから参照
# module.network.vpc_id

9.4 count と for_each

# count — 同じリソースをN個作成
resource "aws_subnet" "public" {
  count             = length(var.availability_zones)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "public-subnet-${count.index}"
  }
}

# 参照: aws_subnet.public[0], aws_subnet.public[1]

# for_each — Map または Set ベースの反復 (推奨)
resource "aws_iam_user" "users" {
  for_each = toset(["alice", "bob", "charlie"])
  name     = each.value
}

# Map ベースの for_each
variable "instances" {
  default = {
    web = { type = "t3.small", az = "ap-northeast-2a" }
    api = { type = "t3.medium", az = "ap-northeast-2c" }
    worker = { type = "t3.large", az = "ap-northeast-2a" }
  }
}

resource "aws_instance" "servers" {
  for_each      = var.instances
  ami           = data.aws_ami.ubuntu.id
  instance_type = each.value.type
  availability_zone = each.value.az

  tags = {
    Name = "${each.key}-server"
  }
}

# 参照: aws_instance.servers["web"], aws_instance.servers["api"]

9.5 dynamic Block

# セキュリティグループのルールを動的に生成
variable "ingress_rules" {
  default = [
    { port = 80,  cidr = "0.0.0.0/0",   description = "HTTP" },
    { port = 443, cidr = "0.0.0.0/0",   description = "HTTPS" },
    { port = 22,  cidr = "10.0.0.0/8",  description = "SSH (internal)" },
  ]
}

resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Web server security group"
  vpc_id      = aws_vpc.main.id

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = "tcp"
      cidr_blocks = [ingress.value.cidr]
      description = ingress.value.description
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

9.6 lifecycle メタ引数

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

  lifecycle {
    # 削除の前に新しいリソースを先に作成 (ダウンタイム最小化)
    create_before_destroy = true

    # 特定属性の変更を無視 (外部から変更されるタグなど)
    ignore_changes = [
      tags["LastModified"],
      ami,
    ]

    # 削除防止 (誤って destroy できないように)
    prevent_destroy = true

    # 事前/事後条件
    precondition {
      condition     = var.instance_type != "t3.nano"
      error_message = "t3.nano is too small for this application."
    }

    postcondition {
      condition     = self.public_ip != ""
      error_message = "Instance must have a public IP."
    }

    # リソース置換のトリガー (値が変わるとリソースを再作成)
    replace_triggered_by = [
      aws_ami.app_ami.id
    ]
  }
}

10. Terraform実践例 — AWS VPC + EC2 + RDS

10.1 プロジェクト構造

terraform-aws-project/
├── main.tf           # Provider、Backend 設定
├── variables.tf      # 入力変数の定義
├── outputs.tf        # 出力値の定義
├── terraform.tfvars  # 変数値 (gitignore 対象)
├── network.tf        # VPC, Subnet, IGW, NAT, Route Table
├── compute.tf        # EC2, Security Group, Key Pair
├── database.tf       # RDS, Subnet Group
├── data.tf           # Data Sources (AMI 参照など)
└── versions.tf       # Provider/Terraform バージョン制約

10.2 全体コード

# versions.tf
terraform {
  required_version = ">= 1.9.0"

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

  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-lock"
  }
}

# main.tf
provider "aws" {
  region = var.region

  default_tags {
    tags = {
      Project     = var.project_name
      Environment = terraform.workspace
      ManagedBy   = "terraform"
    }
  }
}

# variables.tf
variable "region" {
  type    = string
  default = "ap-northeast-2"
}

variable "project_name" {
  type    = string
  default = "myapp"
}

variable "vpc_cidr" {
  type    = string
  default = "10.0.0.0/16"
}

variable "azs" {
  type    = list(string)
  default = ["ap-northeast-2a", "ap-northeast-2c"]
}

variable "db_password" {
  type      = string
  sensitive = true
}

# data.tf
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
  }
}

# network.tf
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = { Name = "${var.project_name}-vpc" }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags   = { Name = "${var.project_name}-igw" }
}

resource "aws_subnet" "public" {
  count                   = length(var.azs)
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone       = var.azs[count.index]
  map_public_ip_on_launch = true

  tags = { Name = "${var.project_name}-public-${count.index}" }
}

resource "aws_subnet" "private" {
  count             = length(var.azs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
  availability_zone = var.azs[count.index]

  tags = { Name = "${var.project_name}-private-${count.index}" }
}

resource "aws_eip" "nat" {
  domain = "vpc"
  tags   = { Name = "${var.project_name}-nat-eip" }
}

resource "aws_nat_gateway" "main" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id

  tags = { Name = "${var.project_name}-nat" }
  depends_on = [aws_internet_gateway.main]
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = { Name = "${var.project_name}-public-rt" }
}

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main.id
  }

  tags = { Name = "${var.project_name}-private-rt" }
}

resource "aws_route_table_association" "public" {
  count          = length(var.azs)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "private" {
  count          = length(var.azs)
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private.id
}

# compute.tf
resource "aws_security_group" "web" {
  name        = "${var.project_name}-web-sg"
  description = "Security group for web servers"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTP"
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTPS"
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/8"]
    description = "SSH internal"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = { Name = "${var.project_name}-web-sg" }
}

resource "aws_instance" "web" {
  count                  = 2
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = "t3.small"
  subnet_id              = aws_subnet.public[count.index % length(var.azs)].id
  vpc_security_group_ids = [aws_security_group.web.id]

  root_block_device {
    volume_size = 30
    volume_type = "gp3"
    encrypted   = true
  }

  tags = { Name = "${var.project_name}-web-${count.index}" }

  lifecycle {
    create_before_destroy = true
    ignore_changes        = [ami]
  }
}

# database.tf
resource "aws_security_group" "db" {
  name        = "${var.project_name}-db-sg"
  description = "Security group for RDS"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.web.id]
    description     = "MySQL from web servers"
  }

  tags = { Name = "${var.project_name}-db-sg" }
}

resource "aws_db_subnet_group" "main" {
  name       = "${var.project_name}-db-subnet-group"
  subnet_ids = aws_subnet.private[*].id

  tags = { Name = "${var.project_name}-db-subnet-group" }
}

resource "aws_db_instance" "main" {
  identifier           = "${var.project_name}-mysql"
  engine               = "mysql"
  engine_version       = "8.0"
  instance_class       = "db.t3.medium"
  allocated_storage    = 100
  storage_type         = "gp3"
  storage_encrypted    = true
  db_name              = "myapp"
  username             = "admin"
  password             = var.db_password
  multi_az             = true
  db_subnet_group_name = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.db.id]
  skip_final_snapshot  = false
  final_snapshot_identifier = "${var.project_name}-final-snapshot"
  backup_retention_period   = 7

  tags = { Name = "${var.project_name}-mysql" }

  lifecycle {
    prevent_destroy = true
  }
}

# outputs.tf
output "vpc_id" {
  value = aws_vpc.main.id
}

output "public_subnet_ids" {
  value = aws_subnet.public[*].id
}

output "web_instance_ips" {
  value = aws_instance.web[*].public_ip
}

output "rds_endpoint" {
  value     = aws_db_instance.main.endpoint
  sensitive = true
}

Part 2: Ansible完全ガイド


11. Ansibleの紹介とアーキテクチャ

11.1 Ansibleとは

AnsibleはRed Hatが管理する オープンソースの自動化ツール で、2012年にMichael DeHaanが作った。Configuration Management、Application Deployment、Task Automationを一つのツールで実行できる。

Ansibleの中核となる哲学:

11.2 Ansibleのアーキテクチャ

┌──────────────────────────────────────────────────────────────────┐
Ansible Architecture├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌─────────────────────────────────────────────────┐             │
│  │              Control Node                        │             │
│  │                                                  │             │
│  │  ┌────────────┐  ┌──────────┐  ┌────────────┐  │             │
│  │  │ Playbook   │  │ Inventory │  │  Modules    │  │             │
│  │   (.yml) (hosts)  (2,500+)   │  │             │
│  │  └─────┬──────┘  └────┬─────┘  └──────┬─────┘  │             │
│  │        │              │               │         │             │
│  │        ▼              ▼               ▼         │             │
│  │  ┌──────────────────────────────────────────┐   │             │
│  │  │            Ansible Engine                 │   │             │
│  │  │                                           │   │             │
│  │  │  - Task Execution                         │   │             │
│  │  │  - Variable Resolution                    │   │             │
│  │  │  - Connection Management (SSH/WinRM)      │   │             │
│  │  │  - Fact Gathering                         │   │             │
│  │  └─────────────┬─────────────────────────────┘   │             │
│  └────────────────┼─────────────────────────────────┘             │
│                   │                                               │
SSH / WinRM (No Agent!)│                   │                                               │
│     ┌─────────────┼──────────────────────────────┐               │
│     │             ▼                              │               │
│     │  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐  │               │
│     │  │ Web1 │  │ Web2 │  │ DB1  │  │ DB2  │  │               │
│     │  └──────┘  └──────┘  └──────┘  └──────┘  │               │
│     │            Managed Nodes                   │               │
│     └────────────────────────────────────────────┘               │
│                                                                   │
└──────────────────────────────────────────────────────────────────┘

12. Ansibleのインストール

# ── pip でインストール (推奨) ──
pip install ansible

# 特定バージョンのインストール
pip install ansible==10.6.0

# ansible-core のみインストール (最小構成)
pip install ansible-core

# ── macOS (Homebrew) ──
brew install ansible

# ── Ubuntu/Debian ──
sudo apt update
sudo apt install ansible

# ── RHEL/CentOS/Fedora ──
sudo dnf install ansible

# ── バージョン確認 ──
ansible --version
# ansible [core 2.17.7]
#   config file = /etc/ansible/ansible.cfg
#   configured module search path = ['~/.ansible/plugins/modules']
#   ansible python module location = /usr/lib/python3.12/site-packages/ansible
#   python version = 3.12.8

# ── ansible-navigator のインストール (TUI、Execution Environment 対応) ──
pip install ansible-navigator

13. Ansibleのインベントリ

13.1 静的インベントリ (INI形式)

# inventory/hosts.ini

# 個別ホスト
web1.example.com
web2.example.com

# グループ定義
[webservers]
web1.example.com ansible_host=10.0.1.10
web2.example.com ansible_host=10.0.1.11

[dbservers]
db1.example.com ansible_host=10.0.2.10 ansible_port=2222
db2.example.com ansible_host=10.0.2.11

[monitoring]
grafana.example.com

# グループの変数
[webservers:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/web_key.pem
http_port=80

[dbservers:vars]
ansible_user=ec2-user
ansible_ssh_private_key_file=~/.ssh/db_key.pem

# グループのグループ (Children)
[production:children]
webservers
dbservers
monitoring

[production:vars]
env=production

# ホスト範囲パターン
[loadbalancers]
lb[01:03].example.com  # lb01, lb02, lb03

13.2 静的インベントリ (YAML形式)

# inventory/hosts.yml
all:
  children:
    production:
      children:
        webservers:
          hosts:
            web1.example.com:
              ansible_host: 10.0.1.10
            web2.example.com:
              ansible_host: 10.0.1.11
          vars:
            ansible_user: ubuntu
            ansible_ssh_private_key_file: ~/.ssh/web_key.pem
            http_port: 80

        dbservers:
          hosts:
            db1.example.com:
              ansible_host: 10.0.2.10
            db2.example.com:
              ansible_host: 10.0.2.11
          vars:
            ansible_user: ec2-user
      vars:
        env: production

    staging:
      children:
        webservers_stg:
          hosts:
            stg-web1.example.com:
              ansible_host: 10.1.1.10

13.3 動的インベントリ

動的インベントリはクラウドAPIからリアルタイムにホスト一覧を取得する。

# inventory/aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-2

keyed_groups:
  - key: tags.Environment
    prefix: env
  - key: instance_type
    prefix: type
  - key: placement.availability_zone
    prefix: az

filters:
  tag:ManagedBy: ansible
  instance-state-name: running

compose:
  ansible_host: private_ip_address
# 動的インベントリのテスト
ansible-inventory -i inventory/aws_ec2.yml --list
ansible-inventory -i inventory/aws_ec2.yml --graph

13.4 インベントリ関連コマンド

# インベントリのホスト一覧を確認
ansible-inventory -i inventory/hosts.yml --list
ansible-inventory -i inventory/hosts.yml --graph

# 特定グループのみ表示
ansible-inventory -i inventory/hosts.yml --graph webservers

# ホスト変数の確認
ansible-inventory -i inventory/hosts.yml --host web1.example.com

# JSON出力
ansible-inventory -i inventory/hosts.yml --list --yaml

14. Ansibleのアドホックコマンド

アドホックコマンドはPlaybookなしで 一行で実行する使い捨てのコマンド だ。手早い確認や簡単な作業に便利だ。

14.1 基本文法

ansible [ホストパターン] -i [インベントリ] -m [モジュール] -a "[引数]" [オプション]

14.2 主なモジュール別の例

# ── ping: 接続確認 ──
ansible all -i inventory/hosts.yml -m ping
ansible webservers -i inventory/hosts.yml -m ping

# ── command: コマンド実行 (デフォルトモジュール、シェル機能は非対応) ──
ansible webservers -m command -a "uptime"
ansible webservers -m command -a "df -h"
ansible webservers -m command -a "free -m"

# ── shell: シェルコマンド実行 (パイプ、リダイレクト対応) ──
ansible webservers -m shell -a "ps aux | grep nginx | wc -l"
ansible webservers -m shell -a "cat /etc/os-release | head -5"
ansible dbservers -m shell -a "mysql -e 'SHOW DATABASES;'"

# ── copy: ファイルコピー ──
ansible webservers -m copy -a "src=./app.conf dest=/etc/nginx/conf.d/app.conf owner=root group=root mode=0644"

# ── file: ファイル/ディレクトリ管理 ──
ansible webservers -m file -a "path=/opt/app state=directory owner=www-data mode=0755"
ansible webservers -m file -a "path=/tmp/old_file state=absent"
ansible webservers -m file -a "src=/etc/nginx/sites-available/app dest=/etc/nginx/sites-enabled/app state=link"

# ── apt: パッケージ管理 (Debian/Ubuntu) ──
ansible webservers -m apt -a "name=nginx state=present update_cache=yes" --become
ansible webservers -m apt -a "name=nginx state=latest" --become
ansible webservers -m apt -a "name=nginx state=absent" --become
ansible webservers -m apt -a "upgrade=dist" --become

# ── yum/dnf: パッケージ管理 (RHEL/CentOS) ──
ansible dbservers -m dnf -a "name=mysql-server state=present" --become

# ── service/systemd: サービス管理 ──
ansible webservers -m service -a "name=nginx state=started enabled=yes" --become
ansible webservers -m service -a "name=nginx state=restarted" --become
ansible webservers -m service -a "name=nginx state=stopped" --become
ansible webservers -m systemd -a "name=nginx state=reloaded daemon_reload=yes" --become

# ── user: ユーザー管理 ──
ansible all -m user -a "name=deploy state=present groups=sudo shell=/bin/bash" --become
ansible all -m user -a "name=olduser state=absent remove=yes" --become

# ── setup: ファクト(システム情報)の収集 ──
ansible webservers -m setup
ansible webservers -m setup -a "filter=ansible_os_family"
ansible webservers -m setup -a "filter=ansible_memtotal_mb"
ansible webservers -m setup -a "filter=ansible_distribution*"

# ── lineinfile: ファイル内の行の管理 ──
ansible webservers -m lineinfile -a "path=/etc/ssh/sshd_config regexp='^PermitRootLogin' line='PermitRootLogin no'" --become

# ── cron: cronジョブ管理 ──
ansible webservers -m cron -a "name='log cleanup' minute='0' hour='3' job='find /var/log -name \"*.gz\" -mtime +30 -delete'"

# ── get_url: URLからファイルをダウンロード ──
ansible webservers -m get_url -a "url=https://example.com/app.tar.gz dest=/tmp/app.tar.gz"

# ── git: Gitリポジトリのクローン/プル ──
ansible webservers -m git -a "repo=https://github.com/myorg/myapp.git dest=/opt/myapp version=main"

14.3 アドホックの主なオプション

# 主なオプション
-i inventory/hosts.yml  # インベントリファイルの指定
-m module_name           # モジュールの指定 (デフォルト: command)
-a "arguments"           # モジュールの引数
--become (-b)            # sudo による権限昇格
--become-user root       # 権限昇格の対象ユーザー
--become-method sudo     # 権限昇格の方法
-u ubuntu                # SSH接続ユーザー
--private-key ~/.ssh/key # SSH鍵
-f 10                    # 並列実行数 (デフォルト: 5)
--limit web1             # 特定ホストのみ実行
-v / -vv / -vvv / -vvvv  # 詳細出力レベル
--check                  # Dry-run (実際の変更なし)
--diff                   # 変更内容の diff 表示
-o                       # 一行出力 (要約)
--ask-pass (-k)          # SSHパスワードのプロンプト
--ask-become-pass (-K)   # sudoパスワードのプロンプト

15. Ansible Playbook

15.1 ansible-playbook コマンドのオプション

# 基本の実行
ansible-playbook -i inventory/hosts.yml playbook.yml

# 主なオプション
ansible-playbook playbook.yml \
  -i inventory/hosts.yml \       # インベントリ
  --limit webservers \           # 対象ホストの制限
  --tags "nginx,ssl" \           # 特定タグのみ実行
  --skip-tags "debug" \          # 特定タグをスキップ
  -e "env=prod version=2.1" \    # 追加変数の受け渡し
  -e @vars/prod.yml \            # 変数ファイルの受け渡し
  --check \                      # Dry-run
  --diff \                       # 変更内容の diff
  --start-at-task "Install Nginx" \ # 特定タスクから開始
  --step \                       # タスクごとの確認プロンプト
  --list-tasks \                 # タスク一覧のみ表示
  --list-tags \                  # タグ一覧のみ表示
  --list-hosts \                 # 対象ホスト一覧のみ表示
  -f 20 \                        # 並列実行数
  --become \                     # sudo
  --vault-password-file .vault_pass \ # Vaultパスワードファイル
  --ask-vault-pass \             # Vaultパスワードのプロンプト
  -v                             # 詳細出力

# 構文チェック
ansible-playbook playbook.yml --syntax-check

15.2 Playbookの基本構造

# site.yml
---
- name: Configure web servers
  hosts: webservers
  become: yes
  gather_facts: yes
  vars:
    http_port: 80
    app_version: '2.1.0'

  pre_tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600

  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present
      tags: [nginx]

    - name: Deploy Nginx config
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
      notify: Restart Nginx
      tags: [nginx, config]

    - name: Deploy application
      git:
        repo: 'https://github.com/myorg/myapp.git'
        dest: /opt/myapp
        version: '{{ app_version }}'
      tags: [deploy]

    - name: Ensure Nginx is running
      service:
        name: nginx
        state: started
        enabled: yes
      tags: [nginx]

  post_tasks:
    - name: Verify HTTP response
      uri:
        url: 'http://localhost:{{ http_port }}'
        status_code: 200
      register: result
      retries: 3
      delay: 5
      until: result.status == 200

  handlers:
    - name: Restart Nginx
      service:
        name: nginx
        state: restarted

15.3 条件分岐 (when)

tasks:
  # 基本の条件
  - name: Install Apache (Debian)
    apt:
      name: apache2
      state: present
    when: ansible_os_family == "Debian"

  - name: Install httpd (RedHat)
    dnf:
      name: httpd
      state: present
    when: ansible_os_family == "RedHat"

  # 複合条件
  - name: Configure for production
    template:
      src: prod.conf.j2
      dest: /etc/app/app.conf
    when:
      - env == "prod"
      - ansible_memtotal_mb >= 4096

  # OR 条件
  - name: Install on Debian or Ubuntu
    apt:
      name: curl
      state: present
    when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

  # 変数の存在チェック
  - name: Configure custom DNS
    template:
      src: resolv.conf.j2
      dest: /etc/resolv.conf
    when: custom_dns is defined

  # 直前のタスク結果に基づく条件
  - name: Check if config exists
    stat:
      path: /etc/app/app.conf
    register: config_file

  - name: Create default config
    template:
      src: default.conf.j2
      dest: /etc/app/app.conf
    when: not config_file.stat.exists

15.4 繰り返し (loop)

tasks:
  # 基本の loop
  - name: Install packages
    apt:
      name: '{{ item }}'
      state: present
    loop:
      - nginx
      - python3
      - git
      - curl
      - htop

  # より効率的な方法 (一度にインストール)
  - name: Install packages (optimized)
    apt:
      name:
        - nginx
        - python3
        - git
      state: present

  # Dict loop
  - name: Create users
    user:
      name: '{{ item.name }}'
      groups: '{{ item.groups }}'
      shell: '{{ item.shell }}'
      state: present
    loop:
      - { name: 'alice', groups: 'sudo', shell: '/bin/bash' }
      - { name: 'bob', groups: 'developers', shell: '/bin/zsh' }
      - { name: 'charlie', groups: 'developers', shell: '/bin/bash' }

  # with_fileglob (ファイルグロブ)
  - name: Copy all config files
    copy:
      src: '{{ item }}'
      dest: /etc/app/conf.d/
    with_fileglob:
      - 'files/configs/*.conf'

  # loop_control
  - name: Create directories with index
    file:
      path: '/opt/app/data-{{ idx }}'
      state: directory
    loop:
      - logs
      - cache
      - uploads
    loop_control:
      index_var: idx
      label: '{{ item }}' # 出力に表示するラベル (機密データを隠す)

15.5 Handlers

tasks:
  - name: Update Nginx config
    template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify:
      - Validate Nginx config
      - Restart Nginx

  - name: Update SSL certificate
    copy:
      src: ssl/cert.pem
      dest: /etc/ssl/certs/app.pem
    notify: Restart Nginx

handlers:
  # Handler は notify されたときのみ実行、複数回 notify されても一度だけ実行
  - name: Validate Nginx config
    command: nginx -t
    listen: 'Validate Nginx config'

  - name: Restart Nginx
    service:
      name: nginx
      state: restarted
    listen: 'Restart Nginx'

15.6 Block (エラー処理)

tasks:
  - name: Application deployment with rollback
    block:
      - name: Pull latest code
        git:
          repo: 'https://github.com/myorg/myapp.git'
          dest: /opt/myapp
          version: '{{ app_version }}'

      - name: Install dependencies
        pip:
          requirements: /opt/myapp/requirements.txt
          virtualenv: /opt/myapp/venv

      - name: Run database migrations
        command: /opt/myapp/venv/bin/python manage.py migrate
        args:
          chdir: /opt/myapp

      - name: Restart application
        systemd:
          name: myapp
          state: restarted

    rescue:
      - name: Rollback to previous version
        git:
          repo: 'https://github.com/myorg/myapp.git'
          dest: /opt/myapp
          version: '{{ previous_version }}'

      - name: Restart application (rollback)
        systemd:
          name: myapp
          state: restarted

      - name: Send failure notification
        slack:
          token: '{{ slack_token }}'
          channel: '#deploy'
          msg: 'Deployment of {{ app_version }} FAILED. Rolled back to {{ previous_version }}.'

    always:
      - name: Clean up temp files
        file:
          path: /tmp/deploy_artifacts
          state: absent

      - name: Log deployment result
        lineinfile:
          path: /var/log/deployments.log
          line: "{{ ansible_date_time.iso8601 }} - {{ app_version }} - {{ ansible_failed_task.name | default('SUCCESS') }}"
          create: yes

16. Ansible Role

16.1 Roleの構造

roles/
└── nginx/
    ├── tasks/
    │   ├── main.yml         # メインタスク
    │   ├── install.yml      # インストールタスク
    │   └── configure.yml    # 設定タスク
    ├── handlers/
    │   └── main.yml         # ハンドラ
    ├── templates/
    │   └── nginx.conf.j2    # Jinja2 テンプレート
    ├── files/
    │   └── index.html       # 静的ファイル
    ├── vars/
    │   └── main.yml         # 変数 (優先度が高い)
    ├── defaults/
    │   └── main.yml         # デフォルト値 (優先度が低い)
    ├── meta/
    │   └── main.yml         # メタデータ、依存関係
    ├── tests/
    │   ├── inventory
    │   └── test.yml
    └── README.md

16.2 ansible-galaxy コマンド

# ── Role の管理 ──

# Role の初期化 (ディレクトリ構造の生成)
ansible-galaxy role init roles/nginx
ansible-galaxy role init --init-path=./roles nginx

# Galaxy から Role をインストール
ansible-galaxy role install geerlingguy.nginx
ansible-galaxy role install geerlingguy.docker -p roles/

# 特定バージョンのインストール
ansible-galaxy role install geerlingguy.nginx,3.1.0

# requirements.yml から一括インストール
ansible-galaxy role install -r requirements.yml

# インストール済み Role の一覧
ansible-galaxy role list

# Role の削除
ansible-galaxy role remove geerlingguy.nginx

# Role の検索
ansible-galaxy role search nginx --author geerlingguy

# Role の情報確認
ansible-galaxy role info geerlingguy.nginx

# ── Collection の管理 ──

# Collection のインストール
ansible-galaxy collection install amazon.aws
ansible-galaxy collection install community.general

# requirements.yml から一括インストール
ansible-galaxy collection install -r requirements.yml

# インストール済み Collection の一覧
ansible-galaxy collection list

# Collection のビルド
ansible-galaxy collection build

# Collection の公開
ansible-galaxy collection publish ./myns-mycoll-1.0.0.tar.gz

requirements.yml の例:

# requirements.yml
---
roles:
  - name: geerlingguy.nginx
    version: '3.1.0'
  - name: geerlingguy.docker
    version: '7.4.1'
  - name: geerlingguy.certbot
  - src: https://github.com/myorg/ansible-role-custom.git
    scm: git
    version: main
    name: custom_role

collections:
  - name: amazon.aws
    version: '>=8.0.0'
  - name: community.general
  - name: ansible.posix

16.3 Roleの使用例

# site.yml
---
- name: Configure web servers
  hosts: webservers
  become: yes

  roles:
    # 基本の使い方
    - nginx

    # 変数の受け渡し
    - role: nginx
      vars:
        nginx_worker_processes: 4
        nginx_worker_connections: 2048

    # 条件付き実行
    - role: certbot
      when: enable_ssl | default(false)

    # タグの指定
    - role: monitoring
      tags: [monitoring, observability]

17. Ansible Vault

Ansible Vaultは 機密データ(パスワード、APIキー、証明書など)を暗号化 する機能だ。

17.1 Vaultコマンド

# ── 暗号化ファイルの作成 ──
ansible-vault create secrets.yml
# エディタが開き、保存時に自動で暗号化

# 指定したエディタで作成
EDITOR=nano ansible-vault create secrets.yml

# ── 暗号化ファイルの編集 ──
ansible-vault edit secrets.yml

# ── 既存ファイルの暗号化 ──
ansible-vault encrypt vars/prod_secrets.yml

# 複数ファイルの同時暗号化
ansible-vault encrypt vars/secret1.yml vars/secret2.yml

# ── 暗号化ファイルの復号 ──
ansible-vault decrypt vars/prod_secrets.yml

# ── 暗号化ファイルの内容を表示 (復号せずに) ──
ansible-vault view secrets.yml

# ── パスワードの変更 ──
ansible-vault rekey secrets.yml

# ── 文字列の暗号化 (インライン) ──
ansible-vault encrypt_string 'SuperSecretPassword123!' --name 'db_password'
# 出力:
# db_password: !vault |
#   $ANSIBLE_VAULT;1.1;AES256
#   61636530396131653661313936353764...

ansible-vault encrypt_string --vault-password-file .vault_pass 'my_api_key_value' --name 'api_key'

# ── Vault パスワードの渡し方 ──
# 1. プロンプト
ansible-playbook site.yml --ask-vault-pass

# 2. ファイル
ansible-playbook site.yml --vault-password-file .vault_pass

# 3. 環境変数
export ANSIBLE_VAULT_PASSWORD_FILE=.vault_pass
ansible-playbook site.yml

# 4. スクリプト (パスワード管理ツールとの連携)
ansible-playbook site.yml --vault-password-file ./get_vault_pass.sh

# ── 複数の Vault ID を使う (環境ごとに異なるパスワード) ──
ansible-vault create --vault-id prod@prompt secrets_prod.yml
ansible-vault create --vault-id dev@.vault_pass_dev secrets_dev.yml

ansible-playbook site.yml \
  --vault-id prod@prompt \
  --vault-id dev@.vault_pass_dev

17.2 Vaultの使用例

# vars/secrets.yml (暗号化済み)
---
db_password: 'SuperSecretPassword123!'
api_key: 'sk-1234567890abcdef'
ssl_private_key: |
  -----BEGIN PRIVATE KEY-----
  MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...
  -----END PRIVATE KEY-----
# playbook での使用
- name: Deploy application
  hosts: webservers
  become: yes
  vars_files:
    - vars/defaults.yml
    - vars/secrets.yml # Vault で暗号化されたファイル

  tasks:
    - name: Configure database connection
      template:
        src: db_config.j2
        dest: /etc/app/database.yml
        mode: '0600'

18. Ansibleの高度な機能

18.1 ansible-doc — モジュールのドキュメント

# モジュールのヘルプを表示
ansible-doc apt
ansible-doc copy
ansible-doc template
ansible-doc amazon.aws.ec2_instance

# モジュール一覧の表示
ansible-doc --list
ansible-doc --list | grep aws

# 短いヘルプ (使用例)
ansible-doc -s apt
ansible-doc -s copy

# プラグイン種別ごとのドキュメント
ansible-doc -t callback -l          # Callback プラグイン一覧
ansible-doc -t connection -l        # Connection プラグイン一覧
ansible-doc -t inventory -l         # Inventory プラグイン一覧
ansible-doc -t lookup -l            # Lookup プラグイン一覧

18.2 ansible-navigator — TUIベースのツール

# TUI モードで Playbook を実行
ansible-navigator run site.yml -i inventory/hosts.yml

# stdout モード (従来の ansible-playbook に近い出力)
ansible-navigator run site.yml -i inventory/hosts.yml -m stdout

# インベントリの探索
ansible-navigator inventory -i inventory/hosts.yml

# モジュールドキュメントの探索
ansible-navigator doc apt

# Collection の探索
ansible-navigator collections

# 設定の確認
ansible-navigator config

18.3 ansible-lint — コード品質

# インストール
pip install ansible-lint

# Playbook のリント
ansible-lint site.yml

# ディレクトリ全体のリント
ansible-lint

# 特定ルールの無視
ansible-lint -x yaml[truthy]

# 自動修正
ansible-lint --fix

18.4 ansible.cfg の設定

# ansible.cfg
[defaults]
inventory = inventory/hosts.yml
remote_user = ubuntu
private_key_file = ~/.ssh/id_rsa
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
callback_whitelist = timer, profile_tasks
forks = 20
timeout = 30
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400

[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
control_path_dir = ~/.ansible/cp

Part 3: 統合、チートシート、トラブルシューティング


19. Terraform + Ansible 統合

19.1 統合アーキテクチャ

TerraformとAnsibleを連携させる最も一般的なパターンは次のとおりだ。

┌─────────────────────────────────────────────────────────────┐
Terraform + Ansible Integration├─────────────────────────────────────────────────────────────┤
│                                                              │
1. Terraformでインフラをプロビジョニング│     terraform apply                                          │
│         │                                                    │
│         ├── VPC, Subnet, SG 作成                             │
│         ├── EC2 インスタンス作成                              │
│         └── terraform output -json                           │
│                    │                                         │
│                    ▼                                         │
2. Terraform OutputAnsible Dynamic Inventory│     terraform output -json > tf_output.json│         │                                                    │
│         ▼                                                    │
3. AnsibleでConfiguration Management│     ansible-playbook -i dynamic_inventory.py site.yml│         │                                                    │
│         ├── パッケージ導入                                   │
│         ├── アプリケーション設定                              │
│         └── サービス起動                                     │
│                                                              │
└─────────────────────────────────────────────────────────────┘

19.2 Terraform OutputをAnsibleで活用する

# Terraform outputs.tf
output "web_server_ips" {
  value = aws_instance.web[*].public_ip
}

output "db_endpoint" {
  value     = aws_db_instance.main.endpoint
  sensitive = true
}

output "ssh_key_name" {
  value = aws_key_pair.deployer.key_name
}
#!/usr/bin/env python3
# dynamic_inventory.py — Terraform Output ベースの動的インベントリ
import json
import subprocess
import sys

def get_terraform_output():
    result = subprocess.run(
        ["terraform", "output", "-json"],
        capture_output=True, text=True
    )
    return json.loads(result.stdout)

def main():
    tf_output = get_terraform_output()
    web_ips = tf_output["web_server_ips"]["value"]

    inventory = {
        "webservers": {
            "hosts": web_ips,
            "vars": {
                "ansible_user": "ubuntu",
                "ansible_ssh_private_key_file": "~/.ssh/deployer.pem",
                "db_endpoint": tf_output["db_endpoint"]["value"]
            }
        },
        "_meta": {
            "hostvars": {}
        }
    }

    print(json.dumps(inventory, indent=2))

if __name__ == "__main__":
    main()
# 実行
chmod +x dynamic_inventory.py
ansible-playbook -i dynamic_inventory.py site.yml

19.3 Terraform local-exec ProvisionerでAnsibleを呼び出す

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.small"
  key_name      = aws_key_pair.deployer.key_name

  # Ansible の実行 (Provisioner は最後の手段としてのみ使う)
  provisioner "local-exec" {
    command = <<-EOT
      sleep 30  # SSH の準備待ち
      ANSIBLE_HOST_KEY_CHECKING=False \
      ansible-playbook \
        -i '${self.public_ip},' \
        -u ubuntu \
        --private-key ~/.ssh/deployer.pem \
        -e "db_endpoint=${aws_db_instance.main.endpoint}" \
        ansible/site.yml
    EOT
  }

  depends_on = [aws_db_instance.main]
}

19.4 Terraform + Ansible 自動化スクリプト

#!/bin/bash
# deploy.sh — インフラ全体 + 設定のデプロイ
set -euo pipefail

echo "=== Step 1: Terraform Init ==="
terraform init -input=false

echo "=== Step 2: Terraform Plan ==="
terraform plan -out=tfplan -input=false

echo "=== Step 3: Terraform Apply ==="
terraform apply tfplan

echo "=== Step 4: Generate Ansible Inventory ==="
terraform output -json > tf_output.json

echo "=== Step 5: Wait for instances to be ready ==="
WEB_IPS=$(terraform output -json web_server_ips | jq -r '.[]')
for ip in $WEB_IPS; do
  echo "Waiting for $ip..."
  until ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 ubuntu@"$ip" true 2>/dev/null; do
    sleep 5
  done
  echo "$ip is ready!"
done

echo "=== Step 6: Run Ansible Playbook ==="
ANSIBLE_HOST_KEY_CHECKING=False \
ansible-playbook \
  -i ./dynamic_inventory.py \
  --vault-password-file .vault_pass \
  site.yml

echo "=== Deployment Complete ==="
terraform output

20. Terraformコマンド チートシート Top 20

# ── 初期化と検証 ──
terraform init                           # 1. プロジェクトの初期化
terraform init -upgrade                  # 2. Provider/モジュールのアップグレード
terraform validate                       # 3. 構文検証
terraform fmt -recursive -check          # 4. フォーマットチェック

# ── 中核ワークフロー ──
terraform plan -out=tfplan               # 5. 実行計画の保存
terraform apply tfplan                   # 6. 計画の適用
terraform apply -auto-approve            # 7. 自動承認で適用 (CI/CD)
terraform destroy -auto-approve          # 8. 全体の削除

# ── State 管理 ──
terraform state list                     # 9. リソース一覧
terraform state show aws_instance.web    # 10. リソースの詳細
terraform state mv OLD NEW              # 11. リソースの移動/名前変更
terraform state rm RESOURCE             # 12. State から除去
terraform state pull > backup.tfstate    # 13. State のバックアップ
terraform force-unlock LOCK_ID          # 14. ロックの解除

# ── Import ──
terraform import RESOURCE ID            # 15. 既存リソースの取り込み
terraform plan -generate-config-out=g.tf # 16. Import コードの自動生成

# ── Workspace ──
terraform workspace new ENV             # 17. Workspace の作成
terraform workspace select ENV          # 18. Workspace の切り替え

# ── ユーティリティ ──
terraform output -json                   # 19. 出力値 (JSON)
terraform console                        # 20. 対話的コンソール

21. Ansibleコマンド チートシート Top 20

# ── 接続確認と情報収集 ──
ansible all -m ping                              # 1. 全ホストへ Ping
ansible all -m setup -a "filter=ansible_os*"     # 2. システム情報の収集

# ── アドホックコマンド ──
ansible web -m shell -a "uptime"                 # 3. シェルコマンドの実行
ansible web -m apt -a "name=nginx state=present" -b  # 4. パッケージのインストール
ansible web -m service -a "name=nginx state=started" -b # 5. サービスの起動
ansible web -m copy -a "src=f dest=/etc/app/" -b # 6. ファイルのコピー
ansible web -m user -a "name=deploy state=present" -b   # 7. ユーザーの作成

# ── Playbook の実行 ──
ansible-playbook site.yml                        # 8. Playbook の実行
ansible-playbook site.yml --check --diff         # 9. Dry-run + diff
ansible-playbook site.yml --limit web1           # 10. 特定ホストのみ
ansible-playbook site.yml --tags deploy          # 11. 特定タグのみ
ansible-playbook site.yml -e "env=prod"          # 12. 変数の受け渡し
ansible-playbook site.yml --syntax-check         # 13. 構文チェック

# ── Vault ──
ansible-vault create secrets.yml                 # 14. 暗号化ファイルの作成
ansible-vault edit secrets.yml                   # 15. 暗号化ファイルの編集
ansible-vault encrypt file.yml                   # 16. ファイルの暗号化
ansible-vault decrypt file.yml                   # 17. ファイルの復号
ansible-vault encrypt_string 'secret' --name key # 18. 文字列の暗号化

# ── Galaxy とユーティリティ ──
ansible-galaxy role install geerlingguy.nginx    # 19. Role のインストール
ansible-doc -s apt                               # 20. モジュールのヘルプ

22. トラブルシューティング

22.1 Terraformのトラブルシューティング

# ── 1. State Lock の問題 ──
# エラー: Error acquiring the state lock
# 原因: 直前の terraform apply が異常終了
# 解決:
terraform force-unlock LOCK_ID

# ── 2. Provider の認証失敗 ──
# エラー: NoCredentialProviders
# 解決: AWS 認証情報の確認
aws sts get-caller-identity
export AWS_PROFILE=myprofile

# ── 3. State と実インフラの不一致 ──
# 解決: State の再取得
terraform apply -refresh-only

# 特定リソースを State から除去して再 Import
terraform state rm aws_instance.web
terraform import aws_instance.web i-0abc123def

# ── 4. Provider のバージョン競合 ──
# 解決: ロックファイルの再生成
rm .terraform.lock.hcl
terraform init -upgrade

# ── 5. 循環依存 ──
# エラー: Cycle detected
# 解決: depends_on の確認、リソースの分離
terraform graph | dot -Tpng > graph.png  # 依存関係の可視化

# ── 6. デバッグログの有効化 ──
export TF_LOG=DEBUG        # TRACE, DEBUG, INFO, WARN, ERROR
export TF_LOG_PATH=terraform.log
terraform apply

# ── 7. Plan は成功するが Apply が失敗する ──
# 原因: API 権限の不足、リソース上限、ネットワークの問題
# 解決: -parallelism=1 で逐次実行し、正確なエラーを特定する
terraform apply -parallelism=1

# ── 8. 大規模 State の性能問題 ──
# 解決: State の分割 (複数の Terraform プロジェクトへ)
# network/ compute/ database/ などに分割し
# terraform_remote_state data source で連携する

22.2 Ansibleのトラブルシューティング

# ── 1. SSH 接続の失敗 ──
# エラー: UNREACHABLE!
# デバッグ:
ansible webservers -m ping -vvvv  # 最大の詳細ログ

# SSH を直接テスト
ssh -i ~/.ssh/key.pem -o StrictHostKeyChecking=no ubuntu@10.0.1.10

# ── 2. sudo 権限の問題 ──
# エラー: Missing sudo password
# 解決:
ansible-playbook site.yml --ask-become-pass
# または ansible.cfg に設定:
# [privilege_escalation]
# become_ask_pass = True

# ── 3. モジュールが見つからない ──
# エラー: MODULE FAILURE
# 解決: Collection のインストール
ansible-galaxy collection install amazon.aws

# ── 4. Jinja2 テンプレートのエラー ──
# エラー: AnsibleUndefinedVariable
# 解決: 変数定義の確認
ansible-playbook site.yml -e "@vars/defaults.yml" --check
# または default フィルタを使う: "{{ my_var | default('fallback') }}"

# ── 5. パフォーマンスの最適化 ──
# ansible.cfg の設定:
# [defaults]
# forks = 20                    # 並列実行数を増やす
# gathering = smart             # ファクトのキャッシュ
# [ssh_connection]
# pipelining = True             # SSH パイプライニングの有効化
# ssh_args = -o ControlMaster=auto -o ControlPersist=60s

# ── 6. Vault パスワードの紛失 ──
# 解決: 復旧不可。新しいパスワードで再暗号化する必要がある
# パスワードを覚えているうちに:
ansible-vault rekey secrets.yml

# ── 7. インベントリのパースエラー ──
# デバッグ: インベントリの妥当性検査
ansible-inventory -i inventory/hosts.yml --list --export

# ── 8. 冪等性の崩れ (changed が繰り返される) ──
# command/shell モジュールに creates/removes 条件を追加する
- name: Initialize database
  command: /opt/app/init_db.sh
  args:
    creates: /opt/app/.db_initialized

23. ベストプラクティスのまとめ

23.1 Terraformのベストプラクティス

項目推奨事項
State 管理Remote Backend(S3 + DynamoDB)を使い、絶対に Git にコミットしない
ディレクトリ構造環境ごとに分離 (envs/dev, envs/prod) または Workspace を使う
コードレビューterraform plan の出力を PR に添付する
モジュール化再利用可能なモジュールに分離し、バージョンをタグ付けする
変数管理機密変数は sensitive = true、環境変数または Vault を活用する
ロックファイル.terraform.lock.hcl は必ず Git にコミットする
CI/CDPlan は PR で、Apply は merge 後に自動実行する
命名規則project-env-resource パターンを一貫して使う

23.2 Ansibleのベストプラクティス

項目推奨事項
インベントリクラウド環境では動的インベントリを使う
機密データAnsible Vault で必ず暗号化する
Role の活用タスクを Role で構造化し、Galaxy Role を積極的に活用する
冪等性command/shell ではなく専用モジュールを使い、creates/removes を活用する
テスト--check --diff で事前検証し、Molecule で Role をテストする
変数の優先順位変数の優先順位を理解し、適切な場所に変数を定義する
タグの活用すべてのタスクにタグを付けて選択的に実行できるようにする
ログcallback_whitelist = timer, profile_tasks で性能を計測する

24. 参考資料

24.1 公式ドキュメント

24.2 おすすめの学習リソース

24.3 関連する認定資格

24.4 コミュニティ


TerraformとAnsibleは現代のインフラ自動化を支える二本の柱だ。Terraformでインフラを宣言的にプロビジョニングし、Ansibleでその上にソフトウェアを構成するパターンは、業界の事実上の標準として定着した。本記事で扱ったコマンドとパターンを実務に適用しながら、繰り返しの手作業をコードに置き換え、インフラを安全で予測可能に管理する文化をチームに根づかせてほしい。

コメント

まだコメントはありません。

ログインするとコメントできます