Ansibleとは、サーバーやPCの設定作業を「YAMLで書いた手順書(playbook)」として自動実行する、エージェント不要の構成管理ツールです。 管理される側に専用ソフトを入れず、SSH(またはローカル実行)とPythonだけで動きます。同じplaybookを何度流しても「あるべき状態」にそろえる冪等性(べきとうせい)が最大の特長です。

この記事では、概念の説明だけで終わらせず、localhost を対象にしたplaybookを実際に2回実行し、1回目の changed が2回目に ok へ変わる様子を出力そのままでお見せします。さらに --check --diff、ansible-lint、ansible-vault、実際のエラー文と直し方まで、コピペで試せる形でまとめました。検証環境は ansible 12.3.0(ansible-core 2.19.13)/Python 3.11/Ubuntu 24.04 です。


Ansibleの早見表

やりたいこと 書き方・コマンド ポイント
インストール pip install ansible 2026年10月時点の最新は ansible 14.4.0(core 2.21.4、Python 3.12以上)。Python 3.11 では 12.3.0 まで
バージョン確認 ansible --version 表示されるのは ansible-core のバージョン
接続確認 ansible -i inventory.ini all -m ansible.builtin.ping "ping": "pong" が返れば成功
playbook実行 ansible-playbook -i inventory.ini site.yml 最後の PLAY RECAP で結果を確認
文法チェック ansible-playbook site.yml --syntax-check 実行せずにYAMLと構文だけ確認
変更の予告 --check --diff 実際には変えずに差分だけ表示(ドライラン)
タスク一覧 --list-tasks 実行順の確認に便利
変数を上書き -e app_port=7802 コマンドライン変数は優先度が最も高い
一部だけ実行 --tags config / --limit web01 タグ・ホストで絞り込み
ロールの雛形 ansible-galaxy role init roles/webapp 標準ディレクトリが一式できる
品質チェック ansible-lint site.yml FQCN・name・権限などを指摘
秘密情報の暗号化 ansible-vault encrypt secret.yml 実行時は --vault-password-file か --ask-vault-pass

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Ansible入門|playbookの書き方を実行結果つきで解説」


Ansibleの全体像:構成図と用語表

Ansibleは「どこに(インベントリ)」「何を(playbook)」「どうやって(モジュール)」の3つで考えると整理できます。

flowchart LR
    subgraph CN[制御ノード(Ansibleを入れたPC)]
        INV[インベントリ<br/>inventory.ini]
        PB[playbook<br/>site.yml]
        VARS[変数<br/>vars / group_vars]
        ROLE[ロール<br/>roles/webapp]
        PB --> ROLE
        VARS --> PB
    end
    PB -- SSH / local --> H1[管理対象ノード1]
    PB -- SSH / local --> H2[管理対象ノード2]
    INV -. 対象を指定 .-> PB
    subgraph Task[playbookの中身]
        T1[タスク = モジュール呼び出し] -- 変更あり notify --> HD[handler]
    end
用語 ひとことで 例
制御ノード Ansibleを実行する側のマシン 手元のPC・踏み台サーバー
管理対象ノード 設定される側のマシン。Pythonがあればよい Webサーバー、検証用VM
インベントリ 対象ホストとグループの一覧 inventory.ini、inventory.yml
playbook 「どのホストに何をするか」を書いたYAML site.yml
play playbook内の1ブロック。hosts とタスクの組 - name: Web設定 / hosts: web
タスク 1つの作業。中身は1つのモジュール呼び出し ディレクトリ作成
モジュール 作業の部品。冪等に作られている ansible.builtin.file
FQCN モジュールの完全修飾名 ansible.builtin.copy
コレクション モジュールやロールの配布単位 ansible.builtin、community.general
変数 値を差し替える仕組み vars:、group_vars/、-e
ファクト 対象ノードから自動収集する情報 ansible_facts['os_family']
handler 変更があったときだけ最後に1回動くタスク サービス再起動
ロール タスク・変数・テンプレートを部品化したもの roles/webapp
冪等性 何度実行しても同じ結果になる性質 2回目は ok(変更なし)

インストールとバージョンの選び方

Ansibleには「ansible」(コレクション同梱のパッケージ)と「ansible-core」(本体のみ)の2種類があります。 入門では、よく使うコレクションがそろう ansible を入れるのがおすすめです。

PyPIで確認した2026年10月1日時点の情報は次のとおりです。

パッケージ 最新版 必要なPython 備考
ansible 14.4.0(2026-09-08) 3.12以上 ansible-core 2.21系を同梱
ansible-core 2.21.4(2026-09-08) 3.12以上 本体のみ
ansible 12.3.0 — 3.11以上 ansible-core 2.19系。本記事の検証版
# 仮想環境に入れる(OSのPythonを汚さない)
python3 -m venv .venv
. .venv/bin/activate
pip install ansible==12.3.0 ansible-lint

python -c "import importlib.metadata as m; print(m.version('ansible'), m.version('ansible-core'))"
12.3.0 2.19.13

Python 3.12以上の環境なら pip install ansible だけで最新版が入ります。 本記事のplaybookは 2.19 と 2.21 で書き方に差が出ない基本機能だけを使っています。


まずはインベントリを書く

インベントリは「どのホストに接続するか」の一覧です。 今回は手元のマシン自身を対象にするため、ansible_connection=local を指定します。SSHの準備なしで試せるので、入門の最初の一歩にぴったりです。

# inventory.ini
[local]
localhost ansible_connection=local ansible_python_interpreter=/usr/bin/python3

接続確認には ping モジュールを使います(ICMPのpingではなく、Pythonが動くかの確認です)。

ansible -i inventory.ini local -m ansible.builtin.ping
localhost | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

リモートサーバーを対象にするときは、IPとユーザーを書くだけです。

[web]
web01 ansible_host=192.0.2.10 ansible_user=deploy
web02 ansible_host=192.0.2.11 ansible_user=deploy

[web:vars]
ansible_ssh_private_key_file=~/.ssh/id_ed25519

playbookの書き方:基本モジュールをまとめて動かす

playbookは「play → tasks → モジュール」の入れ子で書きます。 次の1本で、入門で必要な要素(file、template、lineinfile、copy、command と creates、loop、when、register/debug、handler)を全部使っています。

準備するファイル

ans/
├── inventory.ini
├── site.yml
├── templates/
│   └── app.conf.j2
└── files/
    └── motd.txt      # 中身: hello from ansible
{# templates/app.conf.j2(最初の版) #}
# managed by Ansible
app_name = {{ app_name }}
port = {{ app_port }}

site.yml(コピペで動く完成形)

---
- name: ローカルで基本モジュールを試す
  hosts: local
  gather_facts: true
  vars:
    base_dir: /tmp/t80_demo
    app_name: todo-api
    app_port: 7801
    users_dirs:
      - logs
      - data
      - tmp

  tasks:
    - name: ベースディレクトリを作る
      ansible.builtin.file:
        path: "{{ base_dir }}"
        state: directory
        mode: "0755"

    - name: サブディレクトリをloopで作る
      ansible.builtin.file:
        path: "{{ base_dir }}/{{ item }}"
        state: directory
        mode: "0755"
      loop: "{{ users_dirs }}"

    - name: 設定ファイルをテンプレートから生成
      ansible.builtin.template:
        src: templates/app.conf.j2
        dest: "{{ base_dir }}/app.conf"
        mode: "0644"
      notify: 設定変更を通知

    - name: 1行だけ追記・置換する
      ansible.builtin.lineinfile:
        path: "{{ base_dir }}/app.conf"
        regexp: "^log_level ="
        line: "log_level = info"

    - name: ファイルをコピーする
      ansible.builtin.copy:
        src: files/motd.txt
        dest: "{{ base_dir }}/motd.txt"
        mode: "0644"

    - name: 初回だけコマンドを実行する(creates)
      ansible.builtin.command:
        cmd: touch {{ base_dir }}/data/initialized
        creates: "{{ base_dir }}/data/initialized"

    - name: OSファミリーがDebianのときだけ実行(when)
      ansible.builtin.debug:
        msg: "Debian系です: {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}"
      when: ansible_facts['os_family'] == "Debian"

    - name: 結果を変数に保存(register)
      ansible.builtin.command:
        cmd: cat {{ base_dir }}/app.conf
      register: conf_result
      changed_when: false

    - name: 保存した結果を表示(debug)
      ansible.builtin.debug:
        var: conf_result.stdout_lines

  handlers:
    - name: 設定変更を通知
      ansible.builtin.debug:
        msg: "app.conf が変わったので再起動します(デモ)"
書き方 意味
"{{ 変数 }}" Jinja2で変数を埋め込む。値の先頭が {{ のときは必ずクォートで囲む
mode: "0644" 権限は文字列で書く(8進数の解釈ずれを防ぐ)
loop: 配列の要素ごとにタスクを繰り返す。要素は item で参照
creates: そのファイルがあればコマンドを実行しない(commandを冪等にする)
when: 条件が真のときだけ実行。{{ }} は付けない
register: モジュールの結果を変数に保存
changed_when: false 読み取り専用コマンドを「変更なし」扱いにする
notify: このタスクが changed のとき、指定した handler を予約する

1回目の実行結果

ansible-playbook -i inventory.ini site.yml
PLAY [ローカルで基本モジュールを試す] ******************************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [ベースディレクトリを作る] ************************************************
changed: [localhost]

TASK [サブディレクトリをloopで作る] ********************************************
changed: [localhost] => (item=logs)
changed: [localhost] => (item=data)
changed: [localhost] => (item=tmp)

TASK [設定ファイルをテンプレートから生成] **************************************
changed: [localhost]

TASK [1行だけ追記・置換する] ***************************************************
changed: [localhost]

TASK [ファイルをコピーする] ****************************************************
changed: [localhost]

TASK [初回だけコマンドを実行する(creates)] ***********************************
changed: [localhost]

TASK [OSファミリーがDebianのときだけ実行(when)] ******************************
ok: [localhost] => {
    "msg": "Debian系です: Ubuntu 24.04"
}

TASK [結果を変数に保存(register)] ********************************************
ok: [localhost]

TASK [保存した結果を表示(debug)] *********************************************
ok: [localhost] => {
    "conf_result.stdout_lines": [
        "# managed by Ansible",
        "app_name = todo-api",
        "port = 7801",
        "log_level = info"
    ]
}

RUNNING HANDLER [設定変更を通知] ***********************************************
ok: [localhost] => {
    "msg": "app.conf が変わったので再起動します(デモ)"
}

PLAY RECAP *********************************************************************
localhost                  : ok=11   changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

1回目は作成系のタスクがすべて changed になり、template が changed だったので handler が最後に1回だけ動きました。

2回目の実行結果:changed が残る理由

同じplaybookをもう一度流すと、次のようになりました(該当部分を抜粋)。

TASK [ベースディレクトリを作る] ************************************************
ok: [localhost]

TASK [サブディレクトリをloopで作る] ********************************************
ok: [localhost] => (item=logs)
ok: [localhost] => (item=data)
ok: [localhost] => (item=tmp)

TASK [設定ファイルをテンプレートから生成] **************************************
changed: [localhost]

TASK [1行だけ追記・置換する] ***************************************************
changed: [localhost]

TASK [ファイルをコピーする] ****************************************************
ok: [localhost]

TASK [初回だけコマンドを実行する(creates)] ***********************************
ok: [localhost]
...
PLAY RECAP *********************************************************************
localhost                  : ok=11   changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

file・copy・command(creates)は ok に変わりましたが、template と lineinfile が毎回 changed になります。 理由は次のとおりです。

  1. template がテンプレートどおりにファイルを書き戻す(log_level 行が消える)→ changed
  2. lineinfile が log_level = info を再び追記する → changed
  3. template が changed なので handler も毎回動く(=毎回「再起動」が走る)

1つのファイルを template と lineinfile の両方で管理すると、この「綱引き」が起きます。 直し方は、ファイルの持ち主を1つにまとめることです。

直す前と後

{# 直した後: templates/app.conf.j2 #}
# managed by Ansible
app_name = {{ app_name }}
port = {{ app_port }}
log_level = {{ log_level | default('info') }}

site.yml からは lineinfile タスクを削除します。修正後、作業ディレクトリを消してから2回続けて実行した結果です。

# 1回目(handler も1回動く)
localhost                  : ok=10   changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
# 2回目
localhost                  : ok=9    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

changed=0 になり、handler も動かなくなりました。これが冪等なplaybookの状態です。

状態 2回目の changed handler 判断
直す前(template+lineinfile) 2 毎回動く 不要な再起動が発生する
直した後(templateに一本化) 0 動かない 冪等。何度流しても安心

「2回目に changed=0 になるか」は、playbookを書いたら必ず確認したいチェック項目です。


--check と --diff で変更を予告する

--check は実際には何も変えずに「変わる予定」だけを表示するドライランです。 --diff を足すと、ファイルの差分も見られます。ポート番号を変える想定で実行しました(テンプレートを直した後、lineinfile タスクを消す前の site.yml で実行)。

ansible-playbook -i inventory.ini site.yml --check --diff -e app_port=7802
TASK [設定ファイルをテンプレートから生成] **************************************
--- before: /tmp/t80_demo/app.conf
+++ after: /root/.ansible/tmp/ansible-local-82958aeocwpb/tmpjcafr1_d/app.conf.j2
@@ -1,4 +1,4 @@
 # managed by Ansible
 app_name = todo-api
-port = 7801
+port = 7802
 log_level = info

changed: [localhost]
...
TASK [結果を変数に保存(register)] ********************************************
skipping: [localhost]

TASK [保存した結果を表示(debug)] *********************************************
ok: [localhost] => {
    "conf_result.stdout_lines": []
}
...
PLAY RECAP *********************************************************************
localhost                  : ok=10   changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

実行後もファイルは port = 7801 のままで、本当に変更されていないことを確認しました。

注目したいのは、command タスクが skipping になり、register の中身が空になった点です。 command/shell モジュールは「何が変わるか予測できない」ため、チェックモードでは実行されません。読み取り専用のコマンドなら、次のように書くとチェックモードでも結果を取れます。

    - name: 結果を変数に保存(register)
      ansible.builtin.command:
        cmd: cat {{ base_dir }}/app.conf
      register: conf_result
      changed_when: false
      check_mode: false   # チェックモードでも実行する
オプション 用途
--check 変更せずに予定だけ表示
--diff ファイル内容の差分を表示(--check なしでも使える)
--syntax-check 構文だけ確認(成功時は playbook: site.yml とだけ出る)
--list-tasks 実行されるタスクの一覧
--start-at-task "名前" 途中のタスクから再開
-v 〜 -vvvv 詳細ログ。接続トラブル時は -vvv

ansible-lint で書き方をそろえる

ansible-lint は、playbookの書き方の「よくあるつまずき」を自動で指摘してくれるツールです。 あえて省略形で書いた bad.yml を検査しました(ansible-lint 26.9.0)。

# bad.yml(指摘を受ける例)
- hosts: local
  tasks:
  - shell: echo hello > /tmp/t80_demo/hello.txt
  - name: install
    copy: src=files/motd.txt dest=/tmp/t80_demo/m2.txt
name[play]: All plays should be named.
fqcn[action-core]: Use FQCN for builtin module actions (shell).
name[missing]: All tasks should be named.
no-changed-when: Commands should not change things if nothing needs doing.
yaml[indentation]: Wrong indentation: expected at least 3
name[casing]: All names should start with an uppercase letter.
no-free-form: Avoid using free-form when calling module actions. (copy)
risky-file-permissions: File permissions unset or incorrect.
fqcn[action-core]: Use FQCN for builtin module actions (copy).

Failed: 9 failure(s), 0 warning(s) in 1 files processed of 1 encountered. Last profile that met the validation criteria was 'min'.

(ファイル位置の行は省略しています)一方、先ほどの site.yml は次のとおり最上位のプロファイルを通過しました。

Passed: 0 failure(s), 0 warning(s) in 1 files processed of 1 encountered. Last profile that met the validation criteria was 'production'.
指摘ルール 意味 直し方
name[play] / name[missing] play・タスクに名前がない - name: を付ける
name[casing] 名前が小文字始まり 先頭を大文字に(日本語名は対象外)
fqcn[action-core] モジュール名が短縮形 ansible.builtin.copy と書く
no-free-form src=... dest=... の1行形式 キー: 値 の形式で書く
risky-file-permissions mode がない mode: "0644" を付ける
no-changed-when command/shell が常に changed creates か changed_when を付ける
yaml[indentation] リストのインデント tasks: の下を2スペース下げる

ansible-vault で秘密情報を暗号化する

パスワードやAPIキーは、ansible-vault で暗号化してから Git に入れます。 復号用のパスワードファイルは Git に入れません。

echo 'S3cretPass-demo' > .vault_pass && chmod 600 .vault_pass
printf 'db_password: "p@ssw0rd-demo"\n' > secret.yml

ansible-vault encrypt secret.yml --vault-password-file .vault_pass
head -3 secret.yml
$ANSIBLE_VAULT;1.1;AES256
37626462376532666632626561373963613964336439366565313432643438666433373663326631
3039303466356639393262396265333162383463616664650a373337313230356238373135343938
ansible-vault view secret.yml --vault-password-file .vault_pass
db_password: "p@ssw0rd-demo"

playbookでは普通の変数ファイルと同じように読み込めます。

# vault_use.yml
- name: vault変数を使う
  hosts: local
  gather_facts: false
  vars_files:
    - secret.yml
  tasks:
    - name: パスワードの長さだけ表示
      ansible.builtin.debug:
        msg: "db_password は {{ db_password | length }} 文字です"
TASK [パスワードの長さだけ表示] ************************************************
ok: [localhost] => {
    "msg": "db_password は 13 文字です"
}

パスワードを渡し忘れると、次のエラーになります。

[ERROR]: Invalid vars_files file '.../secret.yml': Attempting to decrypt but no vault secrets found.

→ --vault-password-file .vault_pass か --ask-vault-pass を付けると解決します。ファイル全体ではなく1つの値だけ暗号化したいときは encrypt_string が便利です。

ansible-vault encrypt_string 'p@ssw0rd-demo' --name db_password --vault-password-file .vault_pass
db_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          65346232363531343439383836353563323833373639336530643163343139306466363637616466
          ...(以下略)
コマンド 用途
ansible-vault create f.yml 新規作成(エディタが開く)
ansible-vault encrypt f.yml 既存ファイルを暗号化
ansible-vault view f.yml 中身を表示
ansible-vault edit f.yml 復号して編集→再暗号化
ansible-vault rekey f.yml パスワード変更
ansible-vault encrypt_string 値1つだけ暗号化

ロールで部品化する

ロールは、タスク・handler・変数・テンプレートを決まったフォルダに分けて再利用できるようにしたものです。 雛形は ansible-galaxy で作れます。

ansible-galaxy role init roles/webapp
- Role roles/webapp was created successfully
roles/webapp/
├── README.md
├── defaults/main.yml    # 既定値(優先度が最も低く、上書き前提)
├── files/               # copy で配るファイル
├── handlers/main.yml    # handler
├── meta/main.yml        # 依存ロールなど
├── tasks/main.yml       # タスク本体
├── templates/           # template で使う .j2
├── tests/
└── vars/main.yml        # ロール内部の変数(優先度が高い)
# roles/webapp/defaults/main.yml
---
webapp_dir: /tmp/t80_demo/webapp
webapp_port: 7803
# roles/webapp/tasks/main.yml
---
- name: アプリ用ディレクトリを作る
  ansible.builtin.file:
    path: "{{ webapp_dir }}"
    state: directory
    mode: "0755"

- name: 設定ファイルを置く
  ansible.builtin.copy:
    content: "port={{ webapp_port }}\n"
    dest: "{{ webapp_dir }}/webapp.env"
    mode: "0644"
  notify: Webapp再起動
# roles/webapp/handlers/main.yml
---
- name: Webapp再起動
  ansible.builtin.debug:
    msg: "webapp を再起動します(port={{ webapp_port }})"
# role_site.yml
---
- name: ロールを使う
  hosts: local
  gather_facts: false
  roles:
    - role: webapp
      vars:
        webapp_port: 7804
TASK [webapp : アプリ用ディレクトリを作る] *************************************
changed: [localhost]

TASK [webapp : 設定ファイルを置く] *********************************************
changed: [localhost]

RUNNING HANDLER [webapp : Webapp再起動] ****************************************
ok: [localhost] => {
    "msg": "webapp を再起動します(port=7804)"
}

PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

2回目は ok=2 changed=0 となり、handler は動きませんでした。defaults の 7803 が、playbook側の vars で 7804 に上書きされている点も確認できます。

変数の優先順位(よく使うものだけ)

優先度 場所
低 ロールの defaults/main.yml
↓ インベントリの変数・group_vars/all
↓ group_vars/グループ名・host_vars/ホスト名
↓ play の vars:・vars_files:
↓ ロールの vars/main.yml・set_fact・register
高 コマンドラインの -e(常に最優先)

「上書きされる前提の既定値は defaults、変えてほしくない値は vars」と使い分けると迷いません。


おすすめのディレクトリ構成

infra/
├── ansible.cfg
├── inventories/
│   ├── dev/
│   │   ├── hosts.ini
│   │   └── group_vars/
│   │       └── all.yml
│   └── stg/
│       ├── hosts.ini
│       └── group_vars/
│           ├── all.yml
│           └── vault.yml        # ansible-vault で暗号化
├── roles/
│   ├── common/
│   └── webapp/
├── site.yml                     # 全体を呼ぶ入口
└── webservers.yml               # 役割ごとのplaybook
# ansible.cfg(毎回 -i を書かずに済む)
[defaults]
inventory = inventories/dev/hosts.ini
roles_path = roles
stdout_callback = default

環境ごとにインベントリを分けると、-i inventories/stg/hosts.ini を切り替えるだけで同じplaybookを使い回せます。


よく使うモジュール一覧(FQCN)

やりたいこと モジュール(FQCN) 主な引数
ディレクトリ・権限・リンク ansible.builtin.file path state mode owner
ファイルを配る ansible.builtin.copy src / content dest mode
テンプレートから生成 ansible.builtin.template src dest mode
1行だけ編集 ansible.builtin.lineinfile path regexp line
複数行ブロック編集 ansible.builtin.blockinfile path block marker
コマンド実行 ansible.builtin.command cmd creates removes
パイプ・リダイレクト付き ansible.builtin.shell cmd creates
パッケージ(汎用) ansible.builtin.package name state
apt / dnf ansible.builtin.apt / ansible.builtin.dnf name state update_cache
サービス起動 ansible.builtin.service / ansible.builtin.systemd_service name state enabled
ユーザー作成 ansible.builtin.user name groups shell
ファイルの有無を調べる ansible.builtin.stat path(結果を register)
URLから取得 ansible.builtin.get_url url dest checksum
Git から取得 ansible.builtin.git repo dest version
待ち合わせ ansible.builtin.wait_for port timeout
変数を作る ansible.builtin.set_fact 任意のキー
メッセージ表示 ansible.builtin.debug msg / var
条件チェック ansible.builtin.assert that fail_msg
別ファイルのタスク ansible.builtin.include_tasks / ansible.builtin.import_tasks ファイル名

command と shell は冪等にならないため、専用モジュールがあればそちらを優先し、使う場合は creates / changed_when を付けるのが基本です。


よくあるエラーと直し方

実際に ansible-core 2.19.13 で出したエラー文です。2.19 以降はエラー箇所の行と列が表示されるので、まず Origin の行を見ると早く解決できます。

変数名のタイプミス(undefined)

[ERROR]: Task failed: Finalization of task args for 'ansible.builtin.debug' failed: Error while resolving value for 'msg': 'app_nmae' is undefined
...
7         msg: "{{ app_nmae }}"
               ^ column 14

→ 変数名を確認します。未定義でもよい変数は {{ app_name | default('none') }} と既定値を付けます。

モジュール名の誤り

[ERROR]: couldn't resolve module/action 'ansible.builtin.cpy'. This often indicates a misspelling, missing collection, or incorrect module path.

→ つづりを直すか、community.general など必要なコレクションを ansible-galaxy collection install で入れます。

引数のインデント不足

# 直す前
    - name: ファイル
      ansible.builtin.file:
      path: /tmp/t80_demo/y      # モジュールと同じ深さになっている
      state: directory
[ERROR]: conflicting action statements: ansible.builtin.file, path
# 直した後
    - name: ファイル
      ansible.builtin.file:
        path: /tmp/t80_demo/y
        state: directory

「conflicting action statements」は、引数がモジュール名と同じ深さに並んでいるサインです。

インベントリが読めない

[WARNING]: Unable to parse .../nohost.ini as an inventory source
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: Could not match supplied host pattern, ignoring: local
...
skipping: no hosts matched

→ エラーで止まらず「何もせず終わる」ため見落としがちです。-i のパスと hosts: のグループ名を確認します。

その他よく見るもの

エラー・症状 主な原因 対処
UNREACHABLE! ... Permission denied (publickey) SSH鍵・ユーザー違い ansible_user、鍵のパス、ssh 単体で接続確認
Missing sudo password become に必要なパスワードがない -K(--ask-become-pass)を付ける
Attempting to decrypt but no vault secrets found vaultのパスワード未指定 --vault-password-file
2回目も changed が出る command/shell、template と lineinfile の併用 creates・changed_when、管理を一本化
--check で register が空 command はチェックモードで実行されない check_mode: false

よくある質問

Q. Ansible と Terraform はどう使い分けますか? A. Terraform はクラウドのリソース(ネットワーク・VM など)を「作る」のが得意で、Ansible は作ったサーバーの中身(パッケージ・設定ファイル)を「整える」のが得意です。Terraform でVMを作り、Ansible で設定する組み合わせがよく使われます。

Q. 管理対象のサーバーに何かインストールする必要はありますか? A. SSHで接続でき、Pythonが入っていれば追加のエージェントは不要です。Pythonがない機器(ネットワーク機器など)向けには専用のコネクションプラグインがあります。

Q. command と shell はどちらを使えばよいですか? A. パイプ(|)やリダイレクト(>)、環境変数の展開が必要なときだけ shell、それ以外は command を使います。どちらも冪等ではないので、creates や changed_when を付けます。

Q. Python 3.11 の環境で最新の Ansible は使えますか? A. 2026年10月時点の ansible 14.x/ansible-core 2.21 は Python 3.12 以上が必要です。Python 3.11 では ansible 12.3.0(core 2.19)が最新になります。uv python install 3.12 などで新しいPythonを用意すると最新版も使えます。


まとめ

  • Ansibleは、YAMLのplaybookで設定作業を自動化するエージェント不要のツールで、インベントリ・playbook・モジュールの3つで考えると整理しやすい
  • ansible_connection=local を使えば、SSHの準備なしに手元で練習できる
  • 2回目の実行で changed=0 になるかを必ず確認する。template と lineinfile の併用は「綱引き」になるので一本化する
  • --check --diff で事前に差分を確認でき、command は check_mode: false で読み取りを続けられる
  • ansible-lint で FQCN・name・mode などの書き方をそろえ、ansible-vault で秘密情報を暗号化する
  • ロールで部品化し、既定値は defaults、上書きは -e や play の vars で行う

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Ansible入門|playbookの書き方を実行結果つきで解説」

株式会社RJCでは、Ansibleによる構成管理の知識を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?