さくらインターネット Advent Calendar 2021 16日目の記事です。
概要
Ansibleのインベントリを動的に生成する方法として、「ダイナミックインベントリ」がよく使われていると思いますが、「インベントリプラグイン」という別の方法もあるよ、というお話です。
インベントリプラグインは、ダイナミックインベントリと比べて仕組みが少し複雑ですが、ケースによってはインベントリプラグインを用いたほうが簡潔にプレイブックを構成できる場合があります。
さっそく試してみる
例として、以下のようなExcelファイル(book1.xlsx)をAnsibleのインベントリとして利用してみます。
左から、ホスト名、IPアドレス、用途といったイメージです。
インベントリプラグイン場所を指定しておく
デフォルトのままだと扱いにくいため、インベントリプラグインを参照するディレクトリを設定しておきます。
[defaults]
inventory_plugins=./plugins/inventory
デフォルトの値は以下のドキュメントに記載があります。
インベントリファイルを作る
ansible-playbook
コマンドの -i
オプションで指定する、インベントリファイルを作成します。
plugin
にはこれから作成するインベントリプラグイン名を記載し、files
には読み込むExcelファイルを定義しておきます。
plugin: xlsx
files:
- "book1.xlsx"
インベントリプラグインをつくる
とても雑ですが、Excelファイルを読み込んでインベントリに追加するスクリプトです。
import openpyxl
from ansible.plugins.inventory import BaseInventoryPlugin
class InventoryModule(BaseInventoryPlugin):
NAME = "xlsx"
def verify_file(self, path):
valid = False
if super(InventoryModule, self).verify_file(path):
if path.endswith(".yml"):
valid = True
return valid
return True
def _load_xlsx(self, path):
inventory_data = self.loader.load_from_file(path, cache=False)
nodes = []
for f in inventory_data.get("files"):
wb = openpyxl.load_workbook(f)
ws = wb["Sheet1"]
for row in ws.rows:
nodes.append(
{
"hostname": row[0].value,
"address": row[1].value,
"role": row[2].value,
}
)
return nodes
def parse(self, inventory, loader, path, cache=True):
super(InventoryModule, self).parse(inventory, loader, path, cache)
xlsx_data = self._load_xlsx(path)
for data in xlsx_data:
self.inventory.add_host(data["hostname"])
self.inventory.set_variable(
data["hostname"], "ansible_host", data["address"]
)
self.inventory.add_group(data["role"])
self._populate_host_vars([data["hostname"]], {}, data["role"])
self.inventory.add_child("all", data["role"])
詳細は割愛します。自作される場合は以下のリポジトリが大変参考になります。
実行してみる
無事にExcelファイルの中身をインベントリとして扱うことができました!
$ ansible-inventory -i xlsx.yml --list
{
"_meta": {
"hostvars": {
"node1": {
"ansible_host": "192.0.2.1"
},
"node2": {
"ansible_host": "192.0.2.2"
},
"node3": {
"ansible_host": "192.0.2.3"
}
}
},
"all": {
"children": [
"db",
"ungrouped",
"web"
]
},
"db": {
"hosts": [
"node2",
"node3"
]
},
"web": {
"hosts": [
"node1"
]
}
}
おまけ
たとえばこんなファイル(book2.xlsx)が追加されたとしても
インベントリファイルに追加するだけでOKです。
plugin: xlsx
files:
- "book1.xlsx"
- "book2.xlsx"
$ ansible-inventory -i xlsx.yml --list
{
"_meta": {
"hostvars": {
"node1": {
"ansible_host": "192.0.2.1"
},
"node2": {
"ansible_host": "192.0.2.2"
},
"node3": {
"ansible_host": "192.0.2.3"
},
"node4": {
"ansible_host": "192.0.2.4"
},
"node5": {
"ansible_host": "192.0.2.5"
},
"node6": {
"ansible_host": "192.0.2.6"
}
}
},
"all": {
"children": [
"db",
"ungrouped",
"web"
]
},
"db": {
"hosts": [
"node2",
"node3",
"node5",
"node6"
]
},
"web": {
"hosts": [
"node1",
"node4"
]
}
}
なお作成したファイルのディレクトリ構造は以下の通りです。
$ tree
.
├── ansible.cfg
├── book1.xlsx
├── book2.xlsx
├── plugins
│ └── inventory
│ └── xlsx.py
└── xlsx.yml
おわりに
ダイナミックインベントリの場合、与えられる引数として環境変数を主に使うことになるかと思いますが、インベントリプラグインの場合はインベントリファイルにいくらでも定義ができるため自由度が高く、より柔軟なインベントリを構成することができます。ダイナミックインベントリで悩んでいる方はぜひお試しください。