はじめに
監視設計は専任ではなく、開発に関わる人は誰でも行えることが望ましいですが、監視ツールのひとつである Zabbix は慣れていないと設定が難しい面があると思います。
そこで、Excelのヒアリングシートから Ansible で Zabbix に監視テンプレートを登録できるようにしてみます。
なお、本記事は Ansible Advent Calendar 2020 の 12/10 の記事です。
前提とする環境
以下の環境で動作確認しました。
- CentOS 8.2
- Python 3.6.8
- zabbix-api 0.5.4
- Ansible 2.10.3
環境構築手順は以下の通りです。[Ansible] CentOS 8 に Ansible をインストールする(Python 3 + venv + pip) - てくなべ (tekunabe) の手順のままですので、解説はそちらをご覧ください。
sudo dnf install python3 -y
python3 -m venv ansible-py3
source ansible-py3/bin/activate
pip3 install ansible
pip3 install zabbix-api
zabbix_template モジュールを読む
Ansible の zabbix_template モジュールがどんなオプションを指定して Zabbix API に投げているのか気になったので、ソースを読んでみました。
def import_template(self, template_content, template_type='json'):
if self._module.check_mode:
self._module.exit_json(changed=True)
# rules schema latest version
update_rules = {
'applications': {
'createMissing': True,
'deleteMissing': True
},
'discoveryRules': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'graphs': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'groups': {
'createMissing': True
},
'httptests': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'items': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'templates': {
'createMissing': True,
'updateExisting': True
},
'templateLinkage': {
'createMissing': True
},
'templateScreens': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'triggers': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'valueMaps': {
'createMissing': True,
'updateExisting': True
}
}
(中略)
import_data = {'format': template_type, 'source': template_content, 'rules': update_rules}
self._zapi.configuration.import_(import_data)
テンプレートをインポートする時は、configuration.import を利用していて、「deleteMissing」を True に指定しています。このオプションはデフォルトではないのですが、これがないと、インポート先のテンプレートが存在する場合、前の設定が残ってしまい、削除したはずのアイテムやトリガーが残ってる!? という事態になってしまいます。
Excelヒアリングシートから Zabbix テンプレートをインポートするまでの流れ
以下の流れで処理します。
- Excelヒアリングシートからコピペしてインポート用の情報をTSV形式で書く (手動)
-
- で作成した TSV ファイルを読み、インポート用のXMLファイルを生成する (Ansible)
-
- で生成した XML ファイルをインポートする (Ansible)
Excelヒアリングシート
図のように入力項目に記入すると、別シートにある情報から VLOOKUP 関数を使用して、自動入力項目にTSV形式に取り込みたい情報が入力されるようにしました。
Playbook の構成
以下のディレクトリ・ファイル構成にします。
zbx_tsv2xml.yml ............ Playbook 本体
_make_template.yml ......... インポート用 XML ファイルを生成する Playbook
_import_template.yml ....... Zabbix に XML ファイルをインポートする Playbook
input/ ..................... 入力情報格納用ディレクトリ
items_<Template名>.tsv ... テンプレート毎のアイテム情報
tmpl_list.tsv ............ インポートしたいテンプレート一覧
output/ .................... インポート用 XML ファイル格納用ディレクトリ
templates/ ................. インポート用 XML ファイルのテンプレート格納用ディレクトリ
tmpl.xml.j2 .............. インポート用 XML ファイルのテンプレート
input/items_《Template名》.tsv
テンプレート毎のアイテム情報を定義します。1行1アイテムを定義し、そのアイテムに紐づくトリガーも定義できるようになっています。
name type value_type value_map key applications trigger_name expression priority
TCP Port [$3] (Internal) ZABBIX_PASSIVE UNSIGNED Service state net.tcp.service[tcp,127.0.0.1,22] HealthCheck {$DESCRIPTION} Port 22(sshd) down (1m) {count(#1,0,"eq")}=1 WARNING
TCP Port [$3] (Internal) ZABBIX_PASSIVE UNSIGNED Service state net.tcp.service[tcp,127.0.0.1,22] HealthCheck {$DESCRIPTION} Port 22(sshd) down (3m) {count(#3,0,"eq")}=3 HIGH
input/tmpl_list.tsv
インポートしたいテンプレート一覧を定義します。
template_name groups description applications
Template-IntPort-22-sshd TemplateG-Custom TCP内部ポート監視 HealthCheck
zbx_tsv2xml.yml
Playbook 本体です。 tmpl_list.tsv
を読み込み、 _make_template.yml
で1行ずつループしながらアイテムの情報を読み込み、テンプレートを出力します。その後、 _import_template.yml
で出力したテンプレートを Zabbix にインポートします。
- hosts: localhost
connection: local
gather_facts: false
tasks:
- name: read tmpl list file
read_csv:
path: input/tmpl_list.tsv
delimiter: "\t"
register: tmpl_list
delegate_to: localhost
- name: make template
include: _make_template.yml
with_items: "{{ tmpl_list.list }}"
- name: Import Zabbix templates from XML
include: _import_template.yml
with_items: "{{ tmpl_list.list }}"
_make_template.yml
テンプレートを出力します。ファイル名はテンプレート名から生成できるようにしました。(半角空白をアンダーラインに置換しています)
---
- name: read CSV file
read_csv:
path: input/items_{{ item.template_name | regex_replace(' ','_') }}.tsv
delimiter: "\t"
register: items
delegate_to: localhost
- name: export template file
template:
src: templates/tmpl.xml.j2
dest: "output/{{ item.template_name | regex_replace(' ','_') }}.xml"
mode: 0644
_import_template.yml
出力したテンプレートを Zabbix にインポートします。
今回は PoC なので、Zabbix URL や認証情報を埋め込みにしていますが、実際に運用する場合は、 vars で定義するとよいでしょう。
---
- name: determine Zabbix template file
set_fact:
import_file: "output/{{ item.template_name | regex_replace(' ','_') }}.xml"
- name: set import xml
set_fact:
import_xml: "{{ lookup('file', import_file) }}"
- name: "Import Zabbix templates from XML ({{ import_file }})"
local_action:
module: zabbix_template
server_url: http://192.168.33.11/zabbix
login_user: Admin
login_password: zabbix
template_xml: "{{ import_xml }}"
state: present
templates/tmpl.xml.j2
テンプレートの書式は、Zabbix5.0 のテンプレートを覗いてみた の記事を参照してください。
<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export>
<version>5.0</version>
<date>{{ now(False,"%Y-%m-%dT%H:%M:%SZ") }}</date>
<groups>
{% for group in item.groups.split(',') %}
<group>
<name>{{ group }}</name>
</group>
{% endfor %}
</groups>
<templates>
<template>
<template>{{ item.template_name }}</template>
<name>{{ item.template_name }}</name>
<description/>
<groups>
{% for group in item.groups.split(',') %}
<group>
<name>{{ group }}</name>
</group>
{% endfor %}
</groups>
<applications>
{% for application in item.applications.split(',') %}
<application>
<name>{{ application }}</name>
</application>
{% endfor %}
</applications>
<items>
{% for it in items.list %}
<item>
<name>{{ it.name }}</name>
<key>{{ it.key }}</key>
<delay>60</delay>
{% if it.value_type == 'LOG' %}
<history>60d</history>
<trends>0</trends>
{% else %}
<history>30d</history>
<trends>365d</trends>
{% endif %}
<value_type>{{ it.value_type }}</value_type>
<applications>
{% for application in it.applications.split(',') %}
<application>
<name>{{ application }}</name>
</application>
{% endfor %}
</applications>
{% if it.value_map != "0" %}
<valuemap>
<name>{{ it.value_map }}</name>
</valuemap>
{% endif %}
{% if it.expression is defined %}
<triggers>
<trigger>
<expression>{{ it.expression | e }}</expression>
<name>{{ it.trigger_name }}</name>
<priority>{{ it.priority }}</priority>
</trigger>
</triggers>
{% endif %}
</item>
{% endfor %}
</items>
<macros/>
<templates/>
<screens/>
</template>
</templates>
<value_maps>
<value_map>
<name>Service state</name>
<mappings>
<mapping>
<value>0</value>
<newvalue>Down</newvalue>
</mapping>
<mapping>
<value>1</value>
<newvalue>Up</newvalue>
</mapping>
</mappings>
</value_map>
</value_maps>
</zabbix_export>
実行結果
それでは、実行してみます。
(ansible-py3) [vagrant@ansible zbx_tsv2xml]$ ansible-playbook zbx_tsv2xml.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] ************************************************************************************************************************
TASK [read tmpl list file] **************************************************************************************************************
ok: [localhost]
TASK [make template] ********************************************************************************************************************
included: /home/vagrant/zbx_tsv2xml/_make_template.yml for localhost => (item={'template_name': 'Template-IntPort-22-sshd', 'groups': 'TemplateG-RegaliaPtl', 'description': 'TCP内部ポート監視', 'applications': 'HealthCheck'})
TASK [read CSV file] ********************************************************************************************************************
ok: [localhost]
TASK [export template file] *************************************************************************************************************
changed: [localhost]
TASK [Import Zabbix templates from XML] *************************************************************************************************
included: /home/vagrant/zbx_tsv2xml/_import_template.yml for localhost => (item={'template_name': 'Template-IntPort-22-sshd', 'groups': 'TemplateG-Custom', 'description': 'TCP内部ポート監視', 'applications': 'HealthCheck'})
TASK [determine Zabbix template file] ***************************************************************************************************
ok: [localhost]
TASK [set import xml] *******************************************************************************************************************
ok: [localhost]
TASK [Import Zabbix templates from XML (output/Template-IntPort-22-sshd.xml)] ***********************************************************
changed: [localhost]
PLAY RECAP ******************************************************************************************************************************
localhost : ok=9 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
無事に Template-IntPort-22-sshd
というテンプレートがインポートされました。
(おまけ) 冪等性
ちなみに、上記の playbook は、何度実行してもインポートする処理が必ず changed になってしまいます。
先ほどの zabbix-template.py を読むと、冪等性を担保するために設定前後の比較をする処理がありました。
if template_content is not None and template_type == 'json':
parsed_template_json = self.load_json_template(template_content)
if self.diff_template(parsed_template_json, existing_template):
changed = True
return changed
(中略)
def diff_template(self, template_json_a, template_json_b):
# Compare 2 zabbix templates and return True if they differ.
template_json_a = self.filter_template(template_json_a)
template_json_b = self.filter_template(template_json_b)
if self.ordered_json(template_json_a) == self.ordered_json(template_json_b):
return False
return True
JSON 形式の場合にのみ比較をするようになっています。
Zabbixの管理画面からエクスポートすると XML なので、XML形式でインポートすることにしましたが、 JSON 形式のほうが良かったのかもしれません。