- 1. 序論:Infrastructure as Codeの時代
- Part 1: Terraform完全ガイド
- Part 2: Ansible完全ガイド
- Part 3: 統合、チートシート、トラブルシューティング
1. 序論:Infrastructure as Codeの時代
1.1 なぜIaCとConfiguration Managementなのか
クラウドネイティブ時代にインフラを手作業で管理することは、もはや選択肢ではない。数百台のサーバー、数十のVPC、複雑に絡み合ったセキュリティグループとIAMポリシーをコンソールのクリックで管理するなら、それは 「Snowflake Server」 — 雪の結晶のようにすべてが異なるサーバー — を作る近道だ。再現できず、監査証跡を追いにくく、一度のミスがインフラ全体を崩壊させかねない。
Infrastructure as Code(IaC)とConfiguration Management(CM)は、この問題に対する業界の答えだ。
- IaC(Infrastructure as Code): インフラ自体をコードで宣言し、バージョン管理し、レビューし、自動でプロビジョニングする。代表的なツール: Terraform、Pulumi、AWS CloudFormation、OpenTofu
- CM(Configuration Management): プロビジョニングされたインフラの上にソフトウェアをインストールし、設定を統一し、状態を維持する。代表的なツール: Ansible、Chef、Puppet、SaltStack
この二つの領域の事実上の標準(de facto standard)が、まさに Terraform と Ansible だ。
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 + Declarative │
│ State ベース Agentless (SSH/WinRM) │
└──────────────────────────────────────────────────────────────────┘
1.3 本記事の構成
本記事は大きく三つのパートで構成される。
- Part 1 — Terraform: HCL文法から中核ワークフロー、State管理、Workspace、モジュール、高度な機能まで
- Part 2 — Ansible: インベントリ、アドホックコマンド、Playbook、Role、Vault、Galaxyまで
- 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 Files │ HCL 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.) │ │
│ └──────────────┘ └─────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
- Terraform Core: HCLのパース、依存関係グラフの構築、Plan/Applyエンジン
- Providers: 各クラウド/サービスのAPIを抽象化するプラグイン (AWS、Azure、GCP、Kubernetesなど3,000以上)
- State: 現在のインフラの状態を追跡するJSONファイル
2.3 Terraform vs OpenTofu vs Pulumi
| 項目 | Terraform | OpenTofu | Pulumi |
|---|---|---|---|
| ライセンス | BSL 1.1 | MPL 2.0 (OSS) | Apache 2.0 |
| 言語 | HCL | HCL | Python/Go/TS/C# |
| 状態管理 | terraform.tfstate | terraform.tfstate | Pulumi Cloud |
| Providerエコシステム | 3,000+ | Terraform互換 | 100+ |
| 運営主体 | HashiCorp(IBM) | Linux Foundation | Pulumi Inc. |
| CLIコマンド | terraform | tofu | pulumi |
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の中核となる哲学:
- Agentless: 対象サーバーにエージェントをインストールする必要がない (SSH/WinRMベース)
- Idempotent: 同じPlaybookを何度実行しても結果が同じになる
- YAMLベース: プログラミング言語ではなくYAMLで記述するため、参入障壁が低い
- Pushモデル: Control NodeからManaged Nodeへ作業を押し出す
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 │ │
│ └────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
- Control Node: Ansibleがインストールされ実行されるノード (Linux/macOS、WindowsはWSLが必要)
- Managed Node: Ansibleが管理する対象サーバー (SSH接続できればよい)
- Inventory: 管理対象ホストの一覧
- Module: 実際の作業を行うコードの単位 (2,500以上の組み込みモジュール)
- Playbook: 作業順序を定義したYAMLファイル
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 Output → Ansible 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/CD | Plan は 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 公式ドキュメント
- Terraform: https://developer.hashicorp.com/terraform/docs
- Terraform Registry: https://registry.terraform.io
- OpenTofu: https://opentofu.org/docs
- Ansible: https://docs.ansible.com
- Ansible Galaxy: https://galaxy.ansible.com
24.2 おすすめの学習リソース
- Terraform Up and Running (Yevgeniy Brikman) — Terraformのバイブル
- Ansible for DevOps (Jeff Geerling) — Ansibleの実践書
- HashiCorp Learn: https://developer.hashicorp.com/terraform/tutorials
- Jeff Geerling YouTube: Ansibleの実習動画が多数
24.3 関連する認定資格
- HashiCorp Certified: Terraform Associate (003): Terraformの基礎認定
- Red Hat Certified Specialist in Ansible Automation (EX374): Ansibleの専門家認定
24.4 コミュニティ
- Terraform GitHub: https://github.com/hashicorp/terraform
- Ansible GitHub: https://github.com/ansible/ansible
- OpenTofu GitHub: https://github.com/opentofu/opentofu
- r/Terraform: Redditコミュニティ
- r/ansible: Redditコミュニティ
TerraformとAnsibleは現代のインフラ自動化を支える二本の柱だ。Terraformでインフラを宣言的にプロビジョニングし、Ansibleでその上にソフトウェアを構成するパターンは、業界の事実上の標準として定着した。本記事で扱ったコマンドとパターンを実務に適用しながら、繰り返しの手作業をコードに置き換え、インフラを安全で予測可能に管理する文化をチームに根づかせてほしい。