6
4

More than 3 years have passed since last update.

ホストグループを例にAnsibleのプレイブックでリストを連結・除外する

Last updated at Posted at 2019-12-17

はじめに

本記事はAnsible 3 Advent Calendar 2019の18日目のエントリになります。

インベントリファイルをいじっちゃだめと言われたら…

様々な大人の事情によって、(サーバー設定などの仕様の変更が発生した場合にその範囲のみ)プレイブックは編集OKだけど(処理対象に変更は発生しないはずという理由で)インベントリは編集不可という条件下で、プレイブック(プレイ)のみ編集してホストグループなどのリストを連結したり特定項目を除外する操作について説明します。

文書だとわかりづらいので実際の入出力を見ていきましょう。

インベントリファイル例

こんなホスト構成のインベントリがあったとします。

[master]
master1.example.org
master2.example.org
master3.example.org

[nodes]
master1.example.org
master2.example.org
master3.example.org
node1.example.org
node2.example.org
node3.example.org

[dns]
ns1.example.org
ns2.example.org

[nas]
nas.example.org

[db]
db1.example.org
db2.example.org

こういうインベントリファイルを作っており、最初はそれで問題無かったのに、「追加の設定変更が発生してdnsdbだけを対象にAnsibleで処理したい要件が発生」とか、「master以外のnodesに対して処理」みたいなのを後からやる必要が発生し、プレイブックの編集のみでどう書けばいいか、という内容になります。

Targetセクションのリスト操作

playbook.yml
---
- hosts: group_name

この部分です。

※ 2021.01.28追記: ターゲットホストの結合については、公式ドキュメントに記載があります。
Patterns: targeting hosts and groups — Ansible Documentation
記事末尾にも情報追加

連結

連結は、,で羅列すればOKです。

- hosts: dns, master
  gather_facts: False
  tasks:
    - ping:
結果
PLAY [dns, master] *************************************************************

TASK [ping] ********************************************************************
ok: [ns2.example.org]
ok: [master2.example.org]
ok: [ns1.example.org]
ok: [master3.example.org]
ok: [master1.example.org]

連結(重複要素は自動排除)

(例が悪いのだけど)nodesmasterを連結すると、master1.example.org,master2.example.org,master3.example.orgがダブってしまうけど、タスクの処理では1回しか実行されないのでご安心を。

- hosts: nodes, master
  gather_facts: False
  tasks:
    - ping:
結果
PLAY [nodes, master] ***********************************************************

TASK [ping] ********************************************************************
ok: [node2.example.org]
ok: [node1.example.org]
ok: [master2.example.org]
ok: [master3.example.org]
ok: [master1.example.org]
ok: [node3.example.org]

除外

dns以外すべて」という場合、dns以外を連結してもいいですが、allからdnsを引くと楽。
除外の場合は頭に!を指定します。

- hosts: all, !dns
  gather_facts: False
  tasks:
    - ping:
結果
PLAY [all, !dns] ***************************************************************

TASK [ping] ********************************************************************
ok: [master2.example.org]
ok: [master1.example.org]
ok: [master3.example.org]
ok: [db1.example.org]
ok: [nas.example.org]
ok: [node1.example.org]
ok: [db2.example.org]
ok: [node2.example.org]
ok: [node3.example.org]

除外(1件のみ)

masterグループ、ただしmaster3.example.orgを除く場合。
ユースケース的にはlocalhost以外のall、みたいな場合に使うかな?

- hosts: master, !master3.example.org
  gather_facts: False
  tasks:
    - ping:
結果
PLAY [master, !master3.example.org] ********************************************

TASK [ping] ********************************************************************
ok: [master2.example.org]
ok: [master1.example.org]

グループ中1件のみ

masterグループのうち任意の1台で実行されればOKの場合。
kubectlとかocなどの、どのホストで実行してもOKな場合とか。

- hosts: master[0]
  gather_facts: False
  tasks:
    - ping:
結果
PLAY [master[0]] ***************************************************************

TASK [ping] ********************************************************************
ok: [master1.example.org]

これはリスト操作でなく、run_onceを使っても同様の動作になります。

- hosts: master
  gather_facts: False
  tasks:
    - ping:
      run_once: True
結果
PLAY [master] ******************************************************************

TASK [ping] ********************************************************************
ok: [master1.example.org]

with_items/loopでのリスト操作

使用しているフィルターの説明はこちら:Set Theory Filters

連結(重複あり)

Pythonで配列を連結する+を使えば、単純にリストが連結される。
そのため、nodesmasterを連結すると、master1,master2,master3が重複します。

  - name: concat by "+"
    debug:
      msg: "{{ item }}"
    with_items:
    - "{{ groups.nodes + groups.master }}"
結果
TASK [concat by "+"] ***********************************************************
ok: [localhost] => (item=master1.example.org) => {
    "msg": "master1.example.org"
}
ok: [localhost] => (item=master2.example.org) => {
    "msg": "master2.example.org"
}
ok: [localhost] => (item=master3.example.org) => {
    "msg": "master3.example.org"
}
ok: [localhost] => (item=node1.example.org) => {
    "msg": "node1.example.org"
}
ok: [localhost] => (item=node2.example.org) => {
    "msg": "node2.example.org"
}
ok: [localhost] => (item=node3.example.org) => {
    "msg": "node3.example.org"
}
ok: [localhost] => (item=master1.example.org) => {
    "msg": "master1.example.org"
}
ok: [localhost] => (item=master2.example.org) => {
    "msg": "master2.example.org"
}
ok: [localhost] => (item=master3.example.org) => {
    "msg": "master3.example.org"
}

連結(uniqueによる重複排除)

