はじめに
本記事は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
こういうインベントリファイルを作っており、最初はそれで問題無かったのに、「追加の設定変更が発生してdns
とdb
だけを対象にAnsibleで処理したい要件が発生」とか、「master
以外のnodes
に対して処理」みたいなのを後からやる必要が発生し、プレイブックの編集のみでどう書けばいいか、という内容になります。
Targetセクションのリスト操作
---
- 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]
連結(重複要素は自動排除)
(例が悪いのだけど)nodes
とmaster
を連結すると、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で配列を連結する+
を使えば、単純にリストが連結される。
そのため、nodes
とmaster
を連結すると、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テンプレート。
(事前にall
でgather_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
表になって見やすくなってるけど、or
やand
条件などの指定方法について書かれている。
記述 | 内容 |
---|---|
webservers:dbservers |
webservers グループとdbservers グループどちらかにあるホスト |
webservers:!atlanta |
webservers グループのうちatlanta グループに含まれないホスト |
webservers:&staging |
webservers グループのうちstaging グループにも含まれるホスト |