Configuration Management with Ansible

Ansibleによる構成管理 — 包括的技術ガイド

カテゴリ: Linux システム管理
対象レベル: 中級〜上級
最終更新: 2026年4月


目次

  1. はじめに — なぜ構成管理が必要か
  2. Ansibleの概要と歴史
  3. アーキテクチャ — 全体像
    • 3.1 コントロールノード
    • 3.2 マネージドノード
    • 3.3 インベントリ
    • 3.4 モジュール
    • 3.5 プラグイン
    • 3.6 APIとAnsible Automation Platform
  4. インストールとセットアップ
    • 4.1 コントロールノードのインストール
    • 4.2 SSH鍵の設定
    • 4.3 ansible.cfg の設定
  5. インベントリ管理
    • 5.1 静的インベントリ (INI形式)
    • 5.2 静的インベントリ (YAML形式)
    • 5.3 動的インベントリ
    • 5.4 インベントリ変数
  6. アドホックコマンド
  7. YAMLとJinja2の基礎
    • 7.1 YAML構文
    • 7.2 Jinja2テンプレート
  8. プレイブックの構造
    • 8.1 プレイ (Play)
    • 8.2 タスク (Task)
    • 8.3 ハンドラ (Handler)
    • 8.4 変数 (Variables)
    • 8.5 ファクト (Facts)
    • 8.6 条件分岐 (when)
    • 8.7 ループ (loop)
  9. ロールとAnsible Galaxy
    • 9.1 ロールの構造
    • 9.2 ロールの作成
    • 9.3 Ansible Galaxy
  10. Ansible Vault — 機密情報の管理
  11. 主要モジュール詳解
    • 11.1 パッケージ管理 (apt / yum / dnf)
    • 11.2 サービス管理 (service / systemd)
    • 11.3 ファイル操作 (file / copy / template)
    • 11.4 ユーザー管理 (user / group)
    • 11.5 コマンド実行 (command / shell / raw)
    • 11.6 その他の重要モジュール
  12. エラーハンドリング
  13. べき等性 (Idempotency)
  14. IaC ベストプラクティス
  15. Ansibleと他ツールの比較
  16. 実践的なプレイブック例
  17. トラブルシューティング
  18. 参考資料

1. はじめに

1.1 手動管理の限界

数台のサーバーを管理している場合、SSHでログインして手動でコマンドを実行することは現実的です。しかし、サーバーが数十台、数百台、数千台になると次のような問題が発生します。

課題内容
スケーラビリティ手動作業では追いつかない台数になる
一貫性の欠如設定のドリフト(構成ずれ)が発生しやすい
属人化担当者しか手順を知らない
監査困難「誰が何をいつ変更したか」の追跡が困難
ヒューマンエラータイポや手順の誤りによる障害リスク
ドキュメント陳腐化実態と手順書が乖離する

1.2 構成管理ツールが解決すること

構成管理ツール(Configuration Management Tool; CMT)は以下を実現します。

  • コードによるインフラの定義 (Infrastructure as Code; IaC)
  • 宣言的または手続き的な状態の記述
  • 自動化された繰り返し可能な実行
  • バージョン管理によるインフラ変更の追跡
  • テスト可能なインフラ定義

2. Ansibleの概要と歴史

2.1 概要

Ansible(アンシブル)は Red Hat が開発するオープンソースの構成管理・プロビジョニング・アプリケーションデプロイツールです。

主な特徴は以下の通りです。

特徴説明
エージェントレスマネージドノードにエージェントソフトウェアのインストール不要
SSH/WinRM ベース既存の通信プロトコルを利用
YAML による記述人間が読み書きしやすいフォーマット
べき等性何度実行しても同じ結果になる設計
豊富なモジュール5,000以上のビルトインモジュール
マルチプラットフォームLinux, Windows, ネットワーク機器, クラウドに対応

2.2 歴史

2012年  Michael DeHaan が Ansible を創設
2013年  Ansible, Inc. 設立
2015年  Red Hat が Ansible, Inc. を買収
2019年  Ansible Engine 2.8 リリース、コレクション機能の強化
2021年  Ansible Automation Platform 2.0 リリース
2023年  ansible-core 2.15、コレクションベースアーキテクチャの成熟
2024年  ansible-core 2.17、Python 3.12 サポート強化

2.3 Ansible のエコシステム

┌─────────────────────────────────────────────────────────┐
│              Ansible Ecosystem                          │
│                                                         │
│  ┌─────────────┐  ┌──────────────┐  ┌───────────────┐  │
│  │ansible-core  │  │  Collections │  │  AWX /        │  │
│  │(エンジン本体) │  │ (モジュール群) │  │  AAP          │  │
│  └─────────────┘  └──────────────┘  └───────────────┘  │
│  ┌─────────────┐  ┌──────────────┐  ┌───────────────┐  │
│  │Ansible Vault│  │Ansible Galaxy│  │  Molecule     │  │
│  │(暗号化)      │  │(共有リポジトリ) │  │  (テスト)      │  │
│  └─────────────┘  └──────────────┘  └───────────────┘  │
└─────────────────────────────────────────────────────────┘

3. アーキテクチャ

3.1 全体アーキテクチャ図

                    ┌─────────────────────────────────┐
                    │        Control Node              │
                    │   (Ansible がインストールされた    │
                    │    管理用サーバーまたはPC)          │
                    │                                  │
                    │  ┌──────────┐  ┌─────────────┐  │
                    │  │Inventory │  │  Playbooks  │  │
                    │  │ (静的/動的)│  │  (YAML)     │  │
                    │  └──────────┘  └─────────────┘  │
                    │  ┌──────────┐  ┌─────────────┐  │
                    │  │ Modules  │  │  Variables  │  │
                    │  │(Python等) │  │  & Vault    │  │
                    │  └──────────┘  └─────────────┘  │
                    └──────────┬──────────────────────┘
                               │ SSH / WinRM
              ┌────────────────┼────────────────┐
              │                │                │
     ┌────────▼──────┐ ┌───────▼──────┐ ┌──────▼────────┐
     │ Managed Node  │ │ Managed Node │ │ Managed Node  │
     │  web01        │ │  db01        │ │  app01        │
     │ (Linux/UNIX)  │ │ (Linux/UNIX) │ │  (Windows)    │
     └───────────────┘ └──────────────┘ └───────────────┘

3.2 コントロールノード (Control Node)

コントロールノードは Ansible がインストールされ、プレイブックや ad-hoc コマンドを実行するマシンです。

要件:

  • Linux/macOS/UNIX系 OS(Windowsはコントロールノードとして非サポート、WSL2は可)
  • Python 3.9 以上(ansible-core 2.15+)
  • SSH クライアント

主要な設定ファイルの場所:

/etc/ansible/ansible.cfg      # システム全体の設定
~/.ansible.cfg                # ユーザー設定(優先度高)
./ansible.cfg                 # カレントディレクトリ(最高優先度)

3.3 マネージドノード (Managed Nodes)

マネージドノードは Ansible に管理される対象のサーバーです。

要件 (Linux の場合):

  • SSH サーバー (OpenSSH)
  • Python 2.7 または 3.5+ (一部モジュールは raw モジュールを使用すれば不要)
  • sudo / su (特権昇格が必要な場合)

エージェントレスの仕組み:

1. Ansible がモジュール(Pythonスクリプト)を生成
2. SCP/SFTP でマネージドノードの /tmp に転送
3. SSH でモジュールを実行
4. 実行結果を JSON で返却
5. 一時ファイルを削除

3.4 インベントリ (Inventory)

インベントリは管理対象ホストの一覧と、それらのグループ化を定義するファイルまたはスクリプトです。詳細はセクション5で解説します。

3.5 モジュール (Modules)

モジュールは Ansible が各タスクを実行するための実行単位です。2024年現在、公式コレクション含め 7,000以上のモジュールが利用可能です。

モジュールの分類:

┌─────────────────────────────────────────┐
│           Module Categories             │
├──────────────────┬──────────────────────┤
│ System           │ Files                │
│  user, group,    │  file, copy,         │
│  hostname, cron  │  template, fetch     │
├──────────────────┼──────────────────────┤
│ Package Mgmt     │ Service              │
│  apt, yum, dnf,  │  service, systemd,   │
│  pip, gem        │  supervisorctl       │
├──────────────────┼──────────────────────┤
│ Network          │ Cloud                │
│  uri, get_url,   │  amazon.aws.*,       │
│  firewalld       │  azure.*,gcp.*       │
├──────────────────┼──────────────────────┤
│ Database         │ Commands             │
│  mysql_db,       │  command, shell,     │
│  postgresql_db   │  raw, script         │
└──────────────────┴──────────────────────┘

3.6 プラグイン (Plugins)

モジュールと似ていますが、Ansible エンジン自体の動作を拡張します。

プラグイン種別役割
Connection接続方式の定義ssh, local, docker, winrm
Inventoryインベントリソースaws_ec2, gcp_compute
Callback実行結果の出力形式yaml, json, minimal
FilterJinja2 フィルタの追加to_yaml, from_json
Lookup外部データの取得env, file, password
Vars変数の読み込み元host_group_vars

4. インストールとセットアップ

4.1 コントロールノードのインストール

Ubuntu / Debian

# システムパッケージの更新
sudo apt update && sudo apt upgrade -y

# 依存パッケージのインストール
sudo apt install -y python3 python3-pip python3-venv software-properties-common

# 方法1: pip によるインストール(推奨)
python3 -m pip install --user ansible

# バージョン確認
ansible --version
# 出力例:
# ansible [core 2.17.0]
#   config file = /home/user/.ansible.cfg
#   configured module search path = ['/home/user/.ansible/plugins/modules', ...]
#   ansible python module location = /home/user/.local/lib/python3.11/site-packages/ansible
#   ansible collection location = /home/user/.ansible/collections:/usr/share/ansible/collections
#   executable location = /home/user/.local/bin/ansible
#   python version = 3.11.4
#   jinja version = 3.1.2
#   libyaml = True

# 方法2: apt リポジトリ経由
sudo apt-add-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible

RHEL / CentOS / Rocky Linux / AlmaLinux

# EPEL リポジトリの有効化
sudo dnf install -y epel-release

# Ansible のインストール
sudo dnf install -y ansible

# または pip 経由(最新版)
sudo dnf install -y python3 python3-pip
python3 -m pip install --user ansible

# バージョン確認
ansible --version

macOS

# Homebrew 経由(推奨)
brew install ansible

# pip 経由
pip3 install ansible

仮想環境でのインストール(推奨: プロジェクト分離)

# 仮想環境の作成
python3 -m venv ~/.venv/ansible
source ~/.venv/ansible/bin/activate

# Ansible のインストール
pip install ansible

# 必要なコレクションのインストール
ansible-galaxy collection install amazon.aws
ansible-galaxy collection install community.general

# requirements.yml を使った一括インストール
cat > requirements.yml << 'EOF'
collections:
  - name: community.general
    version: ">=7.0.0"
  - name: amazon.aws
    version: ">=7.0.0"
  - name: community.mysql
    version: ">=3.0.0"
EOF

ansible-galaxy collection install -r requirements.yml

4.2 SSH鍵の設定

Ansible はデフォルトで SSH 公開鍵認証を使用します。

# SSH鍵ペアの生成(まだない場合)
ssh-keygen -t ed25519 -C "ansible-control" -f ~/.ssh/ansible_ed25519
# パスフレーズは空でも設定しても良い(セキュリティ要件に応じて)

# マネージドノードへの公開鍵配布
# 方法1: ssh-copy-id(簡単)
ssh-copy-id -i ~/.ssh/ansible_ed25519.pub user@web01
ssh-copy-id -i ~/.ssh/ansible_ed25519.pub user@web02
ssh-copy-id -i ~/.ssh/ansible_ed25519.pub user@db01

# 方法2: Ansible の authorized_key モジュールでの管理(後述)

# SSH エージェントの設定(パスフレーズ付き鍵の場合)
eval $(ssh-agent)
ssh-add ~/.ssh/ansible_ed25519

# 接続テスト
ssh -i ~/.ssh/ansible_ed25519 user@web01 "hostname"
# 出力: web01

4.3 ansible.cfg の設定

# ~/.ansible.cfg または プロジェクトディレクトリの ansible.cfg

[defaults]
# インベントリファイルのパス
inventory = ./inventory

# SSH接続のデフォルトユーザー
remote_user = ansible

# SSH秘密鍵のパス
private_key_file = ~/.ssh/ansible_ed25519

# 並列実行数(デフォルト: 5)
forks = 20

# SSH接続タイムアウト(秒)
timeout = 30

# ホスト鍵チェックの無効化(開発環境のみ。本番では有効にすること)
host_key_checking = False

# ログの出力先
log_path = ./ansible.log

# コールバックプラグイン(出力形式)
stdout_callback = yaml

# ファクト収集のキャッシュ
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 86400

# roles のパス
roles_path = ./roles:~/.ansible/roles