重複要素のあるリストはuniqueフィルターを使うことで重複分を除去できます。

  - name: concat "+" | unique
    debug:
      msg: "{{ item }}"
    with_items:
    - "{{ (groups.nodes + groups.master) | unique }}"
結果
TASK [concat "+" | unique] *****************************************************
ok: [localhost] => (item=master1.example.org) => {
    "msg": "master1.example.org"
}
ok: [localhost] => (item=master2.example.org) => {
    "msg": "master2.example.org"
}
ok: [localhost] => (item=master3.example.org) => {
    "msg": "master3.example.org"
}
ok: [localhost] => (item=node1.example.org) => {
    "msg": "node1.example.org"
}
ok: [localhost] => (item=node2.example.org) => {
    "msg": "node2.example.org"
}
ok: [localhost] => (item=node3.example.org) => {
    "msg": "node3.example.org"
}

連結(unionによる重複排除)

リストの連結時に+でなくunionを使うと、初めから重複要素が除かれます。

  - name: concat by union
    debug:
      msg: "{{ item }}"
    with_items:
    - "{{ groups.nodes | union(groups.master) }}"
結果
TASK [concat by union] *********************************************************
ok: [localhost] => (item=master1.example.org) => {
    "msg": "master1.example.org"
}
ok: [localhost] => (item=master2.example.org) => {
    "msg": "master2.example.org"
}
ok: [localhost] => (item=master3.example.org) => {
    "msg": "master3.example.org"
}
ok: [localhost] => (item=node1.example.org) => {
    "msg": "node1.example.org"
}
ok: [localhost] => (item=node2.example.org) => {
    "msg": "node2.example.org"
}
ok: [localhost] => (item=node3.example.org) => {
    "msg": "node3.example.org"
}

除外

nodesのうちmasterを含まないもの、という場合。

  - name: nodes - master by difference
    debug:
      msg: "{{ item }}"
    with_items:
    - "{{ groups.nodes | difference(groups.master) }}"
結果
TASK [nodes - master by difference] ********************************************
ok: [localhost] => (item=node1.example.org) => {
    "msg": "node1.example.org"
}
ok: [localhost] => (item=node2.example.org) => {
    "msg": "node2.example.org"
}
ok: [localhost] => (item=node3.example.org) => {
    "msg": "node3.example.org"
}

除外(1件のみ)

nodesのうちmaster3だけ除く、の場合。

  - name: nodes - "master3"
    debug:
      msg: "{{ item }}"
    with_items:
    - "{{ groups.nodes | difference('master3.example.org') }}"
結果
TASK [nodes - "master3"] *******************************************************
ok: [localhost] => (item=master1.example.org) => {
    "msg": "master1.example.org"
}
ok: [localhost] => (item=master2.example.org) => {
    "msg": "master2.example.org"
}
ok: [localhost] => (item=node1.example.org) => {
    "msg": "node1.example.org"
}
ok: [localhost] => (item=node2.example.org) => {
    "msg": "node2.example.org"
}
ok: [localhost] => (item=node3.example.org) => {
    "msg": "node3.example.org"
}

グループ中1件のみ

masterの1件のみ、という場合。
(with_items使う必要はないんだけど、まぁ例ということで)

  - name: one of master
    debug:
      msg: "{{ item }}"
    with_items:
    - "{{ groups.master[0] }}"
結果
TASK [one of master] ***********************************************************
ok: [localhost] => (item=master1.example.org) => {
    "msg": "master1.example.org"
}

ちなみにsymmetric_differenceってなんだろ。よくわからなかった。

Jinja2テンプレートでのリスト操作

with_itemsなどでの動作と同じです。

連結(重複あり)

{% for item in (groups.nodes + groups.master) %}
- {{ item }}
{% endfor %}
結果
- master1.example.org
- master2.example.org
- master3.example.org
- node1.example.org
- node2.example.org
- node3.example.org
- master1.example.org
- master2.example.org
- master3.example.org

uniqueによる重複除外した連結

{% for item in (groups.nodes + groups.master) | unique %}
- {{ item }}
{% endfor %}
結果
- master1.example.org
- master2.example.org
- master3.example.org
- node1.example.org
- node2.example.org
- node3.example.org

unionによる重複除外した連結

{% for item in (groups.nodes | union(groups.master)) %}
- {{ item }}
{% endfor %}
結果
- master1.example.org
- master2.example.org
- master3.example.org
- node1.example.org
- node2.example.org
- node3.example.org

除外

{% for item in (groups.nodes | difference(groups.master)) %}
- {{ item }}
{% endfor %}
結果
- node1.example.org
- node2.example.org
- node3.example.org

まとめ

ということで、インベントリファイルのグループの親子設定等をしなくても、プレイブック側でリストの操作ができることを確認してみました。
ただし、YAMLの特徴である可読性の高さを失う恐れがあるので、プロジェクトの偉い人を頑張って説得してインベントリファイルの修正を素直に行いましょう(本音)

おまけ

ホストグループのホスト名からfactsのIPアドレスを取得するJinja2テンプレート。
(事前にallgather_facts: Trueしておくこと)

{% for item in (groups.all | difference(groups.master)) %}
{{ hostvars[item].ansible_facts.default_ipv4.address }}  {{ item }}
{% endfor %}

/etc/hosts作成やDNSサーバー構築用に備忘録。


参考


2021.01.28追記: ターゲットホストのパターン

Patterns: targeting hosts and groups — Ansible Documentation

表になって見やすくなってるけど、orand条件などの指定方法について書かれている。

記述 内容
webservers:dbservers webserversグループとdbserversグループどちらかにあるホスト
webservers:!atlanta webserversグループのうちatlantaグループに含まれないホスト
webservers:&staging webserversグループのうちstagingグループにも含まれるホスト
6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4