Ansible と Terraform/Puppet/Chef 比較:宣言/手続の違いと併用戦略

インフラ管理ツールの選択で迷われていませんか。Ansible、Terraform、Puppet、Chef それぞれに独自の特徴があり、どれを使うべきか判断に悩むことも多いでしょう。
特に重要なのが「宣言型」と「手続型」という根本的な違いです。この違いを理解することで、各ツールの強みを活かした効果的な組み合わせが可能になります。本記事では、これらのツールを徹底比較し、実際の併用戦略まで詳しく解説していきます。
宣言型と手続型の基本概念
インフラ管理ツールを理解する上で最も重要な概念が「宣言型」と「手続型」の違いです。これらの違いを把握することで、各ツールの特性や適用場面が明確になります。
宣言型アプローチの特徴
宣言型アプローチでは、「どのような状態にしたいか」を記述します。つまり、最終的な目標状態を定義し、その状態に到達するための具体的な手順はツール側が自動的に決定します。
この概念を図で表すと以下のようになります:
mermaidflowchart LR
current["現在の状態"] -->|自動判定| tool["管理ツール"]
desired["望ましい状態<br/>(宣言)"] --> tool
tool -->|最適な手順を決定| target["目標状態"]
宣言型の主な特徴は以下の通りです:
- 冪等性:何度実行しても同じ結果になる
- 自動判定:現在の状態と目標状態の差分を自動検出
- 抽象化:具体的な手順を意識せずに済む
手続型アプローチの特徴
一方、手続型アプローチでは「どのような手順で実行するか」を記述します。実行すべき処理の順序やロジックを明示的に定義し、その通りに実行されます。
手続型のワークフローを図示すると:
mermaidflowchart TD
start["開始"] --> step1["ステップ1"]
step1 --> step2["ステップ2"]
step2 --> step3["ステップ3"]
step3 --> finish["完了"]
step1 -.->|条件分岐| alt["代替処理"]
alt --> step3
手続型の主な特徴は以下の通りです:
- 明示的制御:実行順序を詳細に制御可能
- 柔軟性:複雑な条件分岐やループに対応
- 透明性:何が実行されるかが明確
これらの違いが、各ツールの設計思想や使用感に大きな影響を与えています。
各ツールのアプローチ詳解
それぞれのツールがどのようなアプローチを採用しているか、具体的に見ていきましょう。実装例も交えながら、各ツールの特徴を詳しく解説します。
Ansible:手続型アプローチの特徴
Ansible は手続型アプローチを採用しており、タスクを順次実行する形式でインフラを管理します。プレイブックと呼ばれる設定ファイルで、実行すべき処理を順序立てて記述します。
Ansible の基本構造
以下は Ansible プレイブックの基本的な例です:
yaml---
- name: Webサーバーのセットアップ
hosts: webservers
become: yes
tasks:
- name: Nginxのインストール
package:
name: nginx
state: present
- name: 設定ファイルのコピー
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: nginx restart
- name: Nginxサービスの開始
service:
name: nginx
state: started
enabled: yes
この例では、以下の手順が順次実行されます:
- Nginx パッケージのインストール
- 設定ファイルのコピー
- Nginx サービスの開始と自動起動設定
Ansible の手続型特徴
実行順序の制御
Ansible では、タスクの実行順序が重要な意味を持ちます。依存関係のあるタスクは適切な順序で配置する必要があります。
yamltasks:
- name: データベースの作成
mysql_db:
name: myapp
state: present
- name: ユーザーの作成(データベース作成後)
mysql_user:
name: appuser
password: '{{ db_password }}'
priv: 'myapp.*:ALL'
state: present
条件分岐とループ
複雑な条件分岐やループ処理も手続型の強みです:
yaml- name: OS別パッケージインストール
package:
name: '{{ item }}'
state: present
loop:
- "{{ 'httpd' if ansible_os_family == 'RedHat' else 'apache2' }}"
- "{{ 'mysql-server' if ansible_os_family == 'RedHat' else 'mysql-server' }}"
when: install_web_stack | bool
エラーハンドリング
手続型では、エラー処理も明示的に記述できます:
yaml- name: アプリケーションの配備
copy:
src: app.jar
dest: /opt/myapp/
register: deploy_result
failed_when: false
- name: 配備失敗時のロールバック
command: /opt/myapp/rollback.sh
when: deploy_result.failed
Terraform:宣言型アプローチの特徴
Terraform は宣言型アプローチの代表的なツールで、インフラの最終的な状態を記述することに特化しています。HCL(HashiCorp Configuration Language)を使用して、リソースの望ましい状態を定義します。
Terraform の基本構造
以下は Terraform の基本的な設定例です:
hcl# プロバイダーの設定
provider "aws" {
region = "ap-northeast-1"
}
# VPCの定義
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
}
}
# サブネットの定義
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = true
tags = {
Name = "public-subnet"
}
}
この設定では、以下のリソースの最終状態を宣言しています:
- VPC が存在し、指定の CIDR ブロックを持つ
- サブネットが存在し、VPC に関連付けられている
- 適切なタグが設定されている
Terraform の宣言型特徴
状態管理の自動化
Terraform は現在の状態を自動的に追跡し、差分を検出します:
bash# 現在の状態と設定の差分を確認
terraform plan
# 差分を適用して目標状態に到達
terraform apply
依存関係の自動解決
リソース間の依存関係は暗黙的に解決されます:
hclresource "aws_security_group" "web" {
name_prefix = "web-"
vpc_id = aws_vpc.main.id # VPCへの暗黙的依存
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id # サブネットへの暗黙的依存
vpc_security_group_ids = [aws_security_group.web.id] # セキュリティグループへの暗黙的依存
}
冪等性の保証
同じ設定を何度実行しても、結果は変わりません:
hcl# この設定を何度実行しても、1つのS3バケットのみ存在
resource "aws_s3_bucket" "example" {
bucket = "my-unique-bucket-name"
}
resource "aws_s3_bucket_versioning" "example" {
bucket = aws_s3_bucket.example.id
versioning_configuration {
status = "Enabled"
}
}
Puppet:宣言型アプローチの特徴
Puppet も宣言型アプローチを採用しており、システムの望ましい状態をマニフェストと呼ばれるファイルで定義します。Puppet 独自の DSL(Domain Specific Language)を使用します。
Puppet の基本構造
以下は Puppet マニフェストの例です:
puppet# Webサーバーの設定
class webserver {
# パッケージの管理
package { 'apache2':
ensure => installed,
}
# サービスの管理
service { 'apache2':
ensure => running,
enable => true,
require => Package['apache2'],
}
# 設定ファイルの管理
file { '/etc/apache2/sites-available/default':
ensure => file,
content => template('webserver/default.erb'),
notify => Service['apache2'],
require => Package['apache2'],
}
}
# クラスの適用
include webserver
Puppet の宣言型特徴
リソースの抽象化
Puppet では、OS の違いを抽象化してリソースを管理できます:
puppet# OS に関係なく、適切なパッケージマネージャーを使用
package { 'git':
ensure => installed,
}
# OS に応じてサービス名を自動選択
service { $apache_service_name:
ensure => running,
enable => true,
}
依存関係の明示
リソース間の依存関係を明示的に定義できます:
puppetfile { '/var/www/html/index.html':
ensure => file,
content => 'Hello World',
require => Package['apache2'], # Apacheインストール後に実行
notify => Service['apache2'], # ファイル変更時にサービス再起動
}
設定のテンプレート化
ERB テンプレートを使用して、動的な設定を生成できます:
puppetfile { '/etc/mysql/my.cnf':
ensure => file,
content => template('mysql/my.cnf.erb'),
notify => Service['mysql'],
}
Chef:手続型アプローチの特徴
Chef は Ruby ベースの手続型ツールで、レシピと呼ばれる Ruby スクリプトでインフラの設定手順を記述します。プログラミング言語の柔軟性を活かした複雑な処理が可能です。
Chef の基本構造
以下は Chef レシピの例です:
ruby# パッケージのインストール
package 'nginx' do
action :install
end
# 設定ファイルのテンプレート生成
template '/etc/nginx/nginx.conf' do
source 'nginx.conf.erb'
variables({
worker_processes: node['cpu']['total'],
worker_connections: 1024
})
notifies :restart, 'service[nginx]', :delayed
end
# サービスの管理
service 'nginx' do
action [:enable, :start]
supports restart: true, reload: true
end
Chef の手続型特徴
Ruby の活用
Chef では Ruby の機能をフルに活用できます:
ruby# 動的な設定生成
servers = search(:node, 'role:webserver')
servers.each do |server|
template "/etc/nginx/conf.d/#{server.name}.conf" do
source 'vhost.conf.erb'
variables server: server
end
end
# 条件分岐
if node['platform_family'] == 'debian'
package 'apache2'
elsif node['platform_family'] == 'rhel'
package 'httpd'
end
カスタムリソースの作成
独自のリソースタイプを定義できます:
ruby# カスタムリソースの定義
provides :my_application
property :app_name, String, name_property: true
property :version, String, required: true
property :port, Integer, default: 8080
action :deploy do
# アプリケーション配備のロジック
remote_file "/opt/#{new_resource.app_name}" do
source "https://releases.example.com/#{new_resource.version}.tar.gz"
end
# サービス設定
systemd_unit "#{new_resource.app_name}.service" do
content({
Unit: { Description: "My Application #{new_resource.app_name}" },
Service: {
ExecStart: "/opt/#{new_resource.app_name}/bin/start",
Environment: "PORT=#{new_resource.port}"
}
})
end
end
データバッグの活用
外部データソースとの連携も容易です:
ruby# データバッグから設定情報を取得
db_config = data_bag_item('configs', 'database')
template '/etc/myapp/database.yml' do
source 'database.yml.erb'
variables({
host: db_config['host'],
username: db_config['username'],
password: db_config['password']
})
end
宣言型 vs 手続型の比較分析
各アプローチの特徴を理解したところで、実際の運用における違いを詳しく比較してみましょう。どちらを選ぶべきかの判断材料として、具体的な場面での違いを解説します。
設定管理における違い
設定管理の手法は、宣言型と手続型で大きく異なります。この違いを理解することで、プロジェクトに適したアプローチを選択できます。
設定記述の考え方
以下の図は、同じ Web サーバー構築を両アプローチで実現する際の思考プロセスの違いを示しています:
mermaidflowchart TD
subgraph declarative["宣言型アプローチ"]
d1["Webサーバーが稼働している状態"] --> d2["必要なパッケージが存在"]
d2 --> d3["設定ファイルが適切"]
d3 --> d4["サービスが起動中"]
end
subgraph procedural["手続型アプローチ"]
p1["1. パッケージをインストール"] --> p2["2. 設定ファイルを配置"]
p2 --> p3["3. サービスを起動"]
p3 --> p4["4. 動作確認"]
end
宣言型での設定例(Terraform)
hcl# 望ましい状態を記述
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t3.micro"
# セキュリティグループが適用されている状態
vpc_security_group_ids = [aws_security_group.web.id]
# タグが設定されている状態
tags = {
Name = "WebServer"
Environment = "Production"
}
}
# セキュリティグループが存在し、適切なルールを持つ状態
resource "aws_security_group" "web" {
name = "web-sg"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
手続型での設定例(Ansible)
yaml# 実行手順を記述
- name: Webサーバーセットアップ手順
hosts: webservers
tasks:
- name: Step1:Nginxをインストール
package:
name: nginx
state: present
- name: Step2:設定ファイルを配置
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: Step3:サービスを開始
service:
name: nginx
state: started
enabled: yes
- name: Step4:動作確認
uri:
url: 'http://{{ ansible_default_ipv4.address }}'
method: GET
register: health_check
設定変更時の対応
宣言型の場合
設定変更は定義の更新のみで完了します:
hcl# 変更前
resource "aws_instance" "web" {
instance_type = "t3.micro"
}
# 変更後(インスタンスタイプを変更)
resource "aws_instance" "web" {
instance_type = "t3.small" # この行のみ変更
}
Terraform が自動的に差分を検出し、必要な変更操作を実行します。
手続型の場合
変更手順を明示的に記述する必要があります:
yaml- name: インスタンスタイプ変更手順
hosts: webservers
tasks:
- name: サービス停止
service:
name: nginx
state: stopped
- name: インスタンス停止
aws_ec2:
instance_ids: '{{ instance_id }}'
state: stopped
- name: インスタンスタイプ変更
aws_ec2:
instance_ids: '{{ instance_id }}'
instance_type: t3.small
- name: インスタンス起動
aws_ec2:
instance_ids: '{{ instance_id }}'
state: running
- name: サービス開始
service:
name: nginx
state: started
実行プロセスの違い
実行プロセスの違いは、運用時の作業効率やトラブルシューティングに大きな影響を与えます。
実行前の確認プロセス
宣言型の確認プロセス
bash# Terraform での確認
terraform plan
# Output:
# Plan: 1 to add, 0 to change, 0 to destroy
#
# + aws_instance.web
# + ami = "ami-12345678"
# + instance_type = "t3.micro"
宣言型では、現在の状態と目標状態の差分を自動計算し、実行予定の変更を事前に確認できます。
手続型の確認プロセス
yaml# Ansible での確認(ドライラン)
ansible-playbook site.yml --check --diff
# タスクごとに実行予定の変更が表示される
# TASK [nginx : Install Nginx]
# changed: [server1] => (item=nginx)
#
# TASK [nginx : Configure Nginx]
# --- before: /etc/nginx/nginx.conf
# +++ after: /tmp/nginx.conf
手続型では、各タスクの実行結果を順次確認していく形になります。
エラー発生時の対応
実行中にエラーが発生した場合の対応も、アプローチによって大きく異なります:
mermaidsequenceDiagram
participant User as 管理者
participant Tool as ツール
participant Infra as インフラ
Note over User,Infra: 宣言型の場合
User->>Tool: terraform apply
Tool->>Infra: 状態確認
Infra-->>Tool: 現在の状態
Tool->>Infra: 差分適用
Note over Tool,Infra: エラー発生
Tool-->>User: エラー報告(ロールバック自動)
Note over User,Infra: 手続型の場合
User->>Tool: ansible-playbook
Tool->>Infra: タスク1実行
Tool->>Infra: タスク2実行
Note over Tool,Infra: エラー発生
Tool-->>User: エラー報告(手動対応必要)
宣言型でのエラー処理例
bash# Terraform実行時エラー
terraform apply
# Error: Error creating instance: InvalidAMI.NotFound
#
# 自動的に他の変更もロールバックされる
手続型でのエラー処理例
yaml# Ansibleでのエラーハンドリング
- name: アプリケーション更新
copy:
src: app-v2.jar
dest: /opt/app/
register: copy_result
- name: 更新失敗時のロールバック
copy:
src: app-v1.jar
dest: /opt/app/
when: copy_result.failed
- name: サービス再起動
service:
name: myapp
state: restarted
when: not copy_result.failed
メンテナンス性の違い
長期的な運用を考える上で、メンテナンス性は重要な要素です。チーム体制や技術的負債の観点から比較してみましょう。
コードの可読性と保守性
宣言型の保守性
宣言型では、設定の意図が明確で、変更影響範囲が把握しやすいという特徴があります:
hcl# 意図が明確な設定
variable "environment" {
description = "Environment name (dev/staging/prod)"
type = string
default = "dev"
}
# 環境ごとの設定差分が明確
resource "aws_instance" "web" {
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
tags = {
Environment = var.environment
Purpose = "web-server"
}
}
設定の変更時には、差分が自動的に計算されるため、影響範囲を事前に把握できます。
手続型の保守性
手続型では、処理の詳細が明確で、カスタマイズの自由度が高いという特徴があります:
yaml# 処理手順が明確
- name: 環境別Webサーバー構築
hosts: '{{ target_env }}_servers'
vars:
instance_type: "{{ 't3.large' if target_env == 'prod' else 't3.micro' }}"
tasks:
- name: '{{ target_env }}環境用設定ファイル配置'
template:
src: 'nginx-{{ target_env }}.conf.j2'
dest: /etc/nginx/nginx.conf
when: configure_nginx | default(true)
- name: カスタム処理(本番環境のみ)
script: prod-specific-setup.sh
when: target_env == "prod"
チーム運用での違い
チーム開発における運用面の違いを表にまとめました:
観点 | 宣言型 | 手続型 |
---|---|---|
学習コスト | 専用 DSL の習得が必要 | 一般的なプログラミング知識で対応可能 |
レビュー観点 | 最終状態の妥当性確認 | 処理手順の妥当性確認 |
並行開発 | 状態定義の競合リスク有 | タスク単位での分担が容易 |
デバッグ | 状態差分の確認が中心 | ステップ実行での詳細確認 |
テスト | 結果状態のテストが中心 | 処理過程のテストも可能 |
技術的負債の蓄積パターン
宣言型での技術的負債
hcl# 悪い例:設定の重複と不整合
resource "aws_instance" "web1" {
ami = "ami-12345678" # 古いAMI
instance_type = "t2.micro" # 旧世代
}
resource "aws_instance" "web2" {
ami = "ami-87654321" # 新しいAMI
instance_type = "t3.micro" # 新世代
}
# 改善例:共通設定の抽象化
locals {
common_config = {
ami = "ami-87654321"
instance_type = "t3.micro"
}
}
resource "aws_instance" "web" {
count = 2
ami = local.common_config.ami
instance_type = local.common_config.instance_type
}
手続型での技術的負債
yaml# 悪い例:手順の重複と保守性の悪化
- name: サーバー1のセットアップ
hosts: server1
tasks:
- name: Nginxインストール
package: name=nginx state=present
- name: 設定ファイル配置
copy: src=nginx.conf dest=/etc/nginx/
- name: サービス起動
service: name=nginx state=started
- name: サーバー2のセットアップ
hosts: server2
tasks:
- name: Nginxインストール # 重複
package: name=nginx state=present
- name: 設定ファイル配置 # 重複
copy: src=nginx.conf dest=/etc/nginx/
- name: サービス起動 # 重複
service: name=nginx state=started
# 改善例:ロール化による共通化
- name: Webサーバーセットアップ
hosts: webservers
roles:
- nginx
併用戦略とベストプラクティス
実際のプロジェクトでは、単一のツールですべてを解決するよりも、各ツールの強みを活かした併用が効果的です。特に Ansible と Terraform の組み合わせは、多くの組織で採用されている実績ある戦略です。
Ansible + Terraform の組み合わせ
Ansible と Terraform の併用は、インフラ管理における最も一般的なパターンの一つです。それぞれの役割を明確に分離することで、効率的な運用が可能になります。
役割分担の基本方針
以下の図は、Ansible と Terraform の典型的な役割分担を示しています:
mermaidflowchart TD
subgraph terraform["Terraform 担当領域"]
infra["インフラリソース"]
infra --> compute["コンピュートリソース<br/>EC2, ECS, Lambda"]
infra --> network["ネットワークリソース<br/>VPC, Subnet, Route"]
infra --> storage["ストレージリソース<br/>S3, EBS, RDS"]
infra --> security["セキュリティリソース<br/>IAM, Security Group"]
end
subgraph ansible["Ansible 担当領域"]
config["設定管理"]
config --> os["OS設定<br/>パッケージ, サービス"]
config --> app["アプリケーション<br/>配備, 設定"]
config --> monitor["監視設定<br/>ログ, メトリクス"]
config --> maintenance["メンテナンス<br/>更新, バックアップ"]
end
terraform -->|インフラ情報| ansible
Terraform の責任範囲
- クラウドリソースの作成・管理
- ネットワーク構成の定義
- セキュリティポリシーの設定
- リソース間の依存関係管理
Ansible の責任範囲
- OS レベルの設定
- アプリケーションの配備
- サービス設定の管理
- 運用タスクの自動化
実装例:Web アプリケーション環境の構築
以下は、Terraform で AWS インフラを作成し、Ansible で Web アプリケーションを配備する例です。
Step1: Terraform でインフラ作成
hcl# main.tf
provider "aws" {
region = "ap-northeast-1"
}
# VPC とネットワーク
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = { Name = "webapp-vpc" }
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = true
tags = { Name = "public-subnet" }
}
# セキュリティグループ
resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
EC2 インスタンスの作成
hcl# EC2インスタンス
resource "aws_instance" "web" {
ami = "ami-0c3fd0f5d33134a76"
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
key_name = "my-keypair"
tags = {
Name = "web-server"
Role = "webserver"
}
}
# Ansible用のインベントリ情報を出力
resource "local_file" "ansible_inventory" {
content = templatefile("${path.module}/inventory.tpl", {
web_ip = aws_instance.web.public_ip
})
filename = "../ansible/inventory/hosts"
}
インベントリテンプレート
ini# inventory.tpl
[webservers]
${web_ip} ansible_user=ec2-user ansible_ssh_private_key_file=~/.ssh/my-keypair.pem
[webservers:vars]
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
Step2: Ansible でアプリケーション配備
yaml# site.yml
---
- name: Webアプリケーション配備
hosts: webservers
become: yes
vars:
app_name: mywebapp
app_port: 3000
nginx_port: 80
tasks:
- name: 必要パッケージのインストール
yum:
name:
- nginx
- nodejs
- npm
- git
state: present
- name: アプリケーション用ユーザー作成
user:
name: '{{ app_name }}'
system: yes
home: '/opt/{{ app_name }}'
create_home: yes
- name: アプリケーションコードのクローン
git:
repo: 'https://github.com/example/mywebapp.git'
dest: '/opt/{{ app_name }}/src'
version: main
become_user: '{{ app_name }}'
notify: restart app
アプリケーション設定とサービス起動
yaml- name: NPM依存関係のインストール
npm:
path: '/opt/{{ app_name }}/src'
become_user: '{{ app_name }}'
- name: アプリケーション設定ファイル配置
template:
src: app.env.j2
dest: '/opt/{{ app_name }}/.env'
owner: '{{ app_name }}'
group: '{{ app_name }}'
mode: '0600'
notify: restart app
- name: Systemdサービスファイル作成
template:
src: mywebapp.service.j2
dest: '/etc/systemd/system/{{ app_name }}.service'
notify:
- reload systemd
- restart app
- name: Nginx設定ファイル配置
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: サービス起動と自動起動設定
service:
name: '{{ item }}'
state: started
enabled: yes
loop:
- '{{ app_name }}'
- nginx
ハンドラーの定義
yamlhandlers:
- name: reload systemd
systemd:
daemon_reload: yes
- name: restart app
service:
name: '{{ app_name }}'
state: restarted
- name: restart nginx
service:
name: nginx
state: restarted
データ連携のベストプラクティス
Terraform と Ansible 間でのデータ連携には、いくつかの効果的な方法があります。
方法 1: Terraform Output と Local File
hcl# outputs.tf
output "web_server_info" {
value = {
public_ip = aws_instance.web.public_ip
private_ip = aws_instance.web.private_ip
instance_id = aws_instance.web.id
}
}
# インベントリファイル自動生成
resource "local_file" "ansible_vars" {
content = yamlencode({
instance_id = aws_instance.web.id
public_ip = aws_instance.web.public_ip
private_ip = aws_instance.web.private_ip
vpc_id = aws_vpc.main.id
})
filename = "../ansible/group_vars/webservers.yml"
}
方法 2: 外部データソースの活用
yaml# Ansible側でTerraform stateを参照
- name: Terraform状態からインスタンス情報取得
shell: |
cd ../terraform && terraform output -json web_server_info
register: tf_output
delegate_to: localhost
- name: 出力値の解析
set_fact:
instance_info: '{{ (tf_output.stdout | from_json) }}'
- name: インスタンス情報を使用した処理
debug:
msg: 'Instance {{ instance_info.instance_id }} has IP {{ instance_info.public_ip }}'
適材適所の使い分け指針
各ツールの特性を理解した上で、具体的にどのような場面でどのツールを選択すべきかのガイドラインを提示します。
シナリオ別推奨ツール
以下の表は、一般的なシナリオにおける推奨ツールを示しています:
シナリオ | 推奨ツール | 理由 |
---|---|---|
クラウドインフラ構築 | Terraform | 宣言型でリソース間依存関係を自動解決 |
マルチクラウド対応 | Terraform | プロバイダーが豊富で統一的な管理が可能 |
OS 設定・パッケージ管理 | Ansible/Puppet | OS レベルの詳細な制御が必要 |
アプリケーション配備 | Ansible/Chef | 複雑な配備手順に対応可能 |
設定ファイル管理 | Puppet | 設定の一元管理と監視が強力 |
一時的なタスク実行 | Ansible | エージェントレスで即座に実行可能 |
大規模な設定管理 | Puppet/Chef | スケーラビリティとレポート機能が充実 |
技術的制約による選択
ネットワーク制約がある環境
yaml# エージェントレスのAnsibleが有効
- name: 閉域網内サーバー管理
hosts: isolated_servers
connection: ssh
vars:
ansible_ssh_common_args: '-o ProxyCommand="ssh -W %h:%p bastion-host"'
tasks:
- name: ベースラインセキュリティ設定
include_role:
name: security_baseline
Windows 環境の管理
yaml# Windows環境ではAnsibleのWinRM接続を活用
- name: Windows Server管理
hosts: windows_servers
vars:
ansible_connection: winrm
ansible_winrm_transport: kerberos
tasks:
- name: IIS役割のインストール
win_feature:
name: IIS-WebServerRole
state: present
- name: アプリケーションプール設定
win_iis_webapppool:
name: MyAppPool
state: present
attributes:
processModel.identityType: ApplicationPoolIdentity
レガシーシステムとの共存
puppet# Puppetでレガシーシステムとの段階的移行
class legacy_migration {
# 既存システムとの互換性を保持
if $facts['legacy_app_installed'] {
service { 'legacy-app':
ensure => stopped,
enable => false,
}
# 新システムへの移行処理
exec { 'migrate-data':
command => '/opt/migration/migrate.sh',
creates => '/var/lib/migration.complete',
require => Service['legacy-app'],
}
}
# 新システムの配備
package { 'new-app':
ensure => installed,
require => Exec['migrate-data'],
}
}
実践的な併用パターン
実際のプロジェクトで使われている効果的な併用パターンを、具体的な実装例とともに紹介します。
パターン 1: フェーズ分離型
開発フェーズごとに異なるツールを使い分けるパターンです:
mermaidflowchart LR
subgraph phase1["Phase 1: インフラ構築"]
terraform["Terraform<br/>・VPC/Subnet<br/>・EC2/RDS<br/>・Load Balancer"]
end
subgraph phase2["Phase 2: 基盤設定"]
ansible1["Ansible<br/>・OS設定<br/>・監視エージェント<br/>・セキュリティ設定"]
end
subgraph phase3["Phase 3: アプリ配備"]
ansible2["Ansible<br/>・アプリケーション<br/>・設定ファイル<br/>・サービス起動"]
end
phase1 --> phase2
phase2 --> phase3
実装例: CI/CD パイプライン
yaml# .github/workflows/deploy.yml
name: Infrastructure and Application Deployment
on:
push:
branches: [main]
jobs:
infrastructure:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Plan
run: |
cd terraform
terraform init
terraform plan -out=tfplan
- name: Terraform Apply
run: |
cd terraform
terraform apply tfplan
- name: Generate Ansible Inventory
run: |
cd terraform
terraform output -json > ../ansible/terraform_outputs.json
configuration:
needs: infrastructure
runs-on: ubuntu-latest
steps:
- name: Setup Ansible
run: |
pip install ansible boto3
- name: Base Configuration
run: |
cd ansible
ansible-playbook -i inventory/aws_ec2.yml base-config.yml
- name: Application Deployment
run: |
cd ansible
ansible-playbook -i inventory/aws_ec2.yml app-deploy.yml
パターン 2: レイヤー分離型
インフラの各レイヤーごとに最適なツールを選択するパターンです:
mermaidflowchart TD
subgraph layer1["ネットワークレイヤー"]
terraform1["Terraform<br/>VPC, Subnet, Route, NAT"]
end
subgraph layer2["コンピュートレイヤー"]
terraform2["Terraform<br/>EC2, ECS, Lambda"]
end
subgraph layer3["プラットフォームレイヤー"]
ansible1["Ansible<br/>Docker, Kubernetes"]
end
subgraph layer4["アプリケーションレイヤー"]
ansible2["Ansible<br/>App Deployment, Configuration"]
end
layer1 --> layer2
layer2 --> layer3
layer3 --> layer4
実装例: マイクロサービス環境
hcl# terraform/network/main.tf
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
name = "microservices-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = false
}
hcl# terraform/compute/main.tf
module "eks" {
source = "terraform-aws-modules/eks/aws"
cluster_name = "microservices-cluster"
cluster_version = "1.24"
vpc_id = data.terraform_remote_state.network.outputs.vpc_id
subnet_ids = data.terraform_remote_state.network.outputs.private_subnets
node_groups = {
main = {
desired_capacity = 3
max_capacity = 10
min_capacity = 1
instance_types = ["t3.medium"]
}
}
}
yaml# ansible/k8s-setup.yml
---
- name: Kubernetes クラスター設定
hosts: localhost
vars:
cluster_name: microservices-cluster
tasks:
- name: kubectl 設定更新
shell: |
aws eks update-kubeconfig --name {{ cluster_name }} --region ap-northeast-1
- name: Helm リポジトリ追加
kubernetes.core.helm_repository:
name: '{{ item.name }}'
repo_url: '{{ item.url }}'
loop:
- {
name: ingress-nginx,
url: 'https://kubernetes.github.io/ingress-nginx',
}
- {
name: prometheus-community,
url: 'https://prometheus-community.github.io/helm-charts',
}
- name: NGINX Ingress Controller デプロイ
kubernetes.core.helm:
name: ingress-nginx
chart_ref: ingress-nginx/ingress-nginx
namespace: ingress-nginx
create_namespace: true
パターン 3: 機能分離型
機能やチームの責任範囲に応じてツールを分離するパターンです:
チーム | 担当領域 | 使用ツール | 理由 |
---|---|---|---|
Platform Team | インフラ基盤 | Terraform | 宣言型でインフラの一元管理 |
Security Team | セキュリティ設定 | Puppet | 設定の標準化と監視 |
DevOps Team | CI/CD・運用 | Ansible | 柔軟な自動化とオーケストレーション |
Development Team | アプリケーション | Ansible + Docker | アプリ固有の要件に対応 |
実装例: チーム間連携
yaml# Platform Team: インフラ情報のExport
# terraform/outputs.tf
output "platform_info" {
value = {
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnets
security_group_id = aws_security_group.app.id
database_endpoint = module.rds.db_instance_endpoint
}
sensitive = true
}
puppet# Security Team: セキュリティベースライン
# puppet/modules/security_baseline/manifests/init.pp
class security_baseline {
# ファイアウォール設定
firewall { '100 allow ssh':
dport => [22],
proto => tcp,
action => accept,
}
# セキュリティ監査ログ
file { '/etc/audit/rules.d/base.rules':
ensure => file,
content => template('security_baseline/audit.rules.erb'),
notify => Service['auditd'],
}
# 定期的なセキュリティアップデート
cron { 'security-updates':
command => '/usr/bin/yum update --security -y',
user => 'root',
hour => 2,
minute => 0,
weekday => 0,
}
}
yaml# DevOps Team: アプリケーション配備
# ansible/app-deploy.yml
---
- name: アプリケーション配備
hosts: app_servers
vars:
app_version: "{{ lookup('env', 'APP_VERSION') | default('latest') }}"
tasks:
- name: Platform情報の取得
set_fact:
platform_info: "{{ lookup('file', '../terraform/platform_outputs.json') | from_json }}"
- name: アプリケーションコンテナ配備
docker_container:
name: 'myapp-{{ app_version }}'
image: 'myregistry/myapp:{{ app_version }}'
ports:
- '3000:3000'
env:
DATABASE_URL: '{{ platform_info.database_endpoint }}'
REDIS_URL: '{{ platform_info.cache_endpoint }}'
restart_policy: always
まとめ
インフラ管理ツールの選択において最も重要なのは、「宣言型」と「手続型」という根本的なアプローチの違いを理解することです。
**宣言型アプローチ(Terraform、Puppet)**の特徴:
- 最終的な状態を記述し、達成手順はツールに委ねる
- 冪等性が保証され、状態管理が自動化される
- インフラリソースの管理や標準化された設定管理に適している
**手続型アプローチ(Ansible、Chef)**の特徴:
- 実行すべき手順を明示的に記述する
- 複雑な条件分岐やカスタムロジックに対応可能
- アプリケーション配備や運用タスクの自動化に適している
実際のプロジェクトでは、これらのツールを併用することで、それぞれの強みを最大限に活用できます。特に Terraform と Ansible の組み合わせは、インフラ構築と設定管理の責任を明確に分離し、効率的な運用を実現する実績ある戦略です。
ツール選択の際は、技術的制約だけでなく、チーム体制、プロジェクトの規模、長期的な保守性も考慮することが重要です。適切な使い分けにより、安定性と生産性を両立したインフラ管理が実現できるでしょう。
関連リンク
- Terraform 公式ドキュメント: https://developer.hashicorp.com/terraform
- Ansible 公式ドキュメント: https://docs.ansible.com/
- Puppet 公式ドキュメント: https://puppet.com/docs/
- Chef 公式ドキュメント: https://docs.chef.io/
- AWS Provider for Terraform: https://registry.terraform.io/providers/hashicorp/aws/latest
- Ansible Collection for AWS: https://galaxy.ansible.com/amazon/aws
- article
Ansible と Terraform/Puppet/Chef 比較:宣言/手続の違いと併用戦略
- article
Ansible トラブルシュート:UNREACHABLE!/FAILED! を 3 分で切り分ける
- article
Ansible 入門 2025年:5 分で分かる自動化の全体像と始め方
- article
CentOS8サーバーの初期設定とユーザー追加をAnsible使って自動化する設定
- article
【設定方法】構成管理ツールのAnsibleをMacのHomebrewでサクッとインストールする手順
- article
Obsidian Sync と iCloud/Dropbox/Google Drive:速度・信頼性・復旧性を実測比較
- article
Cline vs Devin vs Cursor 実務比較:要件理解・差分精度・保守コスト
- article
Claude Code vs Cursor vs Codeium:実案件で比較した生産性・品質・コスト
- article
Nuxt と Next.js を徹底比較:開発体験・レンダリング・エコシステムの違い
- article
Ansible と Terraform/Puppet/Chef 比較:宣言/手続の違いと併用戦略
- article
Nginx Unit と Node(+ PM2)/Passenger を比較:再読み込み・可用性・性能の実測
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来