「ユーザーアカウントを10件、同じ設定で一括作成したい。コピペを繰り返すのはつらい」
「Playbookが100行を超えてきた。今日はNginxの設定変更だけ反映して、他のタスクはスキップしたい」
「設定ファイルの書き込みが失敗したとき、自動でバックアップから元に戻したい」
Ansibleを本格的に使い始めると、必ずこうした要求に直面します。単純な「命令を並べるだけ」のPlaybookは、環境差分への対応・複数リソースの一括操作・部分実行・エラー時の自動回復が必要になった瞬間に限界を迎えます。
この記事では、Ansibleの制御フロー3本柱であるwhen条件式(実行分岐)・loop繰り返し処理・tags部分実行を体系的に解説します。さらに本番運用で欠かせないblock・rescue・alwaysによるエラーハンドリング設計も含め、RHEL 10 / Rocky Linux 9の実行例とともに説明します。
この記事のポイント
・whenはOS種別・ファクト変数・register結果・グループ判定など多様な条件を記述できる
・loopはリスト・辞書を使った繰り返しで複数ユーザー・パッケージの一括操作を簡潔に書ける
・tagsで「インストールだけ」「設定だけ」を選択実行でき大規模Playbookの運用効率が上がる
・block/rescue/alwaysはPythonのtry/except/finallyに相当するエラーハンドリング構造
・3つを組み合わせれば環境別インストール・一括作成・差分適用を1Playbookで表現できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
Ansibleの制御フローが必要になる理由|シンプルPlaybookの限界
Ansibleを使い始めた頃は、タスクを上から順に並べるだけで十分でした。しかし運用が進むにつれ、次のような「壁」に必ずぶつかります。・環境差分の壁: 本番はRHEL 10、開発はUbuntu 24.04。OSが違えばパッケージ名もサービス管理コマンドも違う。
・繰り返しの壁: Webアプリ用ユーザーを5件作りたい。内容が少し違う5つのタスクをコピペするのは保守性がゼロ。
・部分適用の壁: 設定ファイルだけ更新したい。インストール済みのパッケージに触りたくない。
・障害回復の壁: 設定変更が失敗した後、サービスが壊れたまま放置される。失敗時の自動ロールバックをPlaybookに組み込めない。
これら4つの壁を突き破るのがwhen・loop・tags・block/rescue/alwaysです。4つを使いこなすことで、Playbookは「手順書の写し」から「設計として表現されたインフラの状態」に昇格します。
when条件式でタスクの実行を制御する方法
whenはタスクに実行条件を付けるキーワードです。条件がfalseのときはタスクがスキップされ、「changed」も「failed」も起きません。1. Ansibleファクト変数を使ったOS別分岐
最も多く使われるパターンが、Ansibleが自動収集するファクト変数によるOS判定です。--- - name: OSに応じたNginxインストール hosts: webservers tasks: - name: Nginxをインストールする(RHEL系) ansible.builtin.dnf: name: nginx state: present when: ansible_os_family == "RedHat" - name: Nginxをインストールする(Debian系) ansible.builtin.apt: name: nginx state: present when: ansible_os_family == "Debian"
TASK [Nginxをインストールする(RHEL系)] ***** ok: [web01.example.com] TASK [Nginxをインストールする(Debian系)] ***** skipping: [web01.example.com]
・
ansible_os_family: "RedHat" / "Debian" / "Suse" など。パッケージ管理の分岐に使う・
ansible_distribution: "Rocky" / "AlmaLinux" / "Ubuntu" など。より細かい分岐に使う・
ansible_distribution_major_version: "9" / "10" など。バージョン別対応に使う・
ansible_hostname: ホスト名。特定ホストにのみ適用するタスクに使う2. register変数を使った前タスクの結果による分岐
registerでタスクの結果を変数に格納し、後続タスクのwhenで参照するパターンです。既存の状態を確認してから次の処理を決める「チェックアンドアクト」の実装に使います。--- - name: サービス状態を確認してから設定を反映する hosts: webservers tasks: - name: サービス情報を収集する ansible.builtin.service_facts: - name: httpdが起動中の場合のみ設定ファイルを更新する ansible.builtin.template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf validate: "httpd -t -f %s" when: "'httpd.service' in ansible_facts.services and ansible_facts.services['httpd.service'].state == 'running'" notify: restart httpd - name: MySQLが存在するか確認する ansible.builtin.command: cmd: rpm -q mariadb-server register: mysql_check failed_when: false changed_when: false - name: MySQLが未インストールの場合のみインストールする ansible.builtin.dnf: name: mariadb-server state: present when: mysql_check.rc != 0
3. グループ判定とカスタム変数による分岐
インベントリのグループや独自変数を使った分岐は、環境ごとの設定差分を管理する定番パターンです。--- - name: 環境別セキュリティ設定 hosts: all tasks: - name: 本番環境のWebサーバーのみSELinuxをenforcingにする ansible.builtin.command: cmd: setenforce 1 when: - inventory_hostname in groups['webservers'] - server_env == "production" - name: ステージング以外では詳細ログを無効化する ansible.builtin.lineinfile: path: /etc/nginx/nginx.conf regexp: '^access_log' line: "access_log off;" when: server_env != "staging"
whenをリスト形式で書くと、全条件のAND(論理積)になります。# リスト形式 = すべての条件を満たす場合のみ実行(AND) when: - ansible_os_family == "RedHat" - ansible_distribution_major_version | int >= 9 - server_role is defined # OR条件は括弧で明示する when: server_role == "web" or server_role == "proxy"
loopで繰り返し処理を設計する方法
loopはAnsible 2.5以降の推奨繰り返し記法です。旧来のwith_itemsと同じ役割ですが、より統一的に使えます。1. シンプルリストのloop(パッケージ一括インストール)
- name: 必要なパッケージをまとめてインストールする ansible.builtin.dnf: name: "{{ item }}" state: present loop: - nginx - mariadb-server - php-fpm - firewalld
dnfモジュールはnameにリストを直接渡す書き方の方が効率的です。loopは1パッケージごとにモジュールを1回起動しますが、リスト渡しは1回のdnf呼び出しで済みます。# より効率的な書き方(loopより推奨) - name: 必要なパッケージをまとめてインストールする ansible.builtin.dnf: name: - nginx - mariadb-server - php-fpm - firewalld state: present
loopが本領を発揮するのは、「タスク単位で1リソースしか扱えないモジュール」(userモジュールなど)での繰り返しです。2. 辞書リストのloop(ユーザー一括作成)
辞書(キーと値のペア)のリストをloopに渡すと、item.キー名で各フィールドにアクセスできます。- name: Webアプリ用ユーザーを一括作成する ansible.builtin.user: name: "{{ item.name }}" groups: "{{ item.groups }}" comment: "{{ item.comment }}" state: present loop: - { name: "appuser", groups: "nginx", comment: "Web Application User" } - { name: "deployer", groups: "wheel,nginx", comment: "Deploy Bot User" } - { name: "monitor", groups: "systemd-journal", comment: "Monitoring Agent" } # 実行結果の例 TASK [Webアプリ用ユーザーを一括作成する] ***** changed: [web01.example.com] => (item={'name': 'appuser', 'groups': 'nginx', ...}) changed: [web01.example.com] => (item={'name': 'deployer', 'groups': 'wheel,nginx', ...}) changed: [web01.example.com] => (item={'name': 'monitor', 'groups': 'systemd-journal', ...})
3. loop_control.labelで出力を見やすくする
辞書リストのloopはデフォルトだと全フィールドが出力されて見づらいです。loop_control.labelで表示内容を絞り込めます。- name: Nginxバーチャルホストの設定ファイルを配置する ansible.builtin.template: src: "vhost.conf.j2" dest: "/etc/nginx/conf.d/{{ item.domain }}.conf" loop: - { domain: "app.example.com", port: 8080, upstream: "app_pool" } - { domain: "api.example.com", port: 8081, upstream: "api_pool" } - { domain: "admin.example.com",port: 8082, upstream: "admin_pool" } loop_control: label: "{{ item.domain }}" # ← ドメイン名だけ表示 # label指定後の出力(すっきり) changed: [web01] => (item=app.example.com) changed: [web01] => (item=api.example.com) changed: [web01] => (item=admin.example.com)
4. loopとwhenを組み合わせる
loopとwhenを組み合わせると、「リストの中から条件に合うものだけ処理する」ことができます。- name: 本番環境用ユーザーのみ作成する ansible.builtin.user: name: "{{ item.name }}" state: present loop: - { name: "appuser", env: "production" } - { name: "testuser", env: "staging" } - { name: "devuser", env: "development"} when: item.env == server_env loop_control: label: "{{ item.name }} ({{ item.env }})"
loopとwhenのパターンを実機で試したい方は、>> Ansibleハンズオン講座の詳細を見る をご覧ください。
tagsでPlaybookを部分実行する設計パターン
tagsはタスク・プレイ・roleに「ラベル」を付ける機能です。実行時に--tagsや--skip-tagsを指定することで、Playbook全体から特定の処理だけを選んで実行できます。1. タグの付け方(タスクレベルとプレイレベル)
--- - name: Webサーバーのフルセットアップ hosts: webservers tags: webserver # ← プレイ全体にタグを付ける tasks: - name: Nginxをインストールする ansible.builtin.dnf: name: nginx state: present tags: [install, nginx] # ← タスクに複数のタグを付ける - name: Nginx設定ファイルを配置する ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf tags: [config, nginx] notify: nginx restart - name: Nginxを起動・自動起動有効にする ansible.builtin.service: name: nginx state: started enabled: true tags: [service, nginx] - name: ログ収集用の監視エージェントをインストールする ansible.builtin.dnf: name: node_exporter state: present tags: [install, monitoring]
2. --tagsと--skip-tagsで実行対象を絞り込む
# インストールタスクだけ実行する ansible-playbook site.yml --tags install # 設定ファイル更新だけ実行する(サービス再起動なし) ansible-playbook site.yml --tags config # Nginx関連タスクだけ実行する ansible-playbook site.yml --tags nginx # 複数のタグを指定する(いずれかに一致するタスクを実行) ansible-playbook site.yml --tags "install,config" # 監視エージェントをスキップしてWebサーバーのみセットアップ ansible-playbook site.yml --skip-tags monitoring # 利用可能なタグを事前確認する(実行はしない) ansible-playbook site.yml --list-tags # 出力例 play #1 (webservers): Webサーバーのフルセットアップ TASK TAGS: [config, install, monitoring, nginx, service, webserver]
3. alwaysとneverの特殊タグ
alwaysタグを付けたタスクは、--skip-tagsでも除外されず必ず実行されます。逆にneverタグを付けたタスクは、--tags neverを明示しない限り実行されません。- name: 実行前の事前確認(常に実行する) ansible.builtin.debug: msg: "対象ホスト: {{ inventory_hostname }}, 環境: {{ server_env }}" tags: always # ← どんな--skip-tagsでも必ず実行 - name: 本番環境の全データを削除する(明示的に指定した時のみ実行) ansible.builtin.file: path: /var/lib/app/data state: absent tags: never # ← --tags neverを指定した時のみ実行(誤実行防止)
alwaysタグの典型的なユースケースは「前提確認タスク」(Ansibleバージョンの確認・ファクト収集・必須変数のバリデーション)です。neverタグは「通常は実行しない危険な処理」(DBリセット・全ファイル削除)の誤実行防止に使います。4. タグ設計のベストプラクティス
タグは「多すぎても少なすぎても使いにくい」ツールです。実務では次の2階層設計が管理しやすいです。・機能タグ(何をするか): install / config / service / cleanup
・コンポーネントタグ(何に対して): nginx / mysql / firewalld / monitoring
この設計だと「Nginxの設定だけ変更する」は
--tags "config,nginx"、「サービス起動系を全部スキップ」は--skip-tags serviceと直感的に指定できます。when・loop・tagsを組み合わせた実務設計パターン
3つを組み合わせた実例として、「本番・ステージング・開発の3環境に対して、複数のWebアプリユーザーを環境別条件でセットアップする」Playbookを示します。--- - name: 環境別Webアプリセットアップ hosts: all vars: app_users: - { name: "appuser", env: "production", uid: 1001 } - { name: "stagingapp",env: "staging", uid: 1002 } - { name: "devapp", env: "development", uid: 1003 } tasks: - name: OS別にNginxをインストールする ansible.builtin.package: name: nginx state: present when: ansible_os_family in ["RedHat", "Debian"] tags: [install, nginx] - name: 対象環境のWebアプリユーザーを作成する ansible.builtin.user: name: "{{ item.name }}" uid: "{{ item.uid }}" state: present loop: "{{ app_users }}" when: item.env == server_env loop_control: label: "{{ item.name }} ({{ item.env }})" tags: [setup, users] - name: 本番環境のみSSHパスワード認証を無効化する ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^PasswordAuthentication' line: "PasswordAuthentication no" when: - ansible_os_family == "RedHat" - server_env == "production" notify: restart sshd tags: [security, ssh, config] - name: ファイアウォールでHTTP・HTTPSを開放する ansible.builtin.command: cmd: "firewall-cmd --add-service={{ item }} --permanent" loop: - http - https when: inventory_hostname in groups['webservers'] tags: [config, firewalld]
# 本番環境でインストールとユーザー作成のみ実行 ansible-playbook site.yml -e "server_env=production" --tags "install,setup" # ステージングでセキュリティ設定だけ更新 ansible-playbook site.yml -e "server_env=staging" --tags security
firewall-cmdコマンドの動作確認にはLinux ポート確認の全コマンド(ss・lsofによる開放確認)も活用してください。Ansibleタスクが成功しても実際にポートが開いているかはssコマンドで確認するのが確実です。block・rescue・alwaysでエラーハンドリングを追加する方法
when・loop・tagsでPlaybookを構造化しても、タスクが失敗したときの処理まで書かなければ本番運用には耐えられません。「設定ファイルの書き込みが失敗したのにサービスが再起動されてしまった」「一部のホストで失敗したのに展開が続いてしまった」——こうした障害を防ぐのがblock・rescue・alwaysです。Pythonのtry / except / finallyに相当するAnsibleのエラーハンドリング構造です。1. block・rescue・alwaysの基本構造
# block/rescue/always の基本構造 - name: Webサーバー設定の適用(エラーハンドリング付き) block: - name: 設定ファイルをデプロイする ansible.builtin.template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf owner: root group: root mode: '0644' - name: 設定の構文チェックを実行する ansible.builtin.command: cmd: httpd -t changed_when: false - name: httpdを再起動する ansible.builtin.service: name: httpd state: restarted rescue: - name: 設定ファイルをバックアップから復元する ansible.builtin.copy: src: /etc/httpd/conf/httpd.conf.bak dest: /etc/httpd/conf/httpd.conf remote_src: yes - name: httpdを元の設定で再起動する ansible.builtin.service: name: httpd state: restarted - name: デプロイ失敗を明示的に通知する ansible.builtin.fail: msg: "rollback completed, but original deploy failed on {{ inventory_hostname }}" always: - name: 一時ファイルを削除する ansible.builtin.file: path: /tmp/httpd_deploy_lock state: absent ignore_errors: yes
rescueに制御が移る・rescue: 失敗時のみ実行される回復処理。注意: rescueが正常終了するとPlaybookは「回復した」とみなして次へ続行する。「ロールバックは成功したがデプロイ自体は失敗した」として後続を止めたい場合は末尾に
ansible.builtin.failを必ず入れる・always: block成功・rescue完了のどちらでも必ず実行されるクリーンアップ処理。always内タスクも失敗しうるため
ignore_errors: yesを添えて波及を防ぐ2. ignore_errors・failed_when・changed_whenの使い分け
blockを使わず局所的にエラーを制御したい場合は、以下の3つのフラグを使い分けます。# ignore_errors: 特定タスクのみエラーを無視して続行する - name: 既存のcronジョブを削除する(存在しなくても続行) ansible.builtin.cron: name: "old-backup-job" state: absent ignore_errors: yes # failed_when: 失敗条件を自分で定義する - name: プロセスの起動確認(grepはプロセスなし時にrc=1を返す) ansible.builtin.command: cmd: pgrep -x httpd register: httpd_check failed_when: httpd_check.rc != 0 and httpd_check.rc != 1 # rc=0: プロセスあり、rc=1: プロセスなし(想定内)、それ以外: 異常 # changed_when: commandモジュールの変更判定を自分で制御する - name: カスタムスクリプトで設定を変更する ansible.builtin.command: cmd: /opt/scripts/update-config.sh register: update_result changed_when: "'Updated' in update_result.stdout" # 出力にUpdatedが含まれる場合のみchangedとして扱う # changed_when: false で副作用なしチェックコマンドをok扱いにする - name: 設定の整合性チェックを実行する ansible.builtin.command: cmd: /opt/scripts/check-config.sh changed_when: false
ignore_errorsを乱用すると本物のエラーが握りつぶされるため、blockのrescueで代替できる場合はblockを優先します。failed_whenはregister変数と組み合わせて「終了コード0なのに実は失敗」「終了コード1だが想定内」を正確に表現できます。changed_when: falseは副作用のないチェックコマンドに付けて、不要な「changed」判定を防ぎます。block/rescue/alwaysを使った本番対応のPlaybook設計を実機で体験したい方は、>> Ansibleハンズオン講座の詳細を見る をご覧ください。
トラブルシュート|when・loop・tagsでよくある失敗と対処法
1. when条件が期待通りに評価されない
症状:ansible_distribution_major_version == 9 と書いたのに条件が一致しない。原因と対処: ファクト変数が文字列として返ってくるケースがあります。整数比較が必要な場合はJinja2フィルタで変換します。
# NG: 文字列と整数を比較しているため期待通りに動かないことがある when: ansible_distribution_major_version == 9 # OK: | int フィルタで整数に変換してから比較する when: ansible_distribution_major_version | int == 9 # OK: 文字列として比較する when: ansible_distribution_major_version == "9"
2. loopとregisterの結果がうまく取得できない
症状: loopの中でregisterした変数を次のタスクで参照すると、最後のループ結果しか入っていない。対処: loopと組み合わせた
registerは、変数名.resultsにリスト形式で全ループ結果が格納されます。- name: 複数のサービス状態を確認する ansible.builtin.service_facts: register: all_services - name: サービス起動確認の結果をデバッグ出力する ansible.builtin.debug: msg: "{{ item.key }}: {{ item.value.state }}" loop: "{{ ansible_facts.services | dict2items }}" when: item.key in ['nginx.service', 'mariadb.service', 'firewalld.service'] loop_control: label: "{{ item.key }}"
3. tagsがinclude_tasksで効かない
症状:include_tasksでインクルードしたファイル内のタスクにtags: configを付けても、--tags configで実行されない。原因:
include_tasksは動的インクルードのため、Playbookの解析時点ではタグが認識されません。静的インクルードのimport_tasksを使うか、include_tasks自体にタグを付けます。# import_tasks(静的): インクルード先のタグが --tags で認識される - name: Nginx設定タスクをインクルードする ansible.builtin.import_tasks: tasks/nginx_config.yml # include_tasks(動的): include自体にタグを付けることで対応する - name: Nginx設定タスクをインクルードする ansible.builtin.include_tasks: tasks/nginx_config.yml tags: [config, nginx]
4. rescue後にPlaybookが続行してしまう
症状: block内のタスクが失敗してrescueが動いたが、rescueが正常終了したのでPlaybookが次のタスクへ進んでしまった。原因と対処: rescueが正常終了するとAnsibleは「回復した」と判断し、後続タスクへ進みます。「ロールバックは成功したがデプロイ自体は失敗した」として後続を停止させたい場合は、rescueの末尾に
ansible.builtin.failを明示的に入れてください。rescue: - name: ロールバック処理を実行する ansible.builtin.copy: src: /etc/app/config.bak dest: /etc/app/config remote_src: yes # これがないとrescue後にPlaybookが続行される(要注意) - name: デプロイ失敗を明示的に通知する ansible.builtin.fail: msg: "Deployment failed and rolled back on {{ inventory_hostname }}"
本記事のまとめ
| やりたいこと | 使う機能と書き方 |
|---|---|
| OS種別でタスクを分岐させる | when: ansible_os_family == "RedHat" |
| コマンド結果で次のタスクを制御する | register: 変数名 + when: 変数名.rc == 0 |
| 複数の条件をすべて満たす場合に実行する | when: にリスト形式で条件を並べる(暗黙のand) |
| 同じ処理を複数のリソースに適用する | loop: [item1, item2, ...] + {{ item }} |
| 辞書リストで複数フィールドを繰り返す | loop: [{name: x, value: y}, ...] + {{ item.name }} |
| loop出力を見やすくする | loop_control: {label: "{{ item.name }}"} |
| インストールだけ・設定だけを実行する | tags: [install, config] + ansible-playbook --tags install |
| 特定のコンポーネントだけスキップする | ansible-playbook --skip-tags monitoring |
| 常に実行するタスクを指定する | tags: always |
| 明示的に指定した時のみ実行する | tags: never |
| 失敗時に自動でロールバックする | blockで処理をまとめ、rescueに回復処理を書く |
| 成否に関わらず後処理を必ず実行する | alwaysにクリーンアップ処理を書く(ignore_errors: yesも添える) |
| 特定タスクのエラーを無視して続行する | ignore_errors: yes |
| コマンドの失敗・成功条件を自分で定義する | failed_when: 変数.rc != 0またはchanged_when: false |
Ansible実践ハンズオンの詳細を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 前のページへ:Ansible GalaxyでRoleを管理する方法|requirements.ymlと自社role設計の実践
- この記事の属するカテゴリ:Ansibleへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます