「タスクをコピペで増やす」のは、今日で終わりにしましょう。
こんにちは!「LINUX工房」管理人の「リナックス先生」です。
前回(第1回)は、変数とFactsを使って「値」を外に出し、Playbookの再利用性を高める方法を学びました。
しかし、実務の現場では「値を変える」だけでは対応できないケースが多々あります。
- 「開発チームのメンバー10人分のユーザーアカウントを作成したい」
- 「Webサーバーの設定ファイルを、必要なパッケージと一緒にまとめてインストールしたい」
- 「管理対象のサーバーに、CentOSとUbuntuが混在している」
もしあなたが、ユーザー作成のタスクを10回コピペしたり、OSごとに別のPlaybookファイルを作ったりしているなら、それはAnsibleのポテンシャルを浪費しています。
今回は、プログラミングの基本である「繰り返し(Loop)」と「条件分岐(When)」をマスターし、スマートで短いPlaybookを書く技術を身に着けましょう。
先生、まさにそれで困ってました!
新入社員が入るたびに、ユーザー作成のタスクをコピペして追加してたんです。
Playbookが縦に長くなりすぎて、どこを編集してるのか分からなくなっちゃって…。
あと、この前Ubuntuサーバーに誤ってyumコマンドを実行してエラーにしちゃいました。
コウ君、それは典型的な「Ansibleアンチパターン」ね。
Ansibleは単なる手順書じゃなくて、プログラムなの。
「リストにある全員を作成する」「OSがUbuntuならaptを使う」といったロジックを組み込めば、Playbookは驚くほど短く、そして賢くなるわ。
今回は少し頭を使うけど、これを覚えるとAnsibleの世界が一気に広がるわよ!
本記事では、実務で頻出する「ユーザー一括管理」や「OS別対応」を題材に、LoopとWhenの具体的な使い方と、プロならではの応用テクニックを解説します。
🚀 【続・Ansible講座】本連載のカリキュラム
- 【第1回】変数(Variables)とFactsの活用:環境差異を吸収する
- 【第2回】ループと条件分岐 (Loop & When)(今回):複雑なロジックを組む
- 【第3回】ハンドラーと通知 (Handlers):無駄な再起動を防ぐ
- 【第4回】テンプレート (Jinja2):設定ファイルを動的に生成する
- 【第5回】ロール (Roles) の設計:Playbookを部品化して再利用する
- 【第6回】Ansible Vault:パスワードなどの機密情報を暗号化する
- 【第7回】エラーハンドリングとデバッグ:止まらない自動化のために
- 【第8回】実践プロジェクト:LAMP環境の完全自動構築と総まとめ
目次
1. 繰り返し処理「loop」の基本
同じモジュールを、異なるパラメータ(値)で何度も実行したい場合に使います。
以前のバージョンのAnsibleでは with_items が使われていましたが、現在はより柔軟な loop が推奨されています。
1-1. 最もシンプルなリストのループ
例えば、httpd, mariadb-server, git の3つのパッケージをインストールしたいとします。
これらを別々のタスクに書くのは非効率です。
悪い例(タスクのコピペ):
- name: httpdをインストール
ansible.builtin.dnf:
name: httpd
state: present
- name: gitをインストール
ansible.builtin.dnf:
name: git
state: present
# ...延々と続く
良い例(loopの使用):
- name: 必要なパッケージを一括インストール
ansible.builtin.dnf:
name: "{{ item }}"
state: present
loop:
- httpd
- mariadb-server
- git
解説:
loop:の下にリスト形式で値を記述します。- タスク内で
"{{ item }}"と書くと、ループのたびにリストの値が順番に入ります。 - これだけで、タスク定義は1つで済み、パッケージが増えてもリストに追加するだけで済みます。
⚠️ 注意:dnf/yumモジュールの特例
実は dnf や apt モジュール自体がリストを受け取れる仕様になっているため、単純なパッケージインストールなら loop を使わず、name に直接リストを渡すほうが高速です。
しかし、user や service など多くのモジュールはリストを受け取れないため、loop が必須になります。
2. 【実践】辞書リストを使ったユーザー一括作成
実務では「単なる値のリスト」ではなく、「ユーザー名、グループ、パスワード」といった複数の属性を持つデータを扱いたい場合がほとんどです。
この場合、「辞書(ハッシュ)のリスト」をループさせます。
要件
- ユーザー
aliceはdevelopersグループに所属させる。 - ユーザー
bobはmanagersグループに所属させる。 - それぞれの公開鍵も設定したい。
Playbookの記述例
---
- name: ユーザー管理Playbook
hosts: all
become: true
vars:
# ユーザー情報を辞書のリストとして定義
users_list:
- name: alice
groups: wheel
ssh_key: "ssh-rsa AAAAB3NzaC1yc2E..."
- name: bob
groups: users
ssh_key: "ssh-rsa BBBBB3NzaC1yc2E..."
tasks:
- name: ユーザーを作成する
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
state: present
create_home: true
loop: "{{ users_list }}"
- name: SSH公開鍵を配置する
ansible.builtin.authorized_key:
user: "{{ item.name }}"
key: "{{ item.ssh_key }}"
state: present
loop: "{{ users_list }}"
解説:
loop: "{{ users_list }}"で、変数をループに渡しています。- タスク内では
item.nameやitem.groupsのように、ドット繋ぎで辞書のキーにアクセスできます。 - これで、ユーザーが100人になっても、
varsのリストを更新するだけで対応できます。
💡 プロのノウハウ:変数の外出し
第1回で学んだ group_vars に users_list を定義しておけば、Playbook本体は一切変更せずに、インベントリごとに作成するユーザーを変えることができます。
「本番環境には管理者だけ、開発環境には全員」といった運用が容易になります。
3. 条件分岐「when」の基本
「特定の条件を満たすときだけタスクを実行したい」場合に使うのが when です。
プログラミングの if 文に相当します。
3-1. 基本的な構文
タスクの最後に when: 条件式 を記述します。
- name: 特定のホストだけで実行するタスク
ansible.builtin.debug:
msg: "このメッセージは web01 サーバーでのみ表示されます"
when: inventory_hostname == "web01"
3-2. 比較演算子と論理演算子
条件式には、Pythonライクな演算子が使えます。
| 演算子 | 意味 | 例 |
|---|---|---|
== |
等しい | ansible_distribution == "Ubuntu" |
!= |
等しくない | ansible_os_family != "RedHat" |
>, < |
大小比較 | ansible_memtotal_mb < 1024 |
in |
リストに含まれる | inventory_hostname in groups['webservers'] |
not |
否定 | not result.stat.exists |
3-3. 複数条件(AND / OR)
複数の条件を組み合わせることも可能です。
AND(かつ):リスト形式で書く
![]() |
ゼロからスタート! 教育系YouTuberまさるのLinuC1冊目の教科書 新品価格 |
when:
- ansible_distribution == "CentOS"
- ansible_distribution_major_version == "8"
OR(または):1行で or を使う
when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
4. 【実践】OSファミリーによる処理の分岐
実務で最もよく使うのが、「RedHat系ならdnf、Debian系ならaptを使う」という分岐です。
Ansible Facts の ansible_os_family 変数を使います。
クロスプラットフォームなPlaybookの例
---
- name: Webサーバー構築 (Multi OS)
hosts: all
become: true
tasks:
# RedHat系 (CentOS, AlmaLinux, RHEL, Rocky) 用の処理
- name: Install Apache (RedHat)
ansible.builtin.dnf:
name: httpd
state: present
when: ansible_os_family == "RedHat"
# Debian系 (Ubuntu, Debian) 用の処理
- name: Install Apache (Debian)
ansible.builtin.apt:
name: apache2
state: present
update_cache: yes
when: ansible_os_family == "Debian"
# サービス名は変数で切り替えるのも手だが、ここではシンプルに分岐
- name: Start Apache (RedHat)
ansible.builtin.service:
name: httpd
state: started
enabled: true
when: ansible_os_family == "RedHat"
- name: Start Apache (Debian)
ansible.builtin.service:
name: apache2
state: started
enabled: true
when: ansible_os_family == "Debian"
これにより、対象ホストがAlmaLinuxでもUbuntuでも、適切なコマンドが実行される「賢いPlaybook」になります。
条件に合致しないタスクは skipping と表示されてスキップされます。
5. 「register」と「when」のコンボ技
第1回で紹介した、コマンド実行結果を保存する register。
これと when を組み合わせることで、「前のタスクの結果に応じて次のタスクを実行する」ことができます。
例:ファイルが存在しない場合のみ作成する
通常、Ansibleのモジュールは自動的に存在確認をしてくれます(冪等性)。
しかし、command モジュールなどで独自の処理をする場合は、自分でチェックロジックを作る必要があります。
tasks:
# 1. ファイルの存在確認
- name: 設定ファイルの存在確認
ansible.builtin.stat:
path: /etc/myapp/config.conf
register: config_file_check
# 2. 存在しない場合のみ、初期化コマンドを実行
- name: 設定ファイルの初期生成
ansible.builtin.command: /usr/local/bin/myapp-init
when: not config_file_check.stat.exists
解説:
statモジュールでファイル情報を取得し、結果をconfig_file_checkに入れます。stat.existsはファイルがあればtrue、なければfalseになります。when: not ...とすることで、「存在しない場合(falseの時)」だけタスクを実行させます。
これにより、「何度実行しても安全な(初期化コマンドを連打しない)」Playbookが作れます。
6. 【プロのノウハウ】failed_when と changed_when
command や shell モジュールを使う際、Ansibleは「終了コードが0以外なら失敗(failed)」「実行すれば必ず変更あり(changed)」と判断します。
しかし、実務ではこの判定をカスタマイズしたい場面があります。
6-1. エラー判定を変える (failed_when)
例えば、「grepコマンドで検索して、見つからなくてもエラーにしたくない(終了コード1でもOKとしたい)」場合。
- name: ログファイルから特定のエラーを検索
ansible.builtin.command: grep "ERROR" /var/log/app.log
register: grep_result
# 終了コードが0(見つかった)でも1(見つからない)でもOKとする。それ以外(2など)はエラー。
failed_when: grep_result.rc not in [0, 1]
# 変更は発生しないので changed は false にする
changed_when: false
6-2. 変更判定を変える (changed_when)
スクリプトを実行したけれど、実際には何も変更していない場合、Ansibleが「changed(黄色)」と報告してくるのはノイズになります。
条件を指定して「変更なし(緑色)」と扱わせることができます。
- name: アプリケーションのバージョン確認
ansible.builtin.command: /usr/local/bin/myapp --version
register: app_version
# このタスクは確認だけなので、常に「変更なし」とする
changed_when: false
これらを適切に設定することで、実行結果のレポートが正確になり、「本当に何か変更があったのか?」が一目で分かるようになります。
これは「美しいPlaybook」を書くための重要な作法です。
まとめ:Ansibleに知能を与えよう
お疲れ様でした!
第2回では、Ansibleをプログラミング言語のように操るための「Loop」と「When」を学びました。
今回の要点まとめ:
loopを使えば、似たようなタスクを一行にまとめられる。- 辞書リストのループを使えば、ユーザー管理などの複雑な処理も一括化できる。
whenを使えば、OSの違いやファイル有無に応じた条件分岐ができる。- Facts (
ansible_os_familyなど) は条件分岐の強力な材料になる。 failed_when/changed_whenを使いこなして、実行結果をコントロールする。
すごい…!
あんなに長かったPlaybookが、変数とループを使ったら半分以下の行数になりました!
しかも、Ubuntuサーバーに対して実行してもエラーにならずに、勝手にaptを使ってくれて感動です!
次回は、設定ファイルを書き換えた後に自動でサービスを再起動させる仕組み、「ハンドラー (Handlers)」について学びます。
「設定を変えたのに反映されない!」というミスを防ぎ、無駄な再起動も抑える、運用の要となる機能です。
次回、【第3回】ハンドラーと通知 (Handlers):無駄な再起動を防ぐ でお会いしましょう!
▼ 複数OS環境でAnsibleを試すなら ▼
UbuntuもAlmaLinuxも選べる
「おすすめVPS」
インフラ自動化スキルを活かす
「ITエンジニア転職」


コメント