---
title: "Ansible Playbookの書き方と実行手順：–checkでのdry-runとhandlers【2.21系】"
url: "https://www.issoh.co.jp/tech/details/18457/"
published: 2026-10-17
updated: 2026-10-17
categories: ["自動化"]
publisher: "株式会社一創"
---

# Ansible Playbookの書き方と実行手順：–checkでのdry-runとhandlers【2.21系】

Ansible Playbookは、対象サーバーと実行するタスクをYAMLで書き、`ansible-playbook`コマンドで流す手順書です。この記事では、ansible-core 2.21系の検証環境づくりから、Nginxの導入と設定配布を題材にした`site.yml`の全文、handlersでの再読み込み、`--check`と`--diff`を使ったdry-runの手順までをコード付きで示します。後半では変数の優先順位やtagsで起きやすい事故と、Playbookにすべきでない作業の見分け方も扱います。バージョンや仕様は2026年10月時点の公式ドキュメントで確かめた内容です。

## まとめ｜Playbookはhosts・tasks・handlersの3層で書き、–check –diffで確かめてから流す

先に結論を並べます。

- Playbookは「どのホストに（hosts）」「何をするか（tasks）」「変更があったときだけ何をするか（handlers）」の3層で書く
- 流す順番は `--syntax-check` → `--check --diff` → `--limit`で1台 → 全台。最初から全台へは流さない
- 設定ファイルは`ansible.builtin.template`の`validate`で検証してから置く。検証できない設定は手で戻せる`backup`を必ず付ける
- commandやshellの羅列になる作業はPlaybookにしても再実行で安全にならない。モジュールで書けない作業が過半なら見送る

2回流して2回目が`changed=0`になれば、そのPlaybookは本番に入れてよい状態です。

## Ansible Playbookの構造：プレイ・タスク・モジュールとYAMLの書式ルール

Ansibleがエージェントを入れずにSSHで動く仕組みは[Ansibleとは？エージェントレスの仕組みとメリット・デメリット、Red Hat AAPの制約](https://www.issoh.co.jp/tech/details/6533/)で扱っています。ここではPlaybookファイルの中身に絞ります。

### 1ファイル＝複数プレイ、1プレイ＝hostsとtasksの組という階層構造

[公式のPlaybook入門](https://docs.ansible.com/projects/ansible/latest/playbook%5Fguide/playbooks%5Fintro.html)では、Playbookを「プレイの順序つきリスト」と定義しています。1つのプレイは対象ホストの指定（`hosts`）と、上から順に実行されるタスクの並び（`tasks`）で構成されます。

- Playbook：1つのYAMLファイル。プレイを上から順に実行する
- プレイ：`hosts`で選んだホスト群に対して、タスクの並びを適用する単位
- タスク：モジュールを1回呼び出す単位。`name`で何をするかを書く
- モジュール：実際の処理を担う部品。`ansible.builtin.dnf`や`ansible.builtin.template`など

Webサーバー群とDBサーバー群で手順が違うなら、1ファイルの中にプレイを2つ並べます。プレイを分けると、片方のホスト群でだけ`become`（権限昇格）を有効にする、といった切り替えもプレイ単位でできるようになります。

### インデント2スペース・FQCN表記など書き始めで守る4つの書式ルール

Playbookで最初につまずくのはYAMLの書式です。書き始めの段階で、次の4点を決めておくと手戻りが減ります。

1. インデントは半角スペース2つで統一し、タブは使わない
2. モジュール名は`ansible.builtin.copy`のような完全修飾名（FQCN）で書く。短縮名の`copy`は同名モジュールとの取り違えが起きる
3. 値が`{{`で始まるときは式全体を引用符で囲む
4. ファイルのパーミッションは`"0644"`のように文字列で書く。引用符を外すと8進数として解釈されない場合がある

3つ目は[公式の変数ガイド](https://docs.ansible.com/projects/ansible/latest/playbook%5Fguide/playbooks%5Fvariables.html)に明記されています。`app_path: {{ base_path }}/22`はYAMLの構文エラーになり、`app_path: "{{ base_path }}/22"`と書けば通ります。YAMLのパーサーが`{{`を辞書の開始と区別できないためです。

## 検証環境の準備：ansible-core 2.21系のインストールとインベントリ作成

本番サーバーへ流す前に、手元から検証用のサーバーへ流せる状態を作ります。以降の例は、制御ノードにLinuxかmacOS、対象にRHEL系（AlmaLinuxなど）のサーバー2台を想定しています。

### Python 3.12以上の仮想環境にpipでansible-coreを入れる手順

[公式のリリースとメンテナンスの表](https://docs.ansible.com/projects/ansible/latest/reference%5Fappendices/release%5Fand%5Fmaintenance.html)によると、2026年10月時点の最新系列はansible-core 2.21系で、2026年5月に公開され、2027年11月にサポートが終わります。制御ノードに必要なPythonは3.12〜3.14です。[PyPIのansible-core](https://pypi.org/project/ansible-core/)では2.21.5が最新でした（2026年10月5日公開）。

```
# 制御ノードに仮想環境を作り、ansible-coreの系列を固定して入れる
python3 -m venv ~/.venvs/ansible
source ~/.venvs/ansible/bin/activate
pip install "ansible-core==2.21.*"
ansible --version
```

系列を`2.21.*`で固定しておくと、パッチ版だけが上がり、挙動が変わる系列更新は自分で決めたタイミングに寄せられます。コミュニティのコレクションを一式で使いたい場合はパッケージ`ansible`（14.x系がcore 2.21に対応）を入れますが、検証ではcoreだけで足ります。

### inventory.iniでwebグループを定義しpingモジュールで疎通確認

インベントリは「どのホストを、どのグループ名で扱うか」を書いたファイルです。書き方は[公式のインベントリガイド](https://docs.ansible.com/projects/ansible/latest/inventory%5Fguide/intro%5Finventory.html)にINI形式とYAML形式の両方があり、ここでは行数の少ないINI形式を使います。

```
# inventory.ini
[web]
web01 ansible_host=192.0.2.11
web02 ansible_host=192.0.2.12

[web:vars]
ansible_user=deploy

# 疎通確認（pongが返れば接続とPythonの実行まで通っている）
ansible web -i inventory.ini -m ansible.builtin.ping
```

ここで失敗する原因は、ほぼSSH鍵か接続ユーザーです。Playbookを書く前にこの1行が通ることを確かめておくと、後のエラーをPlaybookの問題に絞り込めます。

## 最初のPlaybookを書く：Nginx導入・設定配布・再起動をhandlersで連動

題材は「Nginxを入れて、設定ファイルを配り、設定が変わったときだけ再読み込みする」作業です。手作業なら毎回同じ順番で打つコマンド群が、そのまま1つのプレイになります。

### site.ymlの全文とhosts・become・vars・tasksの役割分担

```
---
- name: Nginxを導入して設定を配布する
  hosts: web
  become: true
  vars:
    nginx_worker_connections: 1024

  tasks:
    - name: Nginxをインストールする
      ansible.builtin.dnf:
        name: nginx
        state: present

    - name: nginx.confを配布する
      ansible.builtin.template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: "0644"
        backup: true
      notify: Nginxを再読み込みする

    - name: Nginxを起動し自動起動を有効にする
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: Nginxの設定を検証する
      ansible.builtin.command: nginx -t
      changed_when: false
      listen: Nginxを再読み込みする

    - name: Nginxのプロセスを再読み込みする
      ansible.builtin.service:
        name: nginx
        state: reloaded
      listen: Nginxを再読み込みする
```

`hosts: web`はインベントリのwebグループを指し、`become: true`でタスクをroot権限で実行します。`vars`の値はテンプレート`templates/nginx.conf.j2`の中で`{{ nginx_worker_connections }}`として埋め込まれます。タスクの`state: present`や`state: started`は「こうなっていてほしい状態」の宣言で、すでにその状態なら何もしません。この性質が[冪等性](https://www.issoh.co.jp/tech/details/13382/)で、Playbookを何度流しても結果が同じになる根拠です。

### handlersが変更時だけ1回動く仕組みとflush\_handlersの使いどころ

[公式のhandlersのページ](https://docs.ansible.com/projects/ansible/latest/playbook%5Fguide/playbooks%5Fhandlers.html)が定める挙動は次の4点です。

- 通知元のタスクが`changed`になったときだけ動く。設定ファイルに差分が無ければ再読み込みは起きない
- 同じhandlerを何度通知されても、実行は1回だけ
- 実行されるのは`pre_tasks`・`tasks`・`post_tasks`の各セクションの終わり。順番は`notify`の順ではなく`handlers`に書いた順
- `listen`で共通のトピック名を付けると、1つの通知で複数のhandlerを動かせる

上の`site.yml`では、`listen`を使って「設定の検証」と「再読み込み」を1つの通知にまとめました。定義順に実行されるので、`nginx -t`が失敗すれば再読み込みには進みません。設定を反映してから後続のタスクで疎通を確かめたいときは、途中に`meta: flush_handlers`を置くと、その時点までに通知されたhandlerがすぐに実行されます。

### templateのvalidateで壊れた設定ファイルを本番へ置かない書き方

上の例は、設定ファイルを置いた後に`nginx -t`で検証する順番でした。置く前に検証できる設定なら、[templateモジュール](https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/template%5Fmodule.html)の`validate`を使うほうが安全です。公式ドキュメントにはsshdの例が載っています。

```
- name: sshd_configを検証してから配置する
  ansible.builtin.template:
    src: etc/ssh/sshd_config.j2
    dest: /etc/ssh/sshd_config
    owner: root
    group: root
    mode: "0600"
    validate: /usr/sbin/sshd -t -f %s
    backup: true
```

`%s`には一時ファイルのパスが入り、検証コマンドが失敗すると本来の場所へはコピーされません。sshdの設定を壊すと次のSSH接続ができなくなり、Ansible自身で戻せなくなります。検証の手段がある設定ファイルには`validate`を必ず付けます。なお`validate`のコマンドはシェルを通らないため、パイプや変数展開は使えない点に注意してください。

## Playbook実行の手順：構文確認・dry-run・本番適用の3段階

書いたPlaybookは、いきなり全台へ流しません。[ansible-playbookコマンドのリファレンス](https://docs.ansible.com/projects/ansible/latest/cli/ansible-playbook.html)にあるオプションを組み合わせ、確認の段階を踏みます。

```
# 1. 構文だけを確認する（ホストには接続しない）
ansible-playbook -i inventory.ini site.yml --syntax-check

# 2. 対象ホストと実行されるタスクの一覧を出す
ansible-playbook -i inventory.ini site.yml --list-hosts --list-tasks

# 3. 1台に絞ってdry-runし、ファイルの差分を表示する
ansible-playbook -i inventory.ini site.yml --check --diff --limit web01

# 4. 1台に本番適用し、もう一度流してchanged=0を確かめる
ansible-playbook -i inventory.ini site.yml --limit web01
ansible-playbook -i inventory.ini site.yml --limit web01

# 5. 残りの全台へ適用する
ansible-playbook -i inventory.ini site.yml
```

### –syntax-checkと–list-tasksで流す前に対象と順番を確かめる

`--syntax-check`はPlaybookを実行せずに構文だけを検査します。インデントのずれや未定義のキーはここで止まります。続く`--list-hosts`は対象になるホストの一覧を出すだけで、ほかには何も実行しません。

見落としやすいのは`hosts`の指定ミスで、意図せず全ホストに当たる事故は一覧を見れば流す前に気付けます。`--list-tasks`はタスク名を実行順に並べるので、`name`を「何をするか」が分かる文で書いておくほど確認しやすくなります。

### –check –diffの出力の読み方とcheck modeで予測できないタスク

[check modeの公式ページ](https://docs.ansible.com/projects/ansible/latest/playbook%5Fguide/playbooks%5Fcheckmode.html)によると、`--check`を付けた実行では対象ホストに一切の変更を加えません。`--diff`を併用すると、templateやcopyが書き換える予定のファイルについて、変更前後の差分が行単位で表示されます。`changed`と出たタスクが「本番で実際に変わる箇所」の予測です。

ただしdry-runには限界があります。check modeに対応していないモジュールは、何もせず何も報告しません。command・shellモジュールは`creates`や`removes`を指定しない限り何が変わるかを予測できないため、手前のタスクの結果を`register`で受けて分岐するPlaybookでは、check modeの結果と本番の結果が食い違います。もう1つの注意点は機密情報です。`--diff`はパスワードを含む設定ファイルの中身もそのまま表示するので、該当タスクには`diff: false`を付けて差分を出さないようにします。

### –limitで1台に絞って適用しchanged=0の再実行で冪等性を確かめる

dry-runで差分を確かめたら、`--limit web01`で1台だけに本番適用します。適用直後にもう一度同じコマンドを流し、実行結果の要約（PLAY RECAP）で`changed=0`になっているかを見ます。

2回目にも`changed`が出るタスクは、毎回なにかを書き換えています。原因の多くは、commandやshellで書いたタスクに`changed_when`が無いこと、テンプレートの中に実行時刻のような毎回変わる値を埋め込んでいることのどちらかです。ここを直してから全台へ流します。台数が多い環境では、プレイに`serial: 2`のように書くと、指定した台数ずつ順番に適用できます。並列で接続する台数の既定値は`--forks`の5です。

## 本番運用のPlaybook設計：変数の置き場所・タグ・ロール分割の判断基準

1本のPlaybookが動いた後、運用で問題になるのは「どこに値を書くか」と「一部だけ流したいときの扱い」です。

### group\_varsとextra-varsの使い分けと変数の優先順位で起きる事故

[公式の変数ガイド](https://docs.ansible.com/projects/ansible/latest/playbook%5Fguide/playbooks%5Fvariables.html)には22段階の優先順位が載っています。押さえるべき順序は次のとおりです。

| 置き場所              | 優先度   | 向いている値         |
| ----------------- | ----- | -------------- |
| ロールのdefaults      | 最も低い  | 上書きされる前提の既定値   |
| group\_vars/all   | 低い    | 全ホスト共通の値       |
| group\_vars/グループ名 | 中     | 環境・役割ごとの値      |
| host\_vars/ホスト名   | 中より上  | 1台だけ違う値        |
| プレイのvars          | 高い    | そのPlaybook固有の値 |
| \-e（extra vars）   | 常に最優先 | 実行時だけの一時的な値    |

事故が起きやすいのは2つの場面です。1つは、環境差を`group_vars`で管理しているのに、プレイの`vars`にも同じ変数を書き、環境ごとの値が効かなくなるケース。もう1つは、障害対応で`-e`付きで流した実行例が手順書に残り、次の担当者が同じ値で上書きし続けるケースです。環境で変わる値は`group_vars`、変わらない値だけをプレイの`vars`に置くと決めておけば、前者は防げます。

### tagsで一部のタスクだけ流すときのalwaysタグとhandlersの注意点

設定ファイルの配布だけを流したいときは、タスクに`tags: config`を付けて`--tags config`で実行します。[公式のtagsのページ](https://docs.ansible.com/projects/ansible/latest/playbook%5Fguide/playbooks%5Ftags.html)には、ここで外せない例外が2つ書かれています。

`always`タグを付けたタスクは、`--tags`で別のタグを指定しても実行されます。止めたいときは`--skip-tags always`を明示します。handlersはタグを一切無視し、タグで選ぶことも外すこともできません。`--tags config`で設定配布だけを流しても、変更があれば再読み込みのhandlerは動く、ということです。この挙動は再読み込み漏れを防ぐ方向に働くので、handlerにタグを付けて制御しようとしないでください。

### Playbookが肥大化したらrolesへ分ける判断とansible-lintでの点検

1本のPlaybookに複数のミドルウェアの手順が混ざり、別のPlaybookでも同じタスクを書き写すようになったら、[roles](https://docs.ansible.com/projects/ansible/latest/playbook%5Fguide/playbooks%5Freuse%5Froles.html)へ分ける時期です。ロールは`tasks`・`handlers`・`templates`・`defaults`などのディレクトリ構成が決まっていて、Playbook側は`roles:`に名前を並べるだけになります。目安は、同じタスク群を2つ目のPlaybookに書き写そうとした時点です。

```
# ansible-lintを入れてPlaybookを点検する
pip install ansible-lint
ansible-lint site.yml
```

[ansible-lint](https://github.com/ansible/ansible-lint)はPlaybook・ロール・コレクションの書き方を点検するツールで、PyPIでは2026年10月13日公開の26.10.1が最新でした。FQCNを使っていないモジュール名や、`changed_when`の無いcommandタスクなど、前の章で挙げた問題の多くを機械的に指摘します。ロールの動作をテストまで含めて確かめる方法は[Ansible Moleculeとは？v26系のansible-native設定とmolecule testの実行手順](https://www.issoh.co.jp/tech/details/17630/)で扱っています。

## Playbookを書くべき作業と書かない作業：shell多用・1回限りの手順は見送る

Playbookは書けば得になるわけではありません。書いた後の保守にも工数がかかるため、次の条件でPlaybookにするかどうかを決めます。

### commandとshellが過半を占めるPlaybookで冪等性が崩れる理由

手順書のコマンドをそのまま`ansible.builtin.shell`に移しただけのPlaybookは、Ansibleで書く意味がほとんどありません。shellのタスクは「すでにその状態か」を判断できず、流すたびに同じコマンドを実行して`changed`を返します。check modeでも中身を予測できないため、前の章で示したdry-runの手順が機能しなくなります。

判断基準は単純です。タスクの過半をモジュール（dnf・template・service・user・lineinfileなど）で書けるならPlaybookにします。独自のCLIツールの操作が大半を占め、モジュールで書けるのが一部だけなら、Playbookではなくシェルスクリプトとして版管理するほうが保守しやすくなります。年に1回しか発生しない構築作業も同じで、次に流すときにはansible-coreの系列もモジュールの引数も変わっている可能性が高く、書いた工数を回収できません。どの運用作業から自動化するかの線引きは[Ansibleによる運用自動化の進め方](https://www.issoh.co.jp/column/details/18092/)で費用対効果の試算式とあわせて整理しています。

### AWXやAnsible Automation Platformから実行する段階の目安

流す人が自分1人のうちは、手元の`ansible-playbook`で足ります。複数の担当者が同じPlaybookを流すようになると、実行記録、権限の分離、認証情報を各自の端末に置かない仕組みが必要です。ここでWeb画面から実行する製品を検討します。

商用のAnsible Automation Platform（旧Ansible Tower）では、Gitに置いたPlaybookを「ジョブテンプレート」として登録し、権限を持つ担当者が画面から実行します。費用の判断は[Ansible Automation Platformの費用と導入判断](https://www.issoh.co.jp/column/details/18090/)、OSSで始める選択肢は[Semaphore UIとは？Ansible・TerraformのWeb UIの導入手順と料金](https://www.issoh.co.jp/tech/details/12399/)にまとめました。どちらに載せても、Playbookの書き方は変わりません。ジョブテンプレート・Survey・Webhookの設定手順は[Ansible Towerとは：automation controllerへの改称とSurvey・Webhook・RBACの設定手順](https://www.issoh.co.jp/tech/details/18459/)で解説しています。

### Playbookの設計・レビューを外部に任せる場合の進め方と相談の範囲

社内に書ける人が1人しかいない状態でPlaybookを増やすと、その人の異動とともに誰も直せないコードが残ります。外部に任せる場合は、Playbookを納品物として受け取るだけでなく、ロールの分け方、変数の置き場所、dry-runから本番適用までの手順をレビュー可能な形で残してもらうことが条件です。一創では、サーバー構成のコード化からCIでのansible-lint実行、社内メンバーへの引き継ぎまでを[DevOps・CI/CD導入支援](https://www.issoh.co.jp/service/system/devops/)として請け負っています。既存の手順書を見せていただければ、Playbook化すべき作業と見送る作業の切り分けから始められます。

## よくある質問

Ansible Playbookの書き方と実行について、検索の多い質問に答えます。

### PlaybookとRoleの違いは何ですか？

Playbookは実行の入り口になるファイルで、どのホストに何を適用するかを決めます。Roleは、タスク・handlers・テンプレート・既定値を決まったディレクトリ構成にまとめた再利用の単位です。Playbookの中で`roles:`に名前を書いて呼び出します。最初はPlaybook1本で書き、同じ処理を別のPlaybookでも使いたくなった時点でRoleに切り出す順番で十分です。

### Playbookの拡張子は.ymlと.yamlのどちらがよいですか？

Ansibleはどちらの拡張子でも同じように読み込みます。公式ドキュメントの例は`.yml`が多く、`site.yml`という名前が慣習です。混在するとCIでlint対象を拡張子で指定するときに漏れが出るので、リポジトリ内ではどちらかに統一します。

### –checkで成功したのに本番適用で失敗するのはなぜですか？

check modeはホストに変更を加えないため、前のタスクの結果に依存する処理を正しく再現できません。たとえばパッケージを入れるタスクの後に、そのパッケージのコマンドを使うタスクがあると、dry-runではパッケージが入っていない前提で動きます。check modeに対応していないモジュールは何も実行しない点も原因になります。`--check`の結果は予測であり、最終確認は`--limit`で1台に適用して行ってください。

### 途中で失敗したPlaybookを続きから再実行できますか？

`--start-at-task`にタスク名を渡すと、そのタスクから実行を始められます。1タスクずつ確認しながら進めたい場合は`--step`を使います。ただし、モジュールで書かれたタスクは冪等なので、多くの場合は最初から流し直すほうが確実です。完了済みのタスクは`ok`になるだけで、状態は変わりません。

### sudoのパスワードが必要なサーバーではどう実行しますか？

プレイに`become: true`を書いたうえで、`ansible-playbook`に`-K`（`--ask-become-pass`）を付けると、実行開始時にパスワードの入力を求められます。パスワードをPlaybookや変数ファイルに平文で書くのは避け、どうしてもファイルに置く場合はansible-vaultで暗号化します。CIから実行する場合は、パスワード不要のsudo権限を持つ専用ユーザーを用意するのが一般的です。

## 関連記事

- [Ansibleとは？エージェントレスの仕組みとメリット・デメリット、Red Hat AAPの制約](https://www.issoh.co.jp/tech/details/6533/)：Playbookを動かすAnsible本体の仕組みと既定値の解説。
- [Ansibleによる運用自動化の進め方：対象作業の選び方と費用対効果・失敗パターン](https://www.issoh.co.jp/column/details/18092/)：どの作業からPlaybook化するかを決める判断材料。
- [Ansible Moleculeとは？v26系のansible-native設定とmolecule testの実行手順](https://www.issoh.co.jp/tech/details/17630/)：書いたロールを自動テストする方法。
- [構成管理とは？CMDB・IaC・ソフトウェア構成管理の違いと運用体制の作り方](https://www.issoh.co.jp/column/details/15291/)：Playbookによる構成管理を運用体制の側から整理した記事。
- [構成管理ツールの比較と選び方｜Ansible・Terraform・Chef・Puppet・OpenTofuの選定条件](https://www.issoh.co.jp/column/details/15293/)：Ansible以外の選択肢との比較。

---

出典: [Ansible Playbookの書き方と実行手順：–checkでのdry-runとhandlers【2.21系】](<https://www.issoh.co.jp/tech/details/18457/>)（株式会社一創）