# retry ファイルを作成しない
retry_files_enabled = False

# Python インタープリタの自動検出
interpreter_python = auto_silent

[privilege_escalation]
# sudo による特権昇格を有効化
become = True
become_method = sudo
become_user = root
# パスワードなしの sudo を設定していない場合は True に
become_ask_pass = False

[ssh_connection]
# SSH 接続のパイプライン化(パフォーマンス向上)
pipelining = True

# SSH 引数のカスタマイズ
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s

# ControlPath の設定(接続の再利用)
control_path = %(directory)s/%%h-%%r

[inventory]
# インベントリのキャッシュを有効化
cache = True
cache_plugin = jsonfile
cache_connection = /tmp/ansible_inventory_cache
cache_timeout = 3600

4.4 接続テスト

# ping モジュールでの接続確認(ICMP ping ではなく Python の動作確認)
ansible all -m ping
# 出力例:
# web01 | SUCCESS => {
#     "changed": false,
#     "ping": "pong"
# }
# web02 | SUCCESS => {
#     "changed": false,
#     "ping": "pong"
# }
# db01 | SUCCESS => {
#     "changed": false,
#     "ping": "pong"
# }

# 特定グループのみ
ansible webservers -m ping

# ファクト情報の取得テスト
ansible web01 -m setup | head -30
# 出力例(抜粋):
# web01 | SUCCESS => {
#     "ansible_facts": {
#         "ansible_all_ipv4_addresses": ["192.168.1.101"],
#         "ansible_architecture": "x86_64",
#         "ansible_distribution": "Ubuntu",
#         "ansible_distribution_version": "22.04",
#         "ansible_hostname": "web01",
#         "ansible_kernel": "5.15.0-87-generic",
#         ...
#     }
# }

5. インベントリ管理

5.1 静的インベントリ — INI 形式

# inventory/hosts

# グループなしのホスト(ungrouped)
192.168.1.100

# [グループ名] でグループを定義
[webservers]
web01 ansible_host=192.168.1.101 ansible_port=22
web02 ansible_host=192.168.1.102
web03 ansible_host=192.168.1.103

# 連番パターン(web01〜web05 を一括定義)
[appservers]
app[01:05].example.com

# データベースサーバー
[dbservers]
db01 ansible_host=192.168.1.201 ansible_user=dbadmin
db02 ansible_host=192.168.1.202

# ロードバランサー
[loadbalancers]
lb01 ansible_host=192.168.1.10

# グループの変数定義
[webservers:vars]
http_port=80
https_port=443
max_clients=200

# グループの親子関係(メタグループ)
[production:children]
webservers
appservers
dbservers
loadbalancers

[staging:children]
webservers

# Windowsホストの場合
[windows]
winserver01 ansible_host=192.168.1.50 ansible_connection=winrm ansible_winrm_transport=ntlm

5.2 静的インベントリ — YAML 形式(推奨)

# inventory/hosts.yml

all:
  vars:
    ansible_user: ansible
    ansible_private_key_file: ~/.ssh/ansible_ed25519

  children:
    production:
      children:
        webservers:
          hosts:
            web01:
              ansible_host: 192.168.1.101
              nginx_port: 80
              nginx_ssl_port: 443
            web02:
              ansible_host: 192.168.1.102
              nginx_port: 80
              nginx_ssl_port: 443
          vars:
            # webservers グループ共通の変数
            app_env: production
            max_connections: 1000

        dbservers:
          hosts:
            db01:
              ansible_host: 192.168.1.201
              db_role: primary
            db02:
              ansible_host: 192.168.1.202
              db_role: replica
          vars:
            mysql_version: "8.0"
            mysql_port: 3306

        loadbalancers:
          hosts:
            lb01:
              ansible_host: 192.168.1.10

    staging:
      children:
        webservers:
          hosts:
            web-stg01:
              ansible_host: 192.168.2.101
          vars:
            app_env: staging
            max_connections: 100

5.3 host_vars と group_vars ディレクトリ

project/
├── inventory/
│   ├── hosts.yml
│   ├── group_vars/
│   │   ├── all.yml          # 全ホスト共通の変数
│   │   ├── all/
│   │   │   ├── vars.yml     # ディレクトリ形式も可
│   │   │   └── vault.yml    # 暗号化変数(Vault)
│   │   ├── webservers.yml   # webservers グループの変数
│   │   └── dbservers.yml    # dbservers グループの変数
│   └── host_vars/
│       ├── web01.yml        # web01 固有の変数
│       └── db01.yml         # db01 固有の変数
# inventory/group_vars/all.yml
---
ntp_servers:
  - 0.jp.pool.ntp.org
  - 1.jp.pool.ntp.org

dns_servers:
  - 8.8.8.8
  - 8.8.4.4

timezone: Asia/Tokyo

common_packages:
  - vim
  - curl
  - wget
  - git
  - net-tools
  - htop
# inventory/group_vars/webservers.yml
---
nginx_version: "1.25"
nginx_worker_processes: auto
nginx_worker_connections: 1024

ssl_certificate_path: /etc/ssl/certs/server.crt
ssl_key_path: /etc/ssl/private/server.key

app_deploy_path: /var/www/html
app_owner: www-data
app_group: www-data
# inventory/host_vars/web01.yml
---
# web01 固有の設定
nginx_server_name: web01.example.com
nginx_custom_log: /var/log/nginx/web01_access.log
backup_enabled: true

5.4 動的インベントリ

動的インベントリはスクリプトや Ansible プラグインを使って、クラウドや CMDB などの外部ソースからホスト情報を動的に取得します。

AWS EC2 動的インベントリ

# amazon.aws コレクションのインストール
ansible-galaxy collection install amazon.aws

# AWS 認証情報の設定
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_REGION="ap-northeast-1"
# inventory/aws_ec2.yml(プラグイン設定ファイル)
plugin: amazon.aws.aws_ec2

# リージョン
regions:
  - ap-northeast-1
  - ap-northeast-3

# フィルタリング(running インスタンスのみ)
filters:
  instance-state-name: running
  tag:Environment: production

# ホスト名の設定
hostnames:
  - tag:Name
  - private-ip-address

# グループ化のルール
keyed_groups:
  # タグ値でグループ化
  - key: tags.Role
    prefix: role
    separator: "_"
  # インスタンスタイプでグループ化
  - key: instance_type
    prefix: type
  # AZ でグループ化
  - key: placement.availability_zone
    prefix: az

# グループのカスタマイズ
groups:
  webservers: "'web' in tags.Role"
  dbservers: "'db' in tags.Role"

# 変数のカスタマイズ
compose:
  ansible_host: private_ip_address
  ec2_region: placement.region
# 動的インベントリのテスト
ansible-inventory -i inventory/aws_ec2.yml --list
ansible-inventory -i inventory/aws_ec2.yml --graph

# 出力例(--graph):
# @all:
#   |--@role_web:
#   |  |--web01.ap-northeast-1.compute.internal
#   |  |--web02.ap-northeast-1.compute.internal
#   |--@role_db:
#   |  |--db01.ap-northeast-1.compute.internal
#   |--@type_t3_medium:
#   |  |--web01.ap-northeast-1.compute.internal
#   |  |--web02.ap-northeast-1.compute.internal

5.5 インベントリの確認コマンド

# インベントリの一覧表示(JSON形式)
ansible-inventory --list

# グラフ形式での表示
ansible-inventory --graph

# 特定ホストの変数確認
ansible-inventory --host web01
# 出力例:
# {
#     "ansible_host": "192.168.1.101",
#     "ansible_user": "ansible",
#     "app_env": "production",
#     "nginx_port": 80
# }

# 特定グループのホスト一覧
ansible webservers --list-hosts
# 出力例:
#   hosts (2):
#     web01
#     web02

6. アドホックコマンド

アドホックコマンドはプレイブックを作成せずに、コマンドラインから直接 Ansible モジュールを実行する方法です。一時的な作業や確認に使用します。

6.1 基本構文

ansible <対象ホスト/グループ> [オプション] -m <モジュール名> -a <引数>

6.2 よく使うアドホックコマンド

# ---- 接続確認 ----
ansible all -m ping

# ---- コマンド実行 ----
# command モジュール(シェル機能なし)
ansible webservers -m command -a "uptime"
# 出力例:
# web01 | CHANGED | rc=0 >>
#  10:23:45 up 30 days,  2:15,  1 user,  load average: 0.12, 0.08, 0.05
# web02 | CHANGED | rc=0 >>
#  10:23:45 up 30 days,  2:16,  1 user,  load average: 0.08, 0.07, 0.04

# shell モジュール(シェル機能あり:パイプ、リダイレクト等)
ansible webservers -m shell -a "ps aux | grep nginx | wc -l"

# ---- パッケージ管理 ----
# パッケージのインストール
ansible webservers -m apt -a "name=nginx state=present" --become
# 出力例:
# web01 | CHANGED => {
#     "cache_update_time": 1712345678,
#     "changed": true,
#     ...
# }

# パッケージのアップデート(apt cache更新付き)
ansible webservers -m apt -a "name=nginx state=latest update_cache=yes" --become

# 複数パッケージのインストール
ansible all -m apt -a "name='vim,curl,wget' state=present" --become

# ---- サービス管理 ----
# サービスの起動
ansible webservers -m service -a "name=nginx state=started" --become

# サービスの再起動
ansible webservers -m service -a "name=nginx state=restarted" --become

# サービスの有効化
ansible webservers -m service -a "name=nginx enabled=yes" --become

# ---- ファイル操作 ----
# ファイルのコピー
ansible webservers -m copy -a "src=/tmp/test.conf dest=/etc/nginx/test.conf mode=0644" --become

# ファイルの削除
ansible webservers -m file -a "path=/tmp/old_file.txt state=absent" --become

# ディレクトリの作成
ansible all -m file -a "path=/opt/myapp state=directory mode=0755 owner=ansible group=ansible" --become

# ---- ユーザー管理 ----
# ユーザーの作成
ansible all -m user -a "name=deploy comment='Deploy User' shell=/bin/bash state=present" --become

# ---- システム情報の取得 ----
# ディスク使用量の確認
ansible all -m command -a "df -h"

# メモリ使用量
ansible all -m command -a "free -m"

# ---- ファクトの収集 ----
# 全ファクト情報の取得
ansible web01 -m setup

# 特定のファクトのみ取得(フィルタリング)
ansible web01 -m setup -a "filter=ansible_distribution*"
# 出力例:
# web01 | SUCCESS => {
#     "ansible_facts": {
#         "ansible_distribution": "Ubuntu",
#         "ansible_distribution_file_parsed": true,
#         "ansible_distribution_file_path": "/etc/os-release",
#         "ansible_distribution_major_version": "22",
#         "ansible_distribution_release": "jammy",
#         "ansible_distribution_version": "22.04"
#     }
# }

# ---- 並列数と接続オプション ----
# 並列実行数を指定(デフォルト: 5)
ansible all -m ping -f 20

# 特定のSSHユーザーで接続
ansible webservers -m ping -u deploy

# パスワード認証(テスト環境のみ推奨)
ansible webservers -m ping --ask-pass

# sudo 昇格
ansible webservers -m command -a "whoami" --become
# 出力: root

6.3 アドホックコマンドのオプション一覧

オプション短縮形説明
--module-name-m使用するモジュール名
--args-aモジュールへの引数
--become-b特権昇格(sudo)を有効化
--become-user昇格先ユーザー(デフォルト: root)
--user-uSSH 接続ユーザー
--forks-f並列実行数
--limit-l対象ホストの絞り込み
--check-Cドライラン(実際には変更しない)
--diff-D変更内容の差分表示
--verbose-v/-vv/-vvv詳細出力

7. YAMLとJinja2の基礎

7.1 YAML 構文

Ansible のプレイブックは YAML で記述します。YAML の基本を理解することが Ansible 習得の前提です。

# ---- 基本データ型 ----

# 文字列
string_single: Hello World
string_quoted: "Hello World"
string_literal: 'Hello World'

# 数値
integer: 42
float: 3.14

# 真偽値
boolean_true: true   # または yes, True, TRUE, on
boolean_false: false # または no, False, FALSE, off

# null
null_value: null    # または ~

# ---- リスト(配列)----
# フロー形式
packages_flow: [vim, curl, wget, git]

# ブロック形式(推奨)
packages_block:
  - vim
  - curl
  - wget
  - git

# ---- ディクショナリ(マップ)----
# フロー形式
user_flow: {name: john, uid: 1001, shell: /bin/bash}

# ブロック形式(推奨)
user_block:
  name: john
  uid: 1001
  shell: /bin/bash

# ---- ネスト構造 ----
servers:
  webservers:
    - name: web01
      ip: 192.168.1.101
      ports:
        - 80
        - 443
    - name: web02
      ip: 192.168.1.102
      ports:
        - 80
        - 443
  dbservers:
    - name: db01
      ip: 192.168.1.201

# ---- 複数行文字列 ----
# | : 改行を保持(リテラルブロック)
multiline_literal: |
  server {
      listen 80;
      server_name example.com;
  }

# > : 改行を空白に変換(折りたたみブロック)
multiline_folded: >
  This is a long string that
  will be folded into one line.
  # 結果: "This is a long string that will be folded into one line."

# ---- アンカーとエイリアス(DRY原則)----
# アンカーの定義(&)
default_user: &default_user
  shell: /bin/bash
  groups: sudo
  state: present

# エイリアスの参照(*)
users:
  - name: alice
    <<: *default_user
    uid: 1001
  - name: bob
    <<: *default_user
    uid: 1002
    shell: /bin/zsh  # オーバーライド可能

7.2 Jinja2 テンプレートエンジン

Ansible は変数の展開とテンプレート処理に Jinja2 を使用します。

変数の参照

# プレイブック内での変数参照
- name: ユーザー {{ username }} を作成
  user:
    name: "{{ username }}"
    home: "/home/{{ username }}"
    shell: "{{ user_shell | default('/bin/bash') }}"

フィルタ

# 大文字/小文字変換
- debug:
    msg: "{{ 'hello world' | upper }}"  # HELLO WORLD
    msg: "{{ 'HELLO WORLD' | lower }}"  # hello world

# デフォルト値
- debug:
    msg: "{{ undefined_var | default('default_value') }}"

# リストの結合
- debug:
    msg: "{{ ['a', 'b', 'c'] | join(', ') }}"  # a, b, c

# 型変換
- debug:
    msg: "{{ '42' | int }}"     # 42 (整数)
    msg: "{{ 42 | string }}"   # "42" (文字列)
    msg: "{{ some_var | bool }}"  # True/False

# JSON/YAML 変換
- debug:
    msg: "{{ data | to_json }}"
    msg: "{{ data | to_yaml }}"
    msg: "{{ json_string | from_json }}"

# ファイルパス操作
- debug:
    msg: "{{ '/etc/nginx/nginx.conf' | basename }}"  # nginx.conf
    msg: "{{ '/etc/nginx/nginx.conf' | dirname }}"   # /etc/nginx

# リスト操作
- debug:
    msg: "{{ [3, 1, 4, 1, 5] | sort }}"       # [1, 1, 3, 4, 5]
    msg: "{{ [3, 1, 4, 1, 5] | unique }}"     # [3, 1, 4, 5]
    msg: "{{ packages | select('match', 'python.*') | list }}"

# 文字列操作
- debug:
    msg: "{{ 'Hello World' | replace('World', 'Ansible') }}"
    msg: "{{ hostname | regex_replace('^web', 'app') }}"

# ハッシュ/暗号化
- debug:
    msg: "{{ 'mypassword' | password_hash('sha512') }}"

テスト (Tests)

# 変数の状態チェック
- name: 変数が定義されているか確認
  debug:
    msg: "username is defined"
  when: username is defined

- name: リストかどうか確認
  debug:
    msg: "it's a list"
  when: packages is iterable and packages is not string

- name: 数値の比較
  debug:
    msg: "large number"
  when: count is gt 100  # greater than

# よく使うテスト
# is defined       : 変数が定義されている
# is undefined     : 変数が未定義
# is none          : null/None
# is string        : 文字列型
# is number        : 数値型
# is iterable      : イテラブル(リスト等)
# is mapping       : ディクショナリ型
# is file          : ファイルが存在
# is directory     : ディレクトリが存在
# is version('2.0', '>=') : バージョン比較

テンプレートファイル (.j2)

{# templates/nginx.conf.j2 #}

{# コメント #}

user {{ nginx_user | default('www-data') }};
worker_processes {{ nginx_worker_processes | default('auto') }};

events {
    worker_connections {{ nginx_worker_connections | default(1024) }};
}

http {
    {# SSL設定(条件付き) #}
    {% if ssl_enabled | default(false) %}
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    {% endif %}

    {# バーチャルホストの設定(ループ) #}
    {% for vhost in virtual_hosts %}
    server {
        listen {{ vhost.port | default(80) }};
        server_name {{ vhost.server_name }};
        root {{ vhost.root | default('/var/www/html') }};

        access_log /var/log/nginx/{{ vhost.server_name }}_access.log;
        error_log  /var/log/nginx/{{ vhost.server_name }}_error.log;

        {% if vhost.ssl | default(false) %}
        listen 443 ssl;
        ssl_certificate     {{ vhost.ssl_cert }};
        ssl_certificate_key {{ vhost.ssl_key }};
        {% endif %}

        location / {
            try_files $uri $uri/ =404;
        }
    }
    {% endfor %}
}

8. プレイブックの構造

8.1 プレイブックの全体構造

# site.yml — プレイブックの例

---
# ============================================================
# プレイ1: Webサーバーのセットアップ
# ============================================================
- name: Webサーバーのセットアップ           # プレイ名(必須)
  hosts: webservers                        # 対象ホスト/グループ
  become: true                             # sudo 昇格
  gather_facts: true                       # ファクト収集(デフォルト: true)
  any_errors_fatal: false                  # 1つのホストでエラーが出ても続行
  serial: 2                               # 一度に処理するホスト数(ローリングアップデート)
  max_fail_percentage: 20                 # 許容失敗率 (%)

  # プレイレベルの変数
  vars:
    nginx_version: "1.25"
    app_port: 8080

  # 変数ファイルの読み込み
  vars_files:
    - vars/common.yml
    - vars/webserver.yml

  # ハンドラ(タスクの notify で呼び出される)
  handlers:
    - name: Nginx を再起動
      service:
        name: nginx
        state: restarted

    - name: Nginx の設定をリロード
      service:
        name: nginx
        state: reloaded

  # 前処理タスク(ロールの実行前に実行)
  pre_tasks:
    - name: apt キャッシュを更新
      apt:
        update_cache: yes
        cache_valid_time: 3600

  # メインタスク
  tasks:
    - name: Nginx のインストール
      apt:
        name: "nginx={{ nginx_version }}*"
        state: present
      notify: Nginx を再起動  # ハンドラの呼び出し

    - name: Nginx の設定ファイルを配置
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
        backup: yes
      notify: Nginx の設定をリロード

    - name: Nginx を起動し有効化
      service:
        name: nginx
        state: started
        enabled: yes

  # 後処理タスク(ロールの実行後に実行)
  post_tasks:
    - name: デプロイ完了の通知
      debug:
        msg: "{{ inventory_hostname }} のセットアップが完了しました"

# ============================================================
# プレイ2: DBサーバーのセットアップ
# ============================================================
- name: DBサーバーのセットアップ
  hosts: dbservers
  become: true
  roles:
    - common
    - mysql

8.2 タスク (Task) の詳細

tasks:
  # --- 基本的なタスク ---
  - name: パッケージのインストール
    apt:
      name: vim
      state: present

  # --- タグの付与(選択的実行に使用)---
  - name: セキュリティアップデートの適用
    apt:
      upgrade: dist
    tags:
      - security
      - updates

  # --- register: タスクの実行結果を変数に保存 ---
  - name: Nginx のステータス確認
    command: systemctl status nginx
    register: nginx_status
    ignore_errors: yes  # エラーを無視して続行

  - name: Nginx のステータスを表示
    debug:
      var: nginx_status.stdout_lines

  # --- changed_when / failed_when: 変更/失敗の判定をカスタマイズ ---
  - name: カスタムスクリプトの実行
    command: /opt/scripts/check_app.sh
    register: app_check
    changed_when: "'Updated' in app_check.stdout"
    failed_when: app_check.rc > 1

  # --- delegate_to: 別ホストで実行 ---
  - name: ロードバランサーからホストを切り離す
    command: /usr/local/bin/lb-remove.sh {{ inventory_hostname }}
    delegate_to: lb01

  # --- run_once: 1つのホストでのみ実行 ---
  - name: データベースのマイグレーション
    command: /opt/app/manage.py migrate
    run_once: true
    delegate_to: app01

  # --- block: 複数タスクのグループ化 ---
  - block:
      - name: 設定ファイルのバックアップ
        copy:
          src: /etc/nginx/nginx.conf
          dest: /etc/nginx/nginx.conf.bak
          remote_src: yes

      - name: 新しい設定の適用
        template:
          src: nginx.conf.j2
          dest: /etc/nginx/nginx.conf

      - name: 設定のテスト
        command: nginx -t
    rescue:
      # エラー発生時の回復処理
      - name: バックアップから復元
        copy:
          src: /etc/nginx/nginx.conf.bak
          dest: /etc/nginx/nginx.conf
          remote_src: yes
    always:
      # 成功/失敗に関わらず常に実行
      - name: ステータスの記録
        lineinfile:
          path: /var/log/deploy.log
          line: "{{ ansible_date_time.iso8601 }}: Deploy attempted on {{ inventory_hostname }}"

8.3 ハンドラ (Handler)

ハンドラは notify で呼び出され、プレイの最後に一度だけ実行されます(複数のタスクから notify されても1回)。

handlers:
  # 基本的なハンドラ
  - name: Nginx を再起動
    service:
      name: nginx
      state: restarted

  # ハンドラの連鎖(listen を使用)
  - name: Nginx の設定を検証
    command: nginx -t
    listen: "nginx 設定変更"

  - name: Nginx をリロード
    service:
      name: nginx
      state: reloaded
    listen: "nginx 設定変更"

tasks:
  - name: 設定ファイルの更新
    template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: "nginx 設定変更"  # 両方のハンドラが順番に実行される

  # ハンドラを即時実行
  - name: 証明書の更新
    copy:
      src: server.crt
      dest: /etc/ssl/certs/server.crt
    notify: Nginx を再起動

  # flush_handlers: その場でハンドラを実行
  - name: ハンドラを即時実行
    meta: flush_handlers

8.4 変数 (Variables)

変数の優先順位(低い順 → 高い順)

1.  role defaults (roles/x/defaults/main.yml)
2.  inventory file or script group vars
3.  inventory group_vars/all
4.  playbook group_vars/all
5.  inventory group_vars/*
6.  playbook group_vars/*
7.  inventory file or script host vars
8.  inventory host_vars/*
9.  playbook host_vars/*
10. host facts / cached set_facts
11. play vars
12. play vars_prompt
13. play vars_files
14. role vars (roles/x/vars/main.yml)
15. block vars (only for tasks in block)
16. task vars (only for the task)
17. include_vars
18. set_facts / registered vars
19. role (and include_role) params
20. include params
21. extra vars (ansible-playbook -e) ← 最高優先度
# 変数の定義方法

# --- プレイレベル ---
- hosts: all
  vars:
    app_name: myapp
    app_version: "2.3.1"
    app_config:
      debug: false
      log_level: INFO
      allowed_hosts:
        - "*.example.com"
        - localhost

  vars_files:
    - vars/secrets.yml  # 暗号化ファイルも読める(Vault)

  tasks:
    # --- set_fact: 動的な変数定義 ---
    - name: インストールパスを設定
      set_fact:
        install_path: "/opt/{{ app_name }}/{{ app_version }}"
        config_path: "/etc/{{ app_name }}"

    # --- include_vars: 条件付き変数ファイル読み込み ---
    - name: OS固有の変数を読み込む
      include_vars: "vars/{{ ansible_os_family | lower }}.yml"

    # --- vars_prompt: 実行時に入力 ---
  vars_prompt:
    - name: deploy_password
      prompt: "デプロイパスワードを入力してください"
      private: yes  # 入力を非表示
      confirm: yes  # 確認入力

    # --- extra_vars: コマンドラインから渡す ---
    # ansible-playbook site.yml -e "app_version=3.0.0 env=production"
    # または JSON 形式
    # ansible-playbook site.yml -e '{"app_version": "3.0.0", "env": "production"}'
    # または変数ファイル
    # ansible-playbook site.yml -e @vars/override.yml

8.5 ファクト (Facts)

ファクトはマネージドノードから収集するシステム情報です。

tasks:
  # よく使うファクト
  - debug:
      msg: |
        ホスト名     : {{ ansible_hostname }}
        FQDN        : {{ ansible_fqdn }}
        IP アドレス  : {{ ansible_default_ipv4.address }}
        OS          : {{ ansible_distribution }} {{ ansible_distribution_version }}
        カーネル     : {{ ansible_kernel }}
        CPU コア数   : {{ ansible_processor_vcpus }}
        メモリ(MB)   : {{ ansible_memtotal_mb }}
        ディスク      : {{ ansible_devices.sda.size | default('N/A') }}
        Python       : {{ ansible_python_version }}

  # カスタムファクト(/etc/ansible/facts.d/ に配置)
  # /etc/ansible/facts.d/myapp.fact
  # [application]
  # version=2.3.1
  # install_date=2026-01-15
  #
  # 参照: {{ ansible_local.myapp.application.version }}

  # ファクトのキャッシュ
  # ansible.cfg で fact_caching を設定すると再収集不要になる

8.6 条件分岐 (when)

tasks:
  # --- OS による条件分岐 ---
  - name: Nginx のインストール (Debian系)
    apt:
      name: nginx
      state: present
    when: ansible_os_family == "Debian"

  - name: Nginx のインストール (RedHat系)
    dnf:
      name: nginx
      state: present
    when: ansible_os_family == "RedHat"

  # --- 複数条件 (AND) ---
  - name: 本番環境のUbuntu 22.04にのみ実行
    command: /opt/security/harden.sh
    when:
      - ansible_distribution == "Ubuntu"
      - ansible_distribution_version == "22.04"
      - env == "production"

  # --- 複数条件 (OR) ---
  - name: CentOS または AlmaLinux の場合
    dnf:
      name: epel-release
      state: present
    when: ansible_distribution == "CentOS" or ansible_distribution == "AlmaLinux"

  # または filter 構文
  - name: CentOS または AlmaLinux の場合(filter)
    dnf:
      name: epel-release
      state: present
    when: ansible_distribution in ["CentOS", "AlmaLinux", "Rocky"]

  # --- register 結果による条件分岐 ---
  - name: アプリのプロセス確認
    command: pgrep myapp
    register: myapp_pid
    ignore_errors: yes

  - name: アプリが起動していない場合のみ起動
    command: /opt/myapp/start.sh
    when: myapp_pid.rc != 0

  # --- 変数の存在チェック ---
  - name: カスタム設定の適用(変数が定義されている場合のみ)
    template:
      src: custom.conf.j2
      dest: /etc/myapp/custom.conf
    when: custom_config is defined and custom_config | length > 0

  # --- バージョン比較 ---
  - name: Python 3.9以上の場合のみ実行
    command: /opt/scripts/new_feature.py
    when: ansible_python_version is version('3.9', '>=')

8.7 ループ (loop)

tasks:
  # --- 基本的なループ ---
  - name: 複数パッケージのインストール
    apt:
      name: "{{ item }}"
      state: present
    loop:
      - vim
      - curl
      - wget
      - git
      - htop

  # --- 変数リストを使ったループ ---
  - name: 複数ユーザーの作成
    user:
      name: "{{ item.name }}"
      uid: "{{ item.uid }}"
      shell: "{{ item.shell | default('/bin/bash') }}"
      groups: "{{ item.groups | default([]) }}"
      state: present
    loop: "{{ users }}"
    # vars/users.yml:
    # users:
    #   - name: alice
    #     uid: 1001
    #     groups: ["sudo", "docker"]
    #   - name: bob
    #     uid: 1002
    #     shell: /bin/zsh

  # --- loop_control: ループの制御 ---
  - name: 複数のサービスを管理
    service:
      name: "{{ item.name }}"
      state: "{{ item.state }}"
      enabled: "{{ item.enabled }}"
    loop:
      - {name: nginx,    state: started, enabled: true}
      - {name: mysql,    state: started, enabled: true}
      - {name: redis,    state: started, enabled: true}
      - {name: postfix,  state: stopped, enabled: false}
    loop_control:
      label: "{{ item.name }}"  # ログの見やすさのため名前のみ表示
      pause: 1                   # 各イテレーション間の待機時間(秒)

  # --- with_fileglob: ファイルのグロブパターン ---
  - name: 設定ファイルの一括配置
    copy:
      src: "{{ item }}"
      dest: /etc/myapp/conf.d/
    with_fileglob:
      - "files/conf.d/*.conf"

  # --- with_dict: ディクショナリのループ ---
  - name: 環境変数の設定
    lineinfile:
      path: /etc/environment
      line: "{{ item.key }}={{ item.value }}"
    with_dict:
      PATH: /usr/local/bin:/usr/bin:/bin
      JAVA_HOME: /usr/lib/jvm/java-17
      APP_ENV: production

  # --- with_nested / product: 直積ループ ---
  - name: ユーザーとグループの組み合わせで設定
    command: "adduser {{ item.0 }} {{ item.1 }}"
    with_nested:
      - ['alice', 'bob']
      - ['developers', 'testers']

  # --- until: リトライループ ---
  - name: アプリが起動するまで待機
    uri:
      url: http://localhost:8080/health
      status_code: 200
    register: health_check
    until: health_check.status == 200
    retries: 10    # 最大10回
    delay: 5       # 5秒間隔

9. ロールとAnsible Galaxy

9.1 ロールの概要

ロール (Role) はプレイブックの要素(タスク、変数、ハンドラ、テンプレート等)を再利用可能な単位にパッケージ化する仕組みです。

9.2 ロールのディレクトリ構造

roles/
└── nginx/                    # ロール名
    ├── tasks/
    │   ├── main.yml          # メインタスク(必須)
    │   ├── install.yml       # インストールタスク(include 用)
    │   └── configure.yml     # 設定タスク(include 用)
    ├── handlers/
    │   └── main.yml          # ハンドラ定義
    ├── templates/
    │   ├── nginx.conf.j2     # Jinja2 テンプレート
    │   └── vhost.conf.j2
    ├── files/
    │   ├── ssl/
    │   │   ├── server.crt    # 静的ファイル
    │   │   └── server.key
    │   └── index.html
    ├── vars/
    │   └── main.yml          # ロール変数(高優先度)
    ├── defaults/
    │   └── main.yml          # デフォルト変数(低優先度・上書き可)
    ├── meta/
    │   └── main.yml          # ロールのメタデータと依存関係
    ├── tests/
    │   ├── inventory
    │   └── test.yml          # テスト用プレイブック
    └── README.md

9.3 ロールの作成

# ロールのスケルトン生成
ansible-galaxy role init roles/nginx
# 出力:
# - Role roles/nginx was created successfully

ansible-galaxy role init roles/mysql
ansible-galaxy role init roles/common

roles/common/defaults/main.yml

---
# デフォルト変数(優先度が低く、インベントリやプレイブックで上書き可能)
common_packages:
  - vim
  - curl
  - wget
  - git
  - net-tools
  - htop
  - unzip
  - tmux

common_timezone: Asia/Tokyo

ntp_servers:
  - 0.jp.pool.ntp.org
  - 1.jp.pool.ntp.org
  - 2.jp.pool.ntp.org

sysctl_params:
  net.ipv4.ip_forward: 0
  net.core.somaxconn: 1024
  vm.swappiness: 10

disable_services:
  - bluetooth
  - cups

enable_services:
  - cron
  - rsyslog

roles/common/tasks/main.yml

---
- name: 共通タスクの読み込み
  include_tasks: "{{ item }}"
  loop:
    - packages.yml
    - timezone.yml
    - sysctl.yml
    - ntp.yml
    - security.yml

roles/common/tasks/packages.yml

---
- name: apt キャッシュの更新
  apt:
    update_cache: yes
    cache_valid_time: 3600
  when: ansible_os_family == "Debian"

- name: 共通パッケージのインストール (Debian系)
  apt:
    name: "{{ common_packages }}"
    state: present
  when: ansible_os_family == "Debian"

- name: 共通パッケージのインストール (RedHat系)
  dnf:
    name: "{{ common_packages }}"
    state: present
  when: ansible_os_family == "RedHat"

- name: 不要なパッケージの削除
  apt:
    name:
      - telnet
      - rsh-client
    state: absent
  when: ansible_os_family == "Debian"

roles/nginx/defaults/main.yml

---
nginx_user: www-data
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_gzip: true

nginx_vhosts:
  - server_name: "{{ ansible_fqdn }}"
    root: /var/www/html
    port: 80
    ssl: false

ssl_enabled: false
ssl_certificate: /etc/ssl/certs/ssl-cert-snakeoil.pem
ssl_certificate_key: /etc/ssl/private/ssl-cert-snakeoil.key

roles/nginx/tasks/main.yml

---
- name: Nginx のインストール
  include_tasks: install.yml
  tags: [install]

- name: Nginx の設定
  include_tasks: configure.yml
  tags: [configure]

- name: Nginx の起動と有効化
  service:
    name: nginx
    state: started
    enabled: yes
  tags: [service]

roles/nginx/tasks/install.yml

---
- name: Nginx のインストール (Debian系)
  apt:
    name: nginx
    state: present
    update_cache: yes
  when: ansible_os_family == "Debian"
  notify: Nginx を再起動

- name: Nginx のインストール (RedHat系)
  dnf:
    name: nginx
    state: present
  when: ansible_os_family == "RedHat"
  notify: Nginx を再起動

roles/nginx/tasks/configure.yml

---
- name: Nginx メイン設定の配置
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    validate: '/usr/sbin/nginx -t -c %s'
  notify: Nginx の設定をリロード

- name: デフォルト設定の削除
  file:
    path: /etc/nginx/sites-enabled/default
    state: absent
  notify: Nginx の設定をリロード

- name: バーチャルホスト設定の配置
  template:
    src: vhost.conf.j2
    dest: "/etc/nginx/sites-available/{{ item.server_name }}.conf"
    owner: root
    group: root
    mode: '0644'
  loop: "{{ nginx_vhosts }}"
  notify: Nginx の設定をリロード

- name: バーチャルホストの有効化
  file:
    src: "/etc/nginx/sites-available/{{ item.server_name }}.conf"
    dest: "/etc/nginx/sites-enabled/{{ item.server_name }}.conf"
    state: link
  loop: "{{ nginx_vhosts }}"
  notify: Nginx の設定をリロード

roles/nginx/handlers/main.yml

---
- name: Nginx を再起動
  service:
    name: nginx
    state: restarted

- name: Nginx の設定をリロード
  service:
    name: nginx
    state: reloaded

roles/nginx/meta/main.yml

---
galaxy_info:
  author: yourname
  description: Nginx web server setup role
  license: MIT
  min_ansible_version: "2.14"
  platforms:
    - name: Ubuntu
      versions:
        - "20.04"
        - "22.04"
    - name: EL
      versions:
        - "8"
        - "9"
  galaxy_tags:
    - nginx
    - webserver

# ロールの依存関係(先に実行される)
dependencies:
  - role: common
  - role: firewall
    vars:
      firewall_allowed_ports:
        - 80
        - 443

9.4 ロールの使用

# site.yml
---
- name: 全サーバーの共通設定
  hosts: all
  become: true
  roles:
    - common  # シンプルな呼び出し

- name: Webサーバーの設定
  hosts: webservers
  become: true
  roles:
    # 変数を渡してロールを呼び出す
    - role: nginx
      vars:
        nginx_worker_processes: 4
        ssl_enabled: true
        nginx_vhosts:
          - server_name: app.example.com
            root: /var/www/app
            port: 80

    # include_role: タスクと同じレベルで条件分岐できる
    - role: ssl_certbot
      when: ssl_enabled | default(false)

- name: DBサーバーの設定
  hosts: dbservers
  become: true
  roles:
    - role: mysql
      vars:
        mysql_root_password: "{{ vault_mysql_root_password }}"
        mysql_databases:
          - name: appdb
            encoding: utf8mb4
        mysql_users:
          - name: appuser
            password: "{{ vault_mysql_app_password }}"
            priv: "appdb.*:ALL"

9.5 Ansible Galaxy

Ansible Galaxy はコミュニティが作成したロールやコレクションを共有するプラットフォームです。

# ロールの検索
ansible-galaxy role search nginx

# ロールのインストール
ansible-galaxy role install geerlingguy.nginx

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

# requirements.yml によるバルクインストール
cat > requirements.yml << 'EOF'
roles:
  - name: geerlingguy.nginx
    version: "3.2.0"
  - name: geerlingguy.mysql
    version: "3.3.1"
  - src: https://github.com/example/ansible-role-myapp.git
    scm: git
    version: main
    name: myapp

collections:
  - name: community.general
    version: ">=7.0.0"
  - name: community.mysql
    version: ">=3.0.0"
  - name: amazon.aws
    version: ">=7.0.0"
EOF

ansible-galaxy install -r requirements.yml
ansible-galaxy collection install -r requirements.yml

# インストール済みロールの一覧
ansible-galaxy role list
# 出力例:
# - geerlingguy.nginx, 3.2.0
# - geerlingguy.mysql, 3.3.1

# ロールの削除
ansible-galaxy role remove geerlingguy.nginx

# コレクション関連
ansible-galaxy collection install community.general
ansible-galaxy collection list
ansible-galaxy collection install community.general --upgrade

10. Ansible Vault

10.1 Vault の概要

Ansible Vault はパスワード、APIキー、証明書などの機密情報を AES-256 で暗号化して安全に管理する機能です。

10.2 ファイルの暗号化と復号

# ---- ファイルの暗号化 ----
# 新規暗号化ファイルの作成
ansible-vault create vars/secrets.yml
# パスワードの入力を求められる
# エディタが開き、内容を編集

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

# 暗号化後のファイルの内容(例):
# $ANSIBLE_VAULT;1.1;AES256
# 66386439353236336462626566653766353265656
# 32623261313762366534353938623165363...

# ---- ファイルの確認 ----
ansible-vault view vars/secrets.yml
# パスワードを入力すると平文で表示

# ---- ファイルの編集 ----
ansible-vault edit vars/secrets.yml
# パスワードを入力するとエディタが開く

# ---- ファイルの復号 ----
ansible-vault decrypt vars/secrets.yml

# ---- パスワードの変更 ----
ansible-vault rekey vars/secrets.yml
# 古いパスワードと新しいパスワードを入力

# ---- 文字列の暗号化(inline暗号化)----
ansible-vault encrypt_string 'MySecretPassword123' --name 'db_password'
# 出力:
# db_password: !vault |
#           $ANSIBLE_VAULT;1.1;AES256
#           66386439353236336462626566...
# Encryption successful

10.3 Vault パスワードの管理

# ---- パスワードファイルの使用 ----
# パスワードファイルの作成
echo 'VaultPassword123!' > ~/.vault_pass
chmod 600 ~/.vault_pass

# パスワードファイルを使ったプレイブック実行
ansible-playbook site.yml --vault-password-file ~/.vault_pass

# ansible.cfg での設定
# [defaults]
# vault_password_file = ~/.vault_pass

# ---- パスワードスクリプトの使用 ----
# 例: AWS Secrets Manager からパスワードを取得
cat > /usr/local/bin/vault_pass.sh << 'EOF'
#!/bin/bash
aws secretsmanager get-secret-value \
  --secret-id ansible/vault-password \
  --query SecretString \
  --output text
EOF
chmod +x /usr/local/bin/vault_pass.sh

ansible-playbook site.yml --vault-password-file /usr/local/bin/vault_pass.sh

# ---- 複数 Vault ID の使用 ----
# Vault ID でラベル付けして管理
ansible-vault encrypt vars/prod_secrets.yml --vault-id production@~/.vault_pass_prod
ansible-vault encrypt vars/dev_secrets.yml  --vault-id development@~/.vault_pass_dev

# 実行時に複数の Vault ID を指定
ansible-playbook site.yml \
  --vault-id production@~/.vault_pass_prod \
  --vault-id development@~/.vault_pass_dev

10.4 Vault の実践的な使用例

# vars/secrets.yml(暗号化前の内容)
---
vault_db_root_password: "MyRootPass123!"
vault_db_app_password:  "AppDbPass456!"
vault_api_key:          "sk-1234567890abcdef"
vault_ssl_cert: |
  -----BEGIN CERTIFICATE-----
  MIIDXTCCAkWgAwIBAgIJALxxx...
  -----END CERTIFICATE-----
vault_ssl_key: |
  -----BEGIN PRIVATE KEY-----
  MIIEvgIBADANBgkqhkiG9w0B...
  -----END PRIVATE KEY-----
# group_vars/all/vars.yml(平文)
---
db_host: db01.example.com
db_port: 3306
db_name: appdb

# group_vars/all/vault.yml(暗号化)
# ansible-vault encrypt group_vars/all/vault.yml
---
vault_db_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          66386439353236336462626566653766353265...
# プレイブックでの使用
- name: データベースのセットアップ
  hosts: dbservers
  vars_files:
    - vars/secrets.yml  # Vault で暗号化されたファイル
  tasks:
    - name: MySQL root パスワードの設定
      mysql_user:
        name: root
        password: "{{ vault_db_root_password }}"  # Vault 変数を参照
        state: present
# Vault 付きのプレイブック実行
ansible-playbook site.yml --ask-vault-pass
# または
ansible-playbook site.yml --vault-password-file ~/.vault_pass

11. 主要モジュール詳解

11.1 パッケージ管理モジュール

apt モジュール (Debian/Ubuntu)

tasks:
  # パッケージのインストール
  - name: Nginx のインストール
    apt:
      name: nginx
      state: present          # present/latest/absent/build-dep

  # 特定バージョンのインストール
  - name: Nginx 特定バージョンのインストール
    apt:
      name: "nginx=1.25.3-1~jammy"
      state: present
      allow_downgrade: yes    # ダウングレードを許可

  # キャッシュ更新付きインストール
  - name: パッケージをインストール(キャッシュ更新付き)
    apt:
      name: "{{ item }}"
      state: present
      update_cache: yes
      cache_valid_time: 3600  # 1時間キャッシュが有効なら更新しない
    loop: "{{ packages }}"

  # 複数パッケージの一括インストール
  - name: 開発ツールのインストール
    apt:
      name:
        - build-essential
        - python3-dev
        - libssl-dev
        - libffi-dev
      state: present

  # パッケージのアップグレード
  - name: セキュリティアップデートの適用
    apt:
      upgrade: safe           # safe/full/dist/no
      update_cache: yes

  # パッケージの削除(設定ファイルも含めて削除)
  - name: Apache の完全削除
    apt:
      name: apache2
      state: absent
      purge: yes              # 設定ファイルも削除

  # 未使用パッケージの削除
  - name: autoremove の実行
    apt:
      autoremove: yes
      autoclean: yes

dnf/yum モジュール (RHEL/CentOS/Rocky)

tasks:
  # DNF でのインストール(RHEL8+)
  - name: Nginx のインストール (RHEL系)
    dnf:
      name: nginx
      state: present

  # EPEL リポジトリ経由でのインストール
  - name: EPEL パッケージのインストール
    dnf:
      name:
        - epel-release
        - htop
        - jq
      state: present

  # グループインストール
  - name: 開発ツールグループのインストール
    dnf:
      name: "@Development Tools"
      state: present

  # リポジトリを指定してインストール
  - name: 特定リポジトリからインストール
    dnf:
      name: mypackage
      enablerepo: myrepo

  # ローカルRPMのインストール
  - name: ローカルRPMのインストール
    dnf:
      name: /tmp/mypackage-1.0.rpm
      state: present
      disable_gpg_check: yes

  # pip モジュール(Python パッケージ)
  - name: Python パッケージのインストール
    pip:
      name:
        - flask
        - sqlalchemy
        - gunicorn
      version: ">=2.0"
      state: present
      virtualenv: /opt/myapp/venv
      virtualenv_python: python3

11.2 サービス管理モジュール

service モジュール

tasks:
  # サービスの起動
  - name: Nginx の起動
    service:
      name: nginx
      state: started     # started/stopped/restarted/reloaded

  # 自動起動の設定
  - name: Nginx の自動起動有効化
    service:
      name: nginx
      enabled: yes

  # 起動と有効化を同時に
  - name: Nginx の起動と有効化
    service:
      name: nginx
      state: started
      enabled: yes

systemd モジュール(より高機能)

tasks:
  # systemd サービスの起動
  - name: アプリサービスの起動
    systemd:
      name: myapp
      state: started
      enabled: yes
      daemon_reload: yes   # systemd デーモンのリロード

  # systemd ユニットファイルの配置後にリロード
  - name: カスタムユニットファイルの配置
    copy:
      src: myapp.service
      dest: /etc/systemd/system/myapp.service
      mode: '0644'
    notify: systemd reload

  # ハンドラでの使用
  handlers:
    - name: systemd reload
      systemd:
        daemon_reload: yes

  # サービスのマスク(起動禁止)
  - name: 不要なサービスのマスク
    systemd:
      name: postfix
      masked: yes

  # タイマーの管理
  - name: systemd タイマーの有効化
    systemd:
      name: "{{ item }}"
      state: started
      enabled: yes
    loop:
      - backup.timer
      - cleanup.timer

11.3 ファイル操作モジュール

file モジュール

tasks:
  # ファイルの属性変更
  - name: 設定ファイルのパーミッション設定
    file:
      path: /etc/nginx/nginx.conf
      owner: root
      group: root
      mode: '0644'
      state: file     # file/directory/link/hard/touch/absent

  # ディレクトリの作成(parents=-p相当)
  - name: アプリディレクトリの作成
    file:
      path: "{{ item }}"
      state: directory
      owner: "{{ app_user }}"
      group: "{{ app_group }}"
      mode: '0755'
    loop:
      - /opt/myapp
      - /opt/myapp/logs
      - /opt/myapp/data
      - /var/log/myapp

  # シンボリックリンクの作成
  - name: シンボリックリンクの作成
    file:
      src: /opt/myapp/current/bin/myapp
      dest: /usr/local/bin/myapp
      state: link

  # ファイル/ディレクトリの削除
  - name: 一時ファイルの削除
    file:
      path: /tmp/deploy_artifacts
      state: absent

  # タイムスタンプの更新(touch)
  - name: ロックファイルの作成
    file:
      path: /var/run/deploy.lock
      state: touch
      mode: '0644'

copy モジュール

tasks:
  # コントロールノードからファイルをコピー
  - name: 設定ファイルのコピー
    copy:
      src: files/nginx.conf     # roles/nginx/files/ または playbook の files/ から
      dest: /etc/nginx/nginx.conf
      owner: root
      group: root
      mode: '0644'
      backup: yes               # 既存ファイルをバックアップ

  # 内容を直接指定してファイルを作成
  - name: シンプルな設定ファイルの作成
    copy:
      content: |
        # Auto-generated by Ansible - DO NOT EDIT MANUALLY
        APP_ENV={{ app_env }}
        APP_PORT={{ app_port }}
        DB_HOST={{ db_host }}
      dest: /etc/myapp/config
      owner: "{{ app_user }}"
      mode: '0640'

  # リモートホスト上でのファイルコピー
  - name: 設定ファイルのバックアップ
    copy:
      src: /etc/nginx/nginx.conf
      dest: /etc/nginx/nginx.conf.bak
      remote_src: yes           # マネージドノード上でのコピー

  # ディレクトリのコピー
  - name: ディレクトリの再帰的コピー
    copy:
      src: files/conf.d/        # 末尾の / でディレクトリの内容のみをコピー
      dest: /etc/nginx/conf.d/
      owner: root
      group: root
      mode: '0644'
      directory_mode: '0755'

template モジュール

tasks:
  # Jinja2 テンプレートのレンダリングとコピー
  - name: Nginx 設定テンプレートの配置
    template:
      src: templates/nginx.conf.j2   # .j2 は省略可能
      dest: /etc/nginx/nginx.conf
      owner: root
      group: root
      mode: '0644'
      backup: yes
      validate: '/usr/sbin/nginx -t -c %s'  # 配置前の検証コマンド
    notify: Nginx の設定をリロード

  # バーチャルホスト設定のテンプレート配置
  - name: バーチャルホスト設定の配置
    template:
      src: templates/vhost.conf.j2
      dest: "/etc/nginx/sites-available/{{ item.server_name }}.conf"
      owner: root
      group: root
      mode: '0644'
    loop: "{{ nginx_vhosts }}"
    notify: Nginx の設定をリロード

  # 環境変数ファイルのテンプレート
  - name: アプリの環境変数ファイル生成
    template:
      src: templates/app.env.j2
      dest: /etc/myapp/environment
      owner: "{{ app_user }}"
      group: "{{ app_group }}"
      mode: '0640'

lineinfile / blockinfile モジュール

tasks:
  # ファイルの特定行を追加/変更
  - name: /etc/hosts にエントリを追加
    lineinfile:
      path: /etc/hosts
      line: "192.168.1.100 app.example.com"
      state: present

  # 正規表現で行を検索して置換
  - name: SSH  PermitRootLogin を無効化
    lineinfile:
      path: /etc/ssh/sshd_config
      regexp: '^#?PermitRootLogin'
      line: 'PermitRootLogin no'
      state: present
      backup: yes
    notify: SSH の再起動

  # ブロック単位の挿入
  - name: sysctl 設定ブロックの追加
    blockinfile:
      path: /etc/sysctl.conf
      marker: "# {mark} ANSIBLE MANAGED BLOCK - Network Tuning"
      block: |
        net.core.somaxconn = 65535
        net.ipv4.tcp_max_syn_backlog = 65535
        net.ipv4.ip_local_port_range = 10000 65000
        net.core.netdev_max_backlog = 65536
      state: present
    notify: sysctl の適用

11.4 ユーザー管理モジュール

tasks:
  # ユーザーの作成
  - name: アプリケーションユーザーの作成
    user:
      name: "{{ item.name }}"
      uid: "{{ item.uid | default(omit) }}"
      comment: "{{ item.comment | default('') }}"
      shell: "{{ item.shell | default('/bin/bash') }}"
      home: "{{ item.home | default('/home/' + item.name) }}"
      groups: "{{ item.groups | default([]) }}"
      append: "{{ item.append | default(true) }}"  # グループの追加(上書きしない)
      password: "{{ item.password | default('!') | password_hash('sha512') }}"
      state: present
      create_home: yes
    loop: "{{ users }}"

  # サービスユーザー(ログインなし)の作成
  - name: Nginx 実行ユーザーの作成
    user:
      name: www-data
      system: yes             # システムユーザー
      shell: /usr/sbin/nologin
      home: /var/www
      create_home: no
      state: present

  # ユーザーの削除
  - name: 退職者アカウントの削除
    user:
      name: "{{ item }}"
      state: absent
      remove: yes             # ホームディレクトリも削除
      force: yes              # 実行中プロセスがあっても削除
    loop: "{{ removed_users }}"

  # グループの管理
  - name: アプリグループの作成
    group:
      name: "{{ item.name }}"
      gid: "{{ item.gid | default(omit) }}"
      state: present
    loop:
      - {name: developers, gid: 2001}
      - {name: operators,  gid: 2002}
      - {name: docker,     gid: 999}

  # SSH authorized_key の管理
  - name: SSH 公開鍵の設定
    authorized_key:
      user: "{{ item.user }}"
      key: "{{ item.key }}"
      state: present
      exclusive: "{{ item.exclusive | default(false) }}"
    loop: "{{ ssh_keys }}"
    # vars:
    # ssh_keys:
    #   - user: alice
    #     key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA..."
    #   - user: bob
    #     key: "{{ lookup('file', '~/.ssh/bob.pub') }}"

  # sudoers の設定
  - name: sudoers ファイルの設定
    copy:
      content: |
        # Ansible 管理ユーザー
        ansible ALL=(ALL) NOPASSWD: ALL
        # Operators グループ
        %operators ALL=(ALL) NOPASSWD: /usr/bin/systemctl, /usr/bin/journalctl
      dest: /etc/sudoers.d/ansible-managed
      owner: root
      group: root
      mode: '0440'
      validate: '/usr/sbin/visudo -cf %s'

11.5 コマンド実行モジュール

command モジュール(推奨)

tasks:
  # シンプルなコマンド実行
  - name: アプリのビルド
    command: make build
    args:
      chdir: /opt/myapp/src   # カレントディレクトリの変更

  # 実行結果の取得
  - name: 現在のカーネルバージョン取得
    command: uname -r
    register: kernel_version
    changed_when: false    # 状態変更なしとしてマーク

  - debug:
      msg: "カーネルバージョン: {{ kernel_version.stdout }}"

  # ファイルが存在しない場合のみ実行
  - name: アプリの初期化(未実行の場合のみ)
    command: /opt/myapp/bin/init.sh
    args:
      creates: /opt/myapp/.initialized  # このファイルが存在すればスキップ

shell モジュール(パイプ、リダイレクト等が必要な場合)

tasks:
  # パイプを使ったコマンド
  - name: ログの集計
    shell: "grep ERROR /var/log/myapp.log | wc -l"
    register: error_count
    changed_when: false

  # ヒアドキュメントの使用
  - name: SQL スクリプトの実行
    shell: |
      mysql -u root -p{{ db_password }} << 'EOF'
      CREATE DATABASE IF NOT EXISTS appdb CHARACTER SET utf8mb4;
      GRANT ALL ON appdb.* TO 'appuser'@'localhost';
      FLUSH PRIVILEGES;
      EOF
    args:
      executable: /bin/bash
    no_log: true    # ログに出力しない(パスワード保護)

  # 環境変数の設定
  - name: 環境変数付きでコマンドを実行
    shell: "./deploy.sh"
    args:
      chdir: /opt/myapp
      executable: /bin/bash
    environment:
      APP_ENV: production
      DB_HOST: "{{ db_host }}"
      API_KEY: "{{ vault_api_key }}"
    no_log: true

raw モジュール(Python 不要の環境向け)

tasks:
  # Python がインストールされていない最小構成の環境向け
  - name: Python のインストール(Bootstrap)
    raw: "apt-get install -y python3"
    become: yes

11.6 その他の重要モジュール

uri モジュール(HTTP リクエスト)

tasks:
  # GET リクエスト
  - name: ヘルスチェック
    uri:
      url: "http://{{ ansible_host }}:8080/health"
      method: GET
      status_code: 200
      return_content: yes
    register: health_response

  # POST リクエスト(API 呼び出し)
  - name: デプロイ通知
    uri:
      url: "{{ slack_webhook_url }}"
      method: POST
      body_format: json
      body:
        text: "{{ inventory_hostname }} のデプロイが完了しました"
      status_code: 200
    no_log: true

  # 認証付き API 呼び出し
  - name: API からデータを取得
    uri:
      url: "https://api.example.com/v1/config"
      method: GET
      headers:
        Authorization: "Bearer {{ vault_api_token }}"
        Content-Type: "application/json"
      status_code: 200
      return_content: yes
    register: api_response
    no_log: true

  - set_fact:
      api_config: "{{ api_response.json }}"

get_url モジュール(ファイルダウンロード)

tasks:
  - name: アプリのバイナリダウンロード
    get_url:
      url: "https://releases.example.com/myapp/{{ app_version }}/myapp-linux-amd64"
      dest: "/usr/local/bin/myapp"
      mode: '0755'
      checksum: "sha256:abc123..."   # チェックサム検証
      timeout: 60

  - name: RPM パッケージのダウンロード
    get_url:
      url: "https://packages.example.com/myapp-{{ version }}.rpm"
      dest: "/tmp/myapp.rpm"
      owner: root
      group: root
      mode: '0644'

cron モジュール

tasks:
  - name: バックアップ cron ジョブの設定
    cron:
      name: "Daily Database Backup"    # ジョブの識別名
      minute: "0"
      hour: "2"
      day: "*"
      month: "*"
      weekday: "*"
      job: "/opt/scripts/backup.sh >> /var/log/backup.log 2>&1"
      user: backup
      state: present

  - name: 古いログの定期削除
    cron:
      name: "Cleanup old logs"
      minute: "30"
      hour: "3"
      job: "find /var/log/myapp -name '*.log' -mtime +30 -delete"
      state: present

  - name: cron ジョブの削除
    cron:
      name: "Old Job"
      state: absent

git モジュール

tasks:
  - name: アプリのソースコードのデプロイ
    git:
      repo: "https://github.com/example/myapp.git"
      dest: /opt/myapp/releases/{{ deploy_version }}
      version: "{{ git_tag | default('main') }}"
      depth: 1          # shallow clone
      force: yes
      accept_hostkey: yes
    register: git_result

  - name: デプロイしたバージョンのシンボリックリンク
    file:
      src: "/opt/myapp/releases/{{ deploy_version }}"
      dest: /opt/myapp/current
      state: link
    when: git_result.changed

12. エラーハンドリング

12.1 エラーハンドリングの基本

tasks:
  # ---- ignore_errors: エラーを無視して続行 ----
  - name: オプショナルなサービスの停止
    service:
      name: old_service
      state: stopped
    ignore_errors: yes    # サービスが存在しなくてもエラーにしない

  # ---- failed_when: 失敗条件のカスタマイズ ----
  - name: アプリの起動確認
    command: /opt/myapp/bin/check_status.sh
    register: status_result
    failed_when:
      - status_result.rc != 0
      - "'CRITICAL' in status_result.stdout"

  # ---- changed_when: 変更判定のカスタマイズ ----
  - name: バージョン確認(変更なし)
    command: myapp --version
    register: version_output
    changed_when: false   # 常に "ok" と表示(changed にしない)

  # ---- any_errors_fatal: 1台でも失敗したら全体を停止 ----
  # プレイレベルで設定
  - hosts: dbservers
    any_errors_fatal: true
    tasks:
      - name: DB マイグレーション
        command: /opt/app/manage.py migrate

12.2 block/rescue/always によるエラーハンドリング

tasks:
  - name: アプリデプロイのエラーハンドリング
    block:
      # メイン処理
      - name: デプロイ前のメンテナンスモード有効化
        uri:
          url: "http://localhost/api/maintenance"
          method: POST
          body_format: json
          body: {enabled: true}

      - name: アプリの停止
        service:
          name: myapp
          state: stopped

      - name: アプリのアップデート
        git:
          repo: "{{ app_repo }}"
          dest: "{{ app_dir }}"
          version: "{{ app_version }}"

      - name: データベースのマイグレーション
        command: /opt/myapp/manage.py migrate
        args:
          chdir: "{{ app_dir }}"

      - name: アプリの起動
        service:
          name: myapp
          state: started

      - name: メンテナンスモードの無効化
        uri:
          url: "http://localhost/api/maintenance"
          method: POST
          body_format: json
          body: {enabled: false}

    rescue:
      # エラー発生時の回復処理
      - name: エラーログの取得
        command: journalctl -u myapp -n 50
        register: error_log

      - name: エラーレポートの送信
        uri:
          url: "{{ slack_webhook }}"
          method: POST
          body_format: json
          body:
            text: |
              :red_circle: {{ inventory_hostname }} でデプロイに失敗しました
              エラー: {{ ansible_failed_task.name }}
              ログ: {{ error_log.stdout[-500:] }}

      - name: 前バージョンへのロールバック
        git:
          repo: "{{ app_repo }}"
          dest: "{{ app_dir }}"
          version: "{{ previous_version }}"

      - name: アプリの再起動(前バージョン)
        service:
          name: myapp
          state: restarted

      - name: メンテナンスモードの解除
        uri:
          url: "http://localhost/api/maintenance"
          method: POST
          body_format: json
          body: {enabled: false}

    always:
      # 成功・失敗に関わらず必ず実行
      - name: デプロイ結果のログ記録
        lineinfile:
          path: /var/log/deploy_history.log
          line: >
            {{ ansible_date_time.iso8601 }}
            host={{ inventory_hostname }}
            version={{ app_version }}
            result={{ ansible_failed_task is defined | ternary('FAILED', 'SUCCESS') }}
          create: yes

12.3 リトライとタイムアウト

tasks:
  # ---- retries/delay: リトライ設定 ----
  - name: アプリの起動確認(リトライ付き)
    uri:
      url: "http://{{ ansible_host }}:8080/health"
      status_code: 200
    register: health_check
    until: health_check.status == 200
    retries: 12
    delay: 5
    # 最大 60秒 (12回 × 5秒) 待機

  # ---- wait_for: ポート/ファイル/条件の待機 ----
  - name: MySQL の起動待機
    wait_for:
      host: "{{ ansible_host }}"
      port: 3306
      timeout: 60
      state: started
      msg: "MySQL が起動しませんでした"

  - name: アプリのソケット待機
    wait_for:
      path: /var/run/myapp/myapp.sock
      timeout: 30
      state: present

  - name: ファイルの消滅を待機(プロセスの完了確認)
    wait_for:
      path: /tmp/migration.lock
      state: absent
      timeout: 300

  # ---- async/poll: 非同期実行 ----
  - name: 時間のかかる処理を非同期実行
    command: /opt/scripts/long_running_task.sh
    async: 600      # 最大待機時間(秒)
    poll: 0         # 結果を後でチェック(即座に次のタスクへ)
    register: long_task

  # 他のタスクをここで実行...

  - name: 非同期タスクの完了確認
    async_status:
      jid: "{{ long_task.ansible_job_id }}"
    register: job_result
    until: job_result.finished
    retries: 60
    delay: 10

12.4 デバッグとトラブルシューティング

tasks:
  # debug モジュールによる変数の確認
  - name: 変数の内容を確認
    debug:
      var: nginx_vhosts           # 変数名を直接指定

  - name: メッセージを表示
    debug:
      msg: "現在のユーザー: {{ ansible_user_id }}, 環境: {{ app_env }}"
      verbosity: 2    # -vv 以上の時のみ表示

  # assert モジュールによる前提条件の確認
  - name: 前提条件のアサーション
    assert:
      that:
        - ansible_memtotal_mb >= 2048
        - ansible_processor_vcpus >= 2
        - app_version is defined
        - app_version is match("^[0-9]+\.[0-9]+\.[0-9]+$")
      fail_msg: "サーバーの要件を満たしていません: メモリ {{ ansible_memtotal_mb }}MB, CPU {{ ansible_processor_vcpus }}コア"
      success_msg: "前提条件を満たしています"

12.5 実行オプションとデバッグフラグ

# ---- 詳細出力レベル ----
ansible-playbook site.yml -v      # 基本的な詳細情報
ansible-playbook site.yml -vv     # より詳細なデバッグ
ansible-playbook site.yml -vvv    # SSH 接続情報も表示
ansible-playbook site.yml -vvvv   # 最大詳細(ネットワークデバッグ)

# ---- ドライラン ----
ansible-playbook site.yml --check
# 出力例:
# PLAY [Webサーバーのセットアップ] ************************************
# TASK [Nginx のインストール] *************************************
# ok: [web01]  ← 変更なし
# TASK [Nginx の設定ファイルを配置] *******************************
# changed: [web01]  ← この変更が行われる予定
# PLAY RECAP ****************************************************
# web01: ok=5 changed=1 unreachable=0 failed=0

# ---- 差分表示 ----
ansible-playbook site.yml --check --diff
# 出力例(ファイルの差分):
# --- before: /etc/nginx/nginx.conf
# +++ after: /etc/nginx/nginx.conf
# @@ -1,5 +1,6 @@
#  worker_processes auto;
# +worker_rlimit_nofile 65535;
#  events {

# ---- 特定タスクから開始 ----
ansible-playbook site.yml --start-at-task="Nginx の設定ファイルを配置"

# ---- 特定タグのみ実行 ----
ansible-playbook site.yml --tags "install,configure"
ansible-playbook site.yml --skip-tags "debug"

# ---- 対象ホストの絞り込み ----
ansible-playbook site.yml --limit "web01,web02"
ansible-playbook site.yml --limit "webservers:!web03"  # web03 以外

# ---- ステップ実行 ----
ansible-playbook site.yml --step
# 各タスクの前に実行確認を求める

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

# ---- リストオプション ----
ansible-playbook site.yml --list-tasks   # タスク一覧
ansible-playbook site.yml --list-hosts   # 対象ホスト一覧
ansible-playbook site.yml --list-tags    # タグ一覧

13. べき等性 (Idempotency)

13.1 べき等性とは

べき等性(Idempotency)とは、同じ操作を何度実行しても結果が変わらないという性質です。Ansible では、すでに望ましい状態であれば変更を加えません。

初回実行:  状態A (古い) → 状態B (望ましい) ... changed: 1
2回目実行: 状態B (望ましい) → 状態B (変化なし) ... ok: 1
3回目実行: 状態B (望ましい) → 状態B (変化なし) ... ok: 1

13.2 べき等性の実例

# ---- べき等なタスク ----

# ① apt: state=present → インストール済みなら何もしない
- name: Nginx インストール(べき等)
  apt:
    name: nginx
    state: present   # ← "install" ではなく "present(現在の状態)"

# ② user: → ユーザーが存在すれば何もしない
- name: ユーザー作成(べき等)
  user:
    name: deploy
    state: present

# ③ template: → ファイルが同じ内容なら何もしない
- name: 設定ファイルの配置(べき等)
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf

# ④ file: → ディレクトリが存在すれば何もしない
- name: ディレクトリ作成(べき等)
  file:
    path: /opt/myapp
    state: directory

# ---- べき等でないタスク(注意が必要)----

# ✗ command/shell は基本的にべき等でない
- name: ファイルの追記(非べき等!)
  shell: echo "new entry" >> /etc/hosts
  # 毎回実行されるたびに追記される

# ✓ 対策: creates/removes や lineinfile を使う
- name: /etc/hosts へのエントリ追加(べき等)
  lineinfile:
    path: /etc/hosts
    line: "192.168.1.100 app.example.com"
    state: present

# ✗ 初期化スクリプトの複数回実行(非べき等!)
- name: DB 初期化(非べき等!)
  command: /opt/app/init_db.sh

# ✓ 対策: creates を使ってフラグファイルで制御
- name: DB 初期化(べき等化)
  command: /opt/app/init_db.sh
  args:
    creates: /opt/app/.db_initialized  # このファイルが存在すればスキップ

- name: 初期化完了フラグの作成
  file:
    path: /opt/app/.db_initialized
    state: touch

13.3 check モードとべき等性の確認

# check モードでの確認
ansible-playbook site.yml --check
# すべてのタスクが "ok" であれば、現在の状態が望ましい状態と一致している

# 実行例(初回):
# PLAY RECAP ****
# web01: ok=4 changed=6 unreachable=0 failed=0

# 実行例(2回目以降:べき等が保たれている場合):
# PLAY RECAP ****
# web01: ok=10 changed=0 unreachable=0 failed=0

14. IaCベストプラクティス

14.1 プロジェクト構造のベストプラクティス

ansible-project/
├── ansible.cfg                    # プロジェクト設定
├── requirements.yml               # 依存コレクション/ロール
│
├── inventory/
│   ├── production/
│   │   ├── hosts.yml              # 本番インベントリ
│   │   ├── group_vars/
│   │   │   ├── all/
│   │   │   │   ├── vars.yml       # 共通変数
│   │   │   │   └── vault.yml      # 暗号化変数
│   │   │   ├── webservers.yml
│   │   │   └── dbservers.yml
│   │   └── host_vars/
│   │       ├── web01.yml
│   │       └── db01.yml
│   └── staging/
│       ├── hosts.yml
│       └── group_vars/
│           └── all.yml
│
├── playbooks/
│   ├── site.yml                   # マスタープレイブック
│   ├── webservers.yml             # Webサーバー専用
│   ├── dbservers.yml              # DBサーバー専用
│   └── maintenance/
│       ├── update.yml             # OSアップデート
│       └── backup.yml             # バックアップ
│
├── roles/
│   ├── common/                    # 全サーバー共通
│   ├── nginx/                     # Nginx ロール
│   ├── mysql/                     # MySQL ロール
│   └── myapp/                     # アプリロール
│
├── library/                       # カスタムモジュール
├── filter_plugins/                # カスタムフィルタ
└── tests/
    ├── molecule/                  # Molecule テスト
    └── integration/               # 統合テスト

14.2 変数管理のベストプラクティス

# ✓ ロールの defaults/ でデフォルト値を定義(上書き可能)
# roles/nginx/defaults/main.yml
nginx_port: 80
nginx_ssl_port: 443
nginx_worker_processes: auto

# ✓ 環境固有の値は group_vars/host_vars で上書き
# inventory/production/group_vars/webservers.yml
nginx_worker_processes: 4

# ✓ 機密情報は必ず Vault で暗号化
# group_vars/all/vault.yml(暗号化済み)
vault_db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  ...

# ✓ Vault 変数には vault_ プレフィックスを付ける
# group_vars/all/vars.yml(平文)
db_password: "{{ vault_db_password }}"  # Vault 変数を通常変数でラップ

14.3 セキュリティのベストプラクティス

# ---- 1. no_log でパスワードを保護 ----
- name: MySQL パスワードの設定
  mysql_user:
    name: root
    password: "{{ db_root_password }}"
    state: present
  no_log: true    # タスクの出力をログに残さない

# ---- 2. become は必要な時のみ ----
# プレイレベルで become: false をデフォルトにして
# 必要なタスクのみで become: true を指定

- hosts: webservers
  become: false   # デフォルトは非昇格
  tasks:
    - name: ヘルスチェック(昇格不要)
      uri:
        url: http://localhost/health

    - name: Nginx の再起動(昇格必要)
      service:
        name: nginx
        state: restarted
      become: true   # このタスクのみ昇格

# ---- 3. validate で設定ファイルの検証 ----
- name: nginx.conf の配置
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    validate: '/usr/sbin/nginx -t -c %s'

- name: sudoers の配置
  template:
    src: sudoers.j2
    dest: /etc/sudoers.d/myconf
    validate: '/usr/sbin/visudo -cf %s'

# ---- 4. 最小権限の原則 ----
# SSH 鍵の設定
- name: Ansible  SSH 鍵の設定
  authorized_key:
    user: ansible
    key: "{{ lookup('file', 'files/ansible.pub') }}"
    exclusive: yes    # 他の鍵を排除(管理を明確化)

14.4 パフォーマンスのベストプラクティス

# ansible.cfg でのパフォーマンス最適化

[defaults]
# 並列実行数を増やす(サーバー台数に応じて調整)
forks = 50

# ファクトキャッシュの有効化(再収集を省略)
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400

# gathering の最適化
gathering = smart   # キャッシュが有効ならファクト収集をスキップ

[ssh_connection]
# SSH 接続のパイプライン化(パフォーマンス大幅向上)
pipelining = True

# ControlMaster による接続の再利用
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s
# プレイブック内でのパフォーマンス最適化

# ファクト収集が不要な場合は無効化
- hosts: all
  gather_facts: false
  tasks: ...

# 必要なファクトのみ収集
- hosts: all
  gather_facts: true
  gather_subset:
    - min           # 最小限のファクトのみ
    - hardware      # ハードウェア情報も取得
  tasks: ...

# 並列実行の制御(ローリングアップデート)
- hosts: webservers
  serial: "30%"     # 一度に30%ずつ更新(Canary デプロイ)
  max_fail_percentage: 10
  tasks: ...

14.5 コード品質のベストプラクティス

# ---- ansible-lint によるコード品質チェック ----
pip install ansible-lint

# プレイブックの linting
ansible-lint site.yml

# プロジェクト全体の linting
ansible-lint

# 出力例:
# [yaml] Too many blank lines (2 > 1)
# site.yml:15
# [package-latest] Package installed with 'latest' not 'present'
# roles/nginx/tasks/main.yml:12

# ---- .ansible-lint 設定ファイル ----
cat > .ansible-lint << 'EOF'
---
profile: moderate  # minimal/basic/moderate/safety/shared/production

# 無効化するルール
skip_list:
  - "yaml[line-length]"  # 行長チェックを無効化

# カスタムルールの追加
rulesdir:
  - ./custom_lint_rules/
EOF

# ---- yamllint によるYAML構文チェック ----
pip install yamllint

cat > .yamllint << 'EOF'
extends: default
rules:
  line-length:
    max: 120
  truthy:
    allowed-values: ['true', 'false', 'yes', 'no']
EOF

yamllint site.yml
yamllint roles/

14.6 テストのベストプラクティス

# ---- Molecule によるロールのテスト ----
pip install molecule molecule-docker

# ロールのテスト初期化
cd roles/nginx
molecule init scenario --driver-name docker

# テストシナリオの実行
molecule test  # full test: create → converge → verify → destroy

# 個別ステップの実行
molecule create     # テスト用コンテナを起動
molecule converge   # ロールをコンテナに適用
molecule verify     # テストを実行
molecule destroy    # コンテナを削除

# molecule/default/molecule.yml
# molecule/default/molecule.yml
---
dependency:
  name: galaxy

driver:
  name: docker

platforms:
  - name: ubuntu-22-04
    image: geerlingguy/docker-ubuntu2204-ansible:latest
    pre_build_image: true
  - name: rockylinux-9
    image: geerlingguy/docker-rockylinux9-ansible:latest
    pre_build_image: true

provisioner:
  name: ansible

verifier:
  name: ansible
# molecule/default/verify.yml
---
- name: Nginx ロールの検証
  hosts: all
  tasks:
    - name: Nginx が起動しているか確認
      command: systemctl is-active nginx
      changed_when: false

    - name: ポート 80  Listen しているか確認
      wait_for:
        host: localhost
        port: 80
        timeout: 5

    - name: HTTP レスポンスの確認
      uri:
        url: http://localhost/
        status_code: 200

14.7 Git によるバージョン管理

# ---- .gitignore ----
cat > .gitignore << 'EOF'
# 暗号化されていない機密情報
*secret*
*password*
!*vault*    # vault ファイルは含める(暗号化済み)

# ローカル設定
*.retry
.ansible/

# ログファイル
*.log

# ファクトキャッシュ
.fact_cache/
/tmp/

# Python
__pycache__/
*.pyc
.venv/
EOF

# コミット前のチェック
ansible-lint
yamllint .
ansible-playbook --syntax-check site.yml

# pre-commit フックの設定
pip install pre-commit

cat > .pre-commit-config.yaml << 'EOF'
repos:
  - repo: https://github.com/ansible/ansible-lint
    rev: v24.2.0
    hooks:
      - id: ansible-lint
  - repo: https://github.com/adrienverge/yamllint
    rev: v1.35.1
    hooks:
      - id: yamllint
EOF

pre-commit install

15. Ansibleと他ツールの比較

15.1 主要構成管理ツールの比較

項目AnsiblePuppetChefSaltStackTerraform
アーキテクチャエージェントレスエージェント型エージェント型エージェント型/レスエージェントレス
通信プロトコルSSH/WinRMTLS/MQHTTPSZeroMQ/HTTPSHTTPS API
設定言語YAMLPuppet DSLRuby (DSL)YAML/PythonHCL
実行モデルPushPullPullPush/PullPush
学習コスト
べき等性○(設計依存)◎(標準)◎(標準)○(設計依存)◎(標準)
Windows 対応△(WinRM)○(プロバイダ依存)
コミュニティ非常に大きい大きい中程度中程度非常に大きい
商用版AAP (Red Hat)Puppet EnterpriseChef AutomateSaltStack EnterpriseTerraform Cloud
主な用途構成管理/デプロイ構成管理構成管理構成管理/オーケストレーションインフラプロビジョニング

15.2 Ansible vs Terraform の使い分け

┌─────────────────────────────────────────────────────────┐
│              インフラ管理の役割分担                         │
│                                                         │
│  Terraform (プロビジョニング)                             │
│  ┌────────────────────────────────────────────────┐    │
│  │  VM, Network, Storage, DNS, LB, Security Group │    │
│  │  → 「リソースの作成・削除」が得意                  │    │
│  └────────────────────────────────────────────────┘    │
│                         ↓                              │
│  Ansible (構成管理)                                      │
│  ┌────────────────────────────────────────────────┐    │
│  │  OS設定, パッケージ, サービス, デプロイ              │    │
│  │  → 「作成済みサーバーの設定変更」が得意              │    │
│  └────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────┘

典型的なワークフロー:
1. Terraform でインフラを作成 (EC2, VPC, etc.)
2. Ansible で OS/ミドルウェアを設定
3. Ansible でアプリをデプロイ

15.3 Push型 vs Pull型

観点Push型 (Ansible)Pull型 (Puppet/Chef)
設定適用のタイミング管理者が実行時エージェントが定期的に
新規サーバーの設定手動またはCI/CDで実行エージェントが自動で取得
スケールコントロールノードに負荷集中分散処理が得意
ネットワーク障害適用不可(接続できない)キャッシュで対応可能
セキュリティSSHのみ開放でOKエージェントのポートが必要
デバッグ実行時のログが直接確認可能エージェントのログを確認

16. 実践的なプレイブック例

16.1 LAMP スタックの完全セットアップ

# playbooks/lamp_stack.yml
---
- name: LAMP スタックのセットアップ
  hosts: webservers
  become: true
  vars:
    php_version: "8.2"
    mysql_root_password: "{{ vault_mysql_root_password }}"
    app_db_name: myapp
    app_db_user: myapp_user
    app_db_password: "{{ vault_app_db_password }}"
    app_domain: "{{ inventory_hostname }}.example.com"
    app_deploy_path: /var/www/myapp

  pre_tasks:
    - name: apt キャッシュの更新
      apt:
        update_cache: yes
        cache_valid_time: 3600

  tasks:
    # --- Apache のセットアップ ---
    - name: Apache のインストール
      apt:
        name:
          - apache2
          - apache2-utils
        state: present

    - name: Apache モジュールの有効化
      apache2_module:
        name: "{{ item }}"
        state: present
      loop:
        - rewrite
        - ssl
        - headers
      notify: Apache の再起動

    # --- PHP のセットアップ ---
    - name: PHP PPA の追加
      apt_repository:
        repo: "ppa:ondrej/php"
        state: present
        update_cache: yes

    - name: PHP と必要な拡張モジュールのインストール
      apt:
        name:
          - "php{{ php_version }}"
          - "php{{ php_version }}-mysql"
          - "php{{ php_version }}-curl"
          - "php{{ php_version }}-gd"
          - "php{{ php_version }}-mbstring"
          - "php{{ php_version }}-xml"
          - "php{{ php_version }}-zip"
          - "php{{ php_version }}-fpm"
          - libapache2-mod-fcgid
        state: present

    # --- MySQL のセットアップ ---
    - name: MySQL のインストール
      apt:
        name:
          - mysql-server
          - python3-pymysql
        state: present

    - name: MySQL サービスの起動と有効化
      service:
        name: mysql
        state: started
        enabled: yes

    - name: MySQL root パスワードの設定
      mysql_user:
        name: root
        host: localhost
        password: "{{ mysql_root_password }}"
        login_unix_socket: /var/run/mysqld/mysqld.sock
        state: present
      no_log: true

    - name: アプリ用データベースの作成
      mysql_db:
        name: "{{ app_db_name }}"
        encoding: utf8mb4
        collation: utf8mb4_unicode_ci
        login_user: root
        login_password: "{{ mysql_root_password }}"
        state: present
      no_log: true

    - name: アプリ用 MySQL ユーザーの作成
      mysql_user:
        name: "{{ app_db_user }}"
        host: localhost
        password: "{{ app_db_password }}"
        priv: "{{ app_db_name }}.*:ALL"
        login_user: root
        login_password: "{{ mysql_root_password }}"
        state: present
      no_log: true

    # --- アプリのデプロイ ---
    - name: アプリディレクトリの作成
      file:
        path: "{{ app_deploy_path }}"
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'

    - name: Apache バーチャルホスト設定
      template:
        src: templates/apache_vhost.conf.j2
        dest: "/etc/apache2/sites-available/{{ app_domain }}.conf"
        owner: root
        group: root
        mode: '0644'
      notify: Apache の設定をリロード

    - name: バーチャルホストの有効化
      command: "a2ensite {{ app_domain }}"
      args:
        creates: "/etc/apache2/sites-enabled/{{ app_domain }}.conf"
      notify: Apache の設定をリロード

    - name: デフォルトサイトの無効化
      command: a2dissite 000-default
      args:
        removes: /etc/apache2/sites-enabled/000-default.conf
      notify: Apache の設定をリロード

    - name: アプリの設定ファイル生成
      template:
        src: templates/app_config.php.j2
        dest: "{{ app_deploy_path }}/config.php"
        owner: www-data
        group: www-data
        mode: '0640'
      no_log: true

  handlers:
    - name: Apache の再起動
      service:
        name: apache2
        state: restarted

    - name: Apache の設定をリロード
      service:
        name: apache2
        state: reloaded

  post_tasks:
    - name: Apache のステータス確認
      command: apache2ctl configtest
      changed_when: false

    - name: デプロイ完了の確認
      uri:
        url: "http://{{ ansible_default_ipv4.address }}/health.php"
        status_code: 200
      register: health_check
      retries: 5
      delay: 3
      until: health_check.status == 200

16.2 ローリングアップデートの実装

# playbooks/rolling_update.yml
---
- name: ローリングアップデート
  hosts: webservers
  become: true
  serial: 1          # 1台ずつ更新
  max_fail_percentage: 0  # 1台でも失敗したら停止

  vars:
    app_version: "{{ new_version | mandatory }}"
    lb_host: lb01

  tasks:
    # 1. ロードバランサーから切り離す
    - name: LB からサーバーを切り離す
      uri:
        url: "http://{{ lb_host }}/api/servers/{{ inventory_hostname }}/disable"
        method: POST
        status_code: 200
      delegate_to: "{{ lb_host }}"

    - name: 既存接続の切断待機
      pause:
        seconds: 30

    # 2. アプリの停止
    - name: アプリの停止
      service:
        name: myapp
        state: stopped

    # 3. アップデートの実行
    - name: 新バージョンのダウンロード
      get_url:
        url: "https://releases.example.com/myapp/{{ app_version }}/myapp.tar.gz"
        dest: "/tmp/myapp_{{ app_version }}.tar.gz"
        checksum: "{{ release_checksum }}"

    - name: バックアップの作成
      copy:
        src: /opt/myapp/current/
        dest: "/opt/myapp/backup_{{ ansible_date_time.epoch }}/"
        remote_src: yes

    - name: 新バージョンの展開
      unarchive:
        src: "/tmp/myapp_{{ app_version }}.tar.gz"
        dest: /opt/myapp/releases/
        remote_src: yes

    - name: シンボリックリンクの更新
      file:
        src: "/opt/myapp/releases/myapp-{{ app_version }}"
        dest: /opt/myapp/current
        state: link
        force: yes

    # 4. アプリの起動と確認
    - name: アプリの起動
      service:
        name: myapp
        state: started

    - name: アプリの起動確認
      uri:
        url: "http://localhost:8080/health"
        status_code: 200
      register: health
      until: health.status == 200
      retries: 10
      delay: 5

    # 5. ロードバランサーに復帰
    - name: LB にサーバーを復帰
      uri:
        url: "http://{{ lb_host }}/api/servers/{{ inventory_hostname }}/enable"
        method: POST
        status_code: 200
      delegate_to: "{{ lb_host }}"

    - name: 一時ファイルの削除
      file:
        path: "/tmp/myapp_{{ app_version }}.tar.gz"
        state: absent

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

17.1 よくある問題と解決策

問題原因解決策
UNREACHABLE エラーSSH 接続失敗SSH設定、ファイアウォール、ホスト名解決を確認
Permission deniedsudo設定が不十分sudoers 設定、become設定を確認
Python not foundPythonが未インストールraw モジュールでPythonをインストール
Module not foundコレクションが未インストールansible-galaxy collection install を実行
Template errorJinja2 構文エラー--check-vv で詳細確認
fact not foundファクト収集失敗gather_facts: true-vvv で確認
Vault decrypt failedパスワード不一致Vault パスワードを確認
host_key_checking エラー未知のホスト鍵known_hosts への追加または host_key_checking=False

17.2 デバッグコマンド集

# ---- 接続テスト ----
# SSH 直接接続テスト
ssh -i ~/.ssh/ansible_ed25519 -v ansible@web01

# Ansible 経由の接続テスト
ansible web01 -m ping -vvv

# ---- 変数とファクトの確認 ----
# ファクトの確認
ansible web01 -m setup | python3 -m json.tool | less

# 特定ファクトの確認
ansible web01 -m setup -a "filter=ansible_network_interfaces"

# インベントリの変数確認
ansible-inventory --host web01 | python3 -m json.tool

# ---- プレイブックのデバッグ ----
# 構文チェック
ansible-playbook site.yml --syntax-check

# ドライラン+差分表示
ansible-playbook site.yml --check --diff

# タスク一覧の確認
ansible-playbook site.yml --list-tasks

# ステップ実行(各タスクで確認)
ansible-playbook site.yml --step

# デバッグレベルの最大化
ansible-playbook site.yml -vvvv 2>&1 | tee /tmp/ansible_debug.log

# ---- 特定タスクのデバッグ ----
# 特定タスクから実行開始
ansible-playbook site.yml --start-at-task="Nginx の設定ファイルを配置" -vv

# ---- ログの確認 ----
# Ansible ログ(ansible.cfg で設定した場合)
tail -f ./ansible.log

# マネージドノードのログ
ansible web01 -m command -a "journalctl -xe --no-pager -n 50"

17.3 パフォーマンス改善のチェックリスト

# SSH 接続のパフォーマンス確認
time ssh web01 "echo test"

# Ansible の実行時間を測定
time ansible-playbook site.yml

# profile_tasks コールバックで各タスクの時間を計測
ANSIBLE_CALLBACK_WHITELIST=profile_tasks ansible-playbook site.yml

# または ansible.cfg で常時有効化
# [defaults]
# callbacks_enabled = profile_tasks, timer

18. 参考資料

18.1 公式ドキュメント

リソースURL
Ansible ドキュメントhttps://docs.ansible.com/
ansible-core ドキュメントhttps://docs.ansible.com/ansible-core/latest/
モジュールインデックスhttps://docs.ansible.com/ansible/latest/collections/index.html
Ansible Galaxyhttps://galaxy.ansible.com/
Ansible GitHubhttps://github.com/ansible/ansible

18.2 推奨コレクション

# requirements.yml
collections:
  - name: community.general      # 汎用モジュール集
  - name: community.mysql        # MySQL 管理
  - name: community.postgresql   # PostgreSQL 管理
  - name: ansible.posix          # POSIX システム管理
  - name: amazon.aws             # AWS 管理
  - name: google.cloud           # GCP 管理
  - name: azure.azcollection     # Azure 管理
  - name: kubernetes.core        # Kubernetes 管理
  - name: community.docker       # Docker 管理

18.3 学習リソース

種別タイトル説明
書籍Ansible for DevOps (Jeff Geerling)実践的な Ansible の教科書
書籍Ansible: Up and Running公式に近い包括的ガイド
コースRed Hat DO374 (Developing Advanced Automation with Ansible)公式トレーニング
認定Red Hat Certified Specialist in Ansible AutomationAnsible の公式認定試験
GitHubhttps://github.com/geerlingguy豊富な実践的ロールのサンプル
Bloghttps://www.jeffgeerling.com/Ansible の実践的な記事

18.4 バージョン対応表

ansible-corePython サポート主な変更点
2.173.10-3.12Python 3.12 対応、コレクション安定化
2.163.9-3.12パフォーマンス改善
2.153.9-3.11EL7 サポート終了
2.143.9-3.11core モジュールの分離完了
2.133.8-3.10コレクションアーキテクチャ成熟

まとめ

Ansible は「シンプルさ」と「強力さ」を両立した構成管理ツールです。本記事で解説した主要ポイントを振り返ります。

┌─────────────────────────────────────────────────────────┐
│                 Ansible 学習ロードマップ                    │
│                                                         │
│  入門  →  インストール・インベントリ・アドホックコマンド       │
│  基礎  →  プレイブック・YAML・変数・条件・ループ              │
│  中級  →  ロール・Vault・エラーハンドリング・べき等性          │
│  上級  →  動的インベントリ・カスタムモジュール・Molecule・CI/CD │
│  実践  →  大規模運用・AWX/AAP・パフォーマンスチューニング      │
└─────────────────────────────────────────────────────────┘

重要な設計原則:

  1. べき等性を常に意識する — 何度実行しても安全な設計
  2. 機密情報は必ず Vault で暗号化する — コードとして安全に管理
  3. ロールで再利用性を高める — DRY (Don't Repeat Yourself)
  4. バージョン管理と CI/CD に組み込む — コードとして品質を保つ
  5. check モードでテストしてから本番適用 — 安全な変更管理

Ansible は構成管理の自動化だけでなく、アプリケーションデプロイ、クラウドインフラ管理、ネットワーク機器の設定管理など、幅広い用途に活用できる強力なツールです。本記事を出発点として、実際の環境で積み重ねることで確かなスキルが身につきます。


本記事は ansible-core 2.17 を基準に執筆しました。
最終更新: 2026年4月