search
LoginSignup
6

More than 1 year has passed since last update.

posted at

updated at

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

はじめに

監視設計は専任ではなく、開発に関わる人は誰でも行えることが望ましいですが、監視ツールのひとつである 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 形式のほうが良かったのかもしれません。

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
What you can do with signing up
6