双六工場日誌

平凡な日常を淡々と綴ります。

Zabbix に新規ホストの追加を行う CLI ツールを Perl で作ってみた。【2011/02/06修正】

2011/02/06追記
Zabbix 1.8.3のWebインターフェースのバグ*1回避のため、必ずipmi_privilegeに1を入れるように変更しました。

ちなみにこのバグは、1.8.4では治っていましたが、今度はtemplate.getの仕様変更のため、テンプレートを指定したホスト登録の際にこのスクリプトが動かないという悲しいことになっています。。。
1.8.4対応版は、時間があれば作りますが、あまりご期待なさらぬよう。

  • -

昨日は Zabbix 勉強会お疲れさまでした!

ミツバチワークスの諸富さん (@DECOLOG_TECH) は、すごく率直に DELOLOG での Zabbix の使い方や工夫をお話してくれて実運用の様子を生で知ることができ、ZABBIX-JP の鈴木崇文さん(@BlueSkyDetector) からは予定外の zabbix_sender プロトコルの解説まであったり、さらに寺島さん(@kodai74)からZabbix 2.0の開発状況の解説があったりと非常に充実した勉強会でした。ありがとうございました。

で、昨日の勉強会でも Zabbix 向けの CLI ツールが欲しいとの話が出ていたので、自分からも何か提供しようと思い立ち、昨日のブログPerl から Zabbix API を叩くサンプルスクリプトを拡張して、それを自分で応用して Zabbix に新規ホストの追加を行う CLI ツールを作ってみました。

サンプルスクリプトと同じく使用はご自由に。使ってみてくれたら、連絡をもらえるとうれしいです。


■使用例

perl create_zabbix_host.pl --hostname testhost --hostgroups "Linux Servers" --templates Template_Linux --ip 192.168.1.2 --dnsname test.local --agentport 10050

こんな感じで使います。オプションの設定項目は、Web インターフェースと見比べてもらえれば、おおよそはわかると思います。

■前提条件

  • Zabbix バージョン: 1.8.3 (Zabbix-JP パッケージ使用)
  • CentOS 5.5 上の Perl 5.8.8 で動作確認。
  • Perl 5.8.8 の標準モジュールに加えてJSONモジュールが必要(CPANから適宜インストールをしてください。)
  • Zabbix API が有効になっているアカウントが必要 (Admin もインストール時は API 有効ではないので注意!)

■コマンドラインオプション

  • "--hostname": このオプションのみ必須。Zabbix に登録するホスト名(≠DNS名)を指定。
  • "--zabbix_server": Zabbix サーバのIP。デフォルトでは "localhost" を使用。
  • "--user": Zabbix に API アクセスする際のユーザ名。デフォルトは "Admin"。GUI から API を有効化するのを忘れずに。
  • "--password": Zabbix に API アクセスする際のパスワード。デフォルトは "zabbix"。
  • "--useragent": Zabbix サーバにアクセスする際の UserAgent を指定します。デフォルトは "zabbi-tan" です。(キリッ
  • "--ip": 登録するホストのIPアドレス
  • "--dnsname": 登録するホストのDNS名。
  • "--agentport": 登録するホストの Zabbix Agent が LISTEN しているポート番号。デフォルトは"10050"。
  • "--useip": …Zabbix 本家の API サンプルに載っていたのでつけましたが、正直よくわかってないです。デフォルトは"1"。
  • "--hostgroups": 複数指定できます。"--hostgroups 〜" を繰り返し指定してください。
  • "--templates": これも複数指定できます。ただし、テンプレートの間で重なっている設定項目があると設定に失敗します。
  • "--method": デフォルトは "host.create"。これ以外のメソッドで使うことは想定していませんが、一応設定できます。
  • "--limit": API 経由での情報取得の際の上限。このスクリプトでは上限設定の必要はないので、デフォルトは "undef"。

できるだけ API 仕様に沿って作ったつもりです。Zabbix Proxy 構成の環境が手元にないので、Proxy 設定の分は未対応です。。。

■戻り値
成功時には登録されたホスト名が JSON 形式で返されます。

{
   "jsonrpc" : "2.0",
   "id" : 3,
   "result" : {
      "hostids" : [
         "10060"
      ]
   }
}

失敗した時には、こんな感じのエラーメッセージが返ってきます。

{
   "jsonrpc" : "2.0",
   "error" : {
      "data" : "[ CHost::create ] Host [ testhost ] already exists",
      "message" : "Invalid params.",
      "code" : -32602
   },
   "id" : 3
}

■スクリプト本体(create_zabbix_host.pl)

#!/usr/bin/perl
# First Version 2010/12/19
# Last Modified: 2011/02/06
# Author: Seiichiro, Ishida <twitterID: @sechiro>

use strict;
use warnings;
use IO::Socket::INET;
use JSON;
use Getopt::Long;

# JSON OO Interface
my $json = JSON->new->allow_nonref;

# Command Line Options
my $zabbix_server = "localhost";
my $user = "Admin"; # Zabbix default
my $password = "zabbix"; # Zabbix default
my $useragent = "zabbi-tan"; # できる子
my $method = "host.create";
my $hostname;
my $ip;
my $dnsname;
my $agentport = 10050;
my @hostgroups = ();
my @templates = ();
my $useip = 1;
my $ipmi_privilege = 1; # バグ回避のため追加。
my $limit; # 取得アイテム数の上限。サブルーチンの互換性向けに定義。

my $opt_parse = GetOptions (
    "zabbix_server=s" => \$zabbix_server,
    "user=s"        =>  \$user,
    "password=s"    =>  \$password,
    "useragent=s"   =>  \$useragent,
    "method=s"      =>  \$method,
    "agentport=i"   =>  \$agentport,
    "hostname=s"    =>  \$hostname,
    "ip=s"          =>  \$ip,
    "dnsname=s"     =>  \$dnsname,
    "hostgroups=s"  =>  \@hostgroups,
    "templates=s"   =>  \@templates,
    "useip=i"       =>  \$useip,
    "limit=i"       =>  \$limit,
    "ipmi_privilege=i" => \$ipmi_privilege,
);

die "Hostname is needed!" unless (defined($hostname));

# Authentication
my $json_auth = $json->encode( create_auth_request_hash($user, $password) );
my $rpc_auth_request = create_rpc_request($zabbix_server, $useragent, $json_auth);
my $json_auth_result = $json->decode( get_zabbix_data($zabbix_server, $rpc_auth_request) );
my $auth = $json_auth_result->{result};
my $id = $json_auth_result->{id};
$id++;

# Get hostgroups id
my @hostgroup_ids = ();
foreach my $hostgroup (@hostgroups) {
    my %hostgroup_filter = (
        name    =>  $hostgroup,
    );
    my $json_data = $json->encode( create_get_request_hash($auth, $id, "hostgroup.get", \%hostgroup_filter, $limit) );
    my $rpc_request = create_rpc_request($zabbix_server, $useragent, $json_data);
    my $json_result = $json->decode( get_zabbix_data($zabbix_server, $rpc_request) );
    my $result_array = $json_result->{result};
    foreach my $result ( @$result_array ) { # $json_result->{result}[n]->{groupid}
        push (@hostgroup_ids, $result->{groupid});
    }
    $id++;
}

# Get templates id
my @template_ids = ();
foreach my $template (@templates) {
    my %template_filter = (
        host    =>  $template,
    );
    my $json_data = $json->encode( create_get_request_hash($auth, $id, "template.get", \%template_filter, $limit) );
    my $rpc_request = create_rpc_request($zabbix_server, $useragent, $json_data);
    my $json_result = $json->decode( get_zabbix_data($zabbix_server, $rpc_request) );
    my $result_hash = $json_result->{result};
    foreach my $key ( keys %$result_hash ) { # keyの値はtemplateidと同じ。
        push (@template_ids, $result_hash->{$key}->{templateid});
    }
    $id++;
}

# Create Host
my $json_data = $json->encode( host_create_request_hash($auth, $id, $method, $hostname, $ip, $dnsname, $agentport, \@hostgroup_ids, \@template_ids, $useip, $ipmi_privilege) );
my $rpc_request = create_rpc_request($zabbix_server, $useragent, $json_data);
my $json_result = $json->decode( get_zabbix_data($zabbix_server, $rpc_request) );
$id++;

# output
my $pretty_printed = $json->pretty->encode( $json_result );
print $pretty_printed;

exit;


sub create_auth_request_hash{
    my $user = shift;
    my $password = shift;
    
    my %params = (
        user        =>  $user,
        password    =>  $password,
    );
    my %auth_request = (
        auth        =>  undef,
        method      =>  'user.authenticate',
        id          =>  0,
        params      =>  \%params,
        jsonrpc     =>  '2.0',
    );
    return \%auth_request;
}


sub create_get_request_hash{ #for host.get etc.
    my $auth = shift;
    my $id = shift;
    my $method = shift;
    my $filter_hash = shift;
    my $limit = shift;
    
    my %params = (
        output      =>  'extend',
        limit       =>  $limit,
        filter      =>  $filter_hash,
    );

    my %get_request = (
        auth        =>  $auth,
        method      =>  $method,
        id          =>  $id,
        params      =>  \%params,
        jsonrpc     =>  '2.0',
    );
    return \%get_request;
}


sub host_create_request_hash{ #for host.create
    my $auth = shift;
    my $id = shift;
    my $method = shift;
    my $hostname = shift;
    my $ip = shift;
    my $dnsname = shift;
    my $agentport = shift;
    my $hostgroup_ids = shift;
    my $template_ids = shift;
    my $useip = shift;
    my $ipmi_privilege = shift;
    
    my %params = (
        host        =>  $hostname,
        ip          =>  $ip,
        dns         =>  $dnsname,
        port        =>  $agentport,
        groups      =>  $hostgroup_ids, 
        templates   =>  $template_ids, 
        useip       =>  $useip,
        ipmi_privilege => $ipmi_privilege,
    );

    my %request = (
        auth        =>  $auth,
        method      =>  $method,
        id          =>  $id,
        params      =>  \%params,
        jsonrpc     =>  '2.0',
    );
    return \%request;
}

sub create_rpc_request{
    my $zabbix_server = shift;
    my $useragent = shift;
    my $json_data = shift;
    my $header = "POST /zabbix/api_jsonrpc.php HTTP/1.1\n"
        . "Accept: */*\n"
        . "Connection: close\n"
        . "Content-Type: application/json-rpc\n";
    
    $header .= "User-Agent: " . $useragent ."\n";
    $header .= "Content-Length: " . length($json_data) . "\n";
    $header .= "Host: " . $zabbix_server . "\n\n";

    return $header . $json_data;
}

sub get_zabbix_data{
    use IO::Socket::INET;
    my $zabbix_server = shift;
    my $rpc_request = shift;
    
    my $sock = IO::Socket::INET->new(PeerAddr => "$zabbix_server",
                                     PeerPort => 'http(80)',
                                     Proto    => 'tcp',
        ) or die "Cannot create socket: $!";

    $sock->print("$rpc_request");
    
    # データの読み込み
    my $json_result;
    while( my $line = $sock->getline ){
        if ( $line =~ /jsonrpc/ ){
            $json_result = $line;
        }
    }
    return $json_result;
}


__END__

*1:ipmiを使用するにチェックが入っていなくても、ipmi特権レベルの値を見に行ってしまうバグ。