LoginSignup
3
7

More than 3 years have passed since last update.

ExcelのヒアリングシートからAnsibleでZabbixに監視テンプレートを登録する

Last updated at Posted at 2020-12-09

はじめに

監視設計は専任ではなく、開発に関わる人は誰でも行えることが望ましいですが、監視ツールのひとつである 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 に投げているのか気になったので、ソースを読んでみました。

~/ansible-py3/lib/python3.6/site-packages/ansible_collections/community/zabbix/plugins/modules/zabbix_template.py
    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 テンプレートをインポートするまでの流れ

以下の流れで処理します。

  1. Excelヒアリングシートからコピペしてインポート用の情報をTSV形式で書く (手動)
  2. 1. で作成した TSV ファイルを読み、インポート用のXMLファイルを生成する (Ansible)
  3. 2. で生成した XML ファイルをインポートする (Ansible)

Excelヒアリングシート

図のように入力項目に記入すると、別シートにある情報から VLOOKUP 関数を使用して、自動入力項目にTSV形式に取り込みたい情報が入力されるようにしました。

excel1.png
↓ 上の表に入力すると、下の表が生成される
excel2.png

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アイテムを定義し、そのアイテムに紐づくトリガーも定義できるようになっています。

items_Template-IntPort-22-sshd.tsv
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 形式のほうが良かったのかもしれません。

3
7
2

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
3
7