本日も乙

ただの自己満足な備忘録。

Icinga Web REST API を使ってみた

Icingaという監視ツールがあります。 IcingaとはNagiosからForkした監視ツールで、Icinga 1はNagiosと互換性があるので、設定ファイルやプラグインなどNagiosで使っていたものがそのまま使えます。

Icinga 1とNagiosとの大きな違いの1つに、Icinga Web REST APIがあります。 これは、Icingaでの操作(監視の開始・停止)や条件に合った監視ホスト・サービスを取得するAPIが提供されています。 つまり、Icingaサーバ以外のサーバからIcingaの操作や情報の取得が可能になるものです。

操作を自動化したいと思っていたので、「これだ!」と思って調べていたのですが、あまり情報がなかったので数少ないドキュメントと数々の試行錯誤の結果得た知見を紹介します。

Icingaのインストール方法はQiitaに載せているのでこちらを参照してください。 Icinga Web REST APIを使うためには、Icinga のバージョンが1.5以上であることが必須です。

Nagios - Icinga 1をインストール - Qiita

事前準備

auth_enabledtrueに変更します。

# /usr/share/icinga-web/app/modules/AppKit/config/auth.xml
<ae:parameter name="auth_key">
    <ae:parameter name="auth_module">AppKit</ae:parameter>
    <ae:parameter name="auth_provider">Auth.Provider.AuthKey</ae:parameter>
    <ae:parameter name="auth_enable">true</ae:parameter>
    <ae:parameter name="auth_authoritative">true</ae:parameter>
</ae:parameter>

キャッシュクリア

$ sudo rm -f /usr/share/icinga-web/app/config/*.php

APIキー発行

API操作するためにAPIキーを発行します。

  1. Icinga-Webにログイン後、Admin -> Users をクリック
  2. 「Add new user」をクリックして、APIアクセス用のユーザを作成
  3. 「Auth via」で「auth_key」を選択
  4. 「Authkey for Api (optional)」 の更新ボタンをクリックしてAPIキーを発行する
  5. Rightタブ -> Credentialsタブ を選択し、appkit.api.accessicinga.userにチェックを入れる
  6. 「Create user」をクリック

テスト

以下のAPIが使えるかテストします。

CriticalかWarningの状態であるサービスすべてを取得するAPI

# XML
$ curl -s --globoff "http://icinga.example.com/icinga-web/web/api/service/filter[AND(HOST_CURRENT_STATE|=|0;OR(SERVICE_CURRENT_STATE|=|1;SERVICE_CURRENT_STATE|=|2))]/columns[SERVICE_NAME|HOST_NAME|SERVICE_CURRENT_STATE|HOST_NAME|HOST_CURRENT_STATE|HOSTGROUP_NAME]/order(SERVICE_CURRENT_STATE;DESC)/countColumn=SERVICE_ID/authkey=<APIキー>/xml"
<?xml version="1.0" encoding="UTF-8"?>
<results><result><column name="SERVICE_NAME">HTTPD</column><column name="HOST_NAME">host1</column><column name="SERVICE_CURRENT_STATE">1</column><column name="HOST_CURRENT_STATE">0</column><column name="HOST_IS_PENDING">0</column><column name="SERVICE_IS_PENDING">0</column>...</result><total>1</total></results>
# JSON (jqで整形)
curl -s --globoff "http://icinga.example.com/icinga-web/web/api/service/filter[AND(HOST_CURRENT_STATE|=|0;OR(SERVICE_CURRENT_STATE|=|1;SERVICE_CURRENT_STATE|=|2))]/columns[SERVICE_NAME|HOST_NAME|SERVICE_CURRENT_STATE|HOST_NAME|HOST_CURRENT_STATE|HOSTGROUP_NAME]/order(SERVICE_CURRENT_STATE;DESC)/countColumn=SERVICE_ID/authkey=<APIキー>/json" | jq .
{
  "total": 1,
  "success": "true",
  "result": [
    {
      "SERVICE_IS_PENDING": "0",
      "HOST_IS_PENDING": "0",
      "HOST_CURRENT_STATE": "0",
      "SERVICE_CURRENT_STATE": "1",
      "HOST_NAME": "host1",
      "SERVICE_NAME": "HTTPD"
    },
    ...
  ]
}

通知がオフになっているサービスすべてを取得するAPI

curl -s --globoff "http://icinga.example.com/icinga-web/web/api/service/filter[OR(SERVICE_NOTIFICATIONS_ENABLED|=|0;HOST_NOTIFICATIONS_ENABLED|=|0)]/columns[HOST_NAME|SERVICE_NAME]/order(HOST_NAME;asc)/countColumn=SERVICE_ID/authkey=<APIキー>/json" | jq .
{
  "total": 135,
  "success": "true",
  "result": [
    {
      "SERVICE_NAME": "CPU Usage System",
      "HOST_NAME": "host1"
    },
    ...
  ]
}

URLの構成

ひな形 : http://icinga.example.com/icinga-web/web/api/TARGET/COLUMNS/FILTER/ORDER/GROUPING/LIMIT/COUNTFIELD/auth=AUTH_KEY/OUTPUT_TYPE

パラメータ 説明
TARGET service or host service
COLUMNS 取得したいカラム名 columns[COL1!COL2!COL3!...]
FILTER SQLで言うとwhere and(カラム!=!条件);or(カラム!=!条件;カラム=条件)・・・で検索、!like!でLIKE検索
ORDER SQLで言うとorder by order(カラム;ASC or DESC)
GROUPING SQLで言うとgroup by group(カラム)
LIMIT SQLで言うとlimit limit[START;END]
COUNTFIELD SQLで言うとcount(*)、カウントしたいカラム名(idなど) countColumn=カラム名
AUTH_KEY APIキー 先ほど発行したキー
OUTPUT 出力フォーマット json or xml

FILTERについて

  • (), and(), or()で1つのグループ
  • 演算子|=|のようにする
    • 上記例では!|に置き換えてください
  • ;がセパレータ

  • A=1 => (A|=|1)
  • A=1 and B=2 => and(A|=|1;B|=|2)
  • A=1 or B=2 => or(A|=|1;B|=|2)
  • A=1 and (B=2 or C=3) => and(A|=|1;or(B|=|2;C|=|3))

使用できるカラム名

/usr/share/icinga-web/app/modules/Cronks/lib/js/Icinga/Cronks/Tackle/Information/Head.jsに取得できるカラム一覧があるのでそちらをご参照ください。

ホスト

大体同じものやあまり使わなそうなものは説明を省略しています。

カラム名 説明
HOST_ID ホストID -
HOST_OBJECT_ID ホストオブジェクトID -
HOST_INSTANCE_ID ホストインスタンスID -
HOST_NAME ホスト名 host1
HOST_ALIAS ホストエイリアス -
HOST_DISPLAY_NAME ホストのディスプレイ表示名 -
HOST_ADDRESS ホストのアドレス名(IPアドレス) 192.168.192.5
HOST_ADDRESS6 ホストのアドレス名(IPアドレスv6?) -
HOST_ACTIVE_CHECKS_ENABLED ホストのアクティブチェックが有効になっているか 0:無効, 1:有効
HOST_CONFIG_TYPE
HOST_FLAP_DETECTION_ENABLED
HOST_PROCESS_PERFORMANCE_DATA
HOST_FRESHNESS_CHECKS_ENABLED
HOST_FRESHNESS_THRESHOLD
HOST_PASSIVE_CHECKS_ENABLED
HOST_EVENT_HANDLER_ENABLED
HOST_RETAIN_STATUS_INFORMATION
HOST_RETAIN_NONSTATUS_INFORMATION
HOST_NOTIFICATIONS_ENABLED 通知が有効になっているか 0:無効, 1:有効
HOST_OBSESS_OVER_HOST
HOST_FAILURE_PREDICTION_ENABLED
HOST_NOTES
HOST_NOTES_URL
HOST_ACTION_URL
HOST_ICON_IMAGE
HOST_ICON_IMAGE_ALT
HOST_IS_ACTIVE
HOST_OUTPUT
HOST_LONG_OUTPUT
HOST_PERFDATA
HOST_CURRENT_STATE ホストの状態 0:UP, 1:DOWN, 2:UNREACHABLE
HOST_CURRENT_CHECK_ATTEMPT
HOST_MAX_CHECK_ATTEMPTS
HOST_LAST_CHECK 最後に監視チェックした時刻 2015-06-10 18:46:43
HOST_LAST_STATE_CHANGE 最後に状態が変わった時刻 2015-03-04 19:45:16
HOST_CHECK_TYPE
HOST_LATENCY
HOST_EXECUTION_TIME
HOST_NEXT_CHECK 次に監視チェックする時刻 2015-06-10 18:49:53
HOST_HAS_BEEN_CHECKED
HOST_LAST_HARD_STATE_CHANGE
HOST_LAST_NOTIFICATION
HOST_PROCESS_PERFORMANCE_DATA
HOST_STATE_TYPE 
HOST_IS_FLAPPING
HOST_PROBLEM_HAS_BEEN_ACKNOWLEDGED
HOST_SCHEDULED_DOWNTIME_DEPTH
HOST_SHOULD_BE_SCHEDULED
HOST_STATUS_UPDATE_TIME
HOST_CHECK_SOURCE

サービス

カラム名 説明
SERVICE_ID サービスID
SERVICE_INSTANCE_ID サービスインスタンスID
SERVICE_CONFIG_TYPE
SERVICE_IS_ACTIVE
SERVICE_OBJECT_ID サービスオブジェクトID
SERVICE_NAME サービス名
SERVICE_DISPLAY_NAME サービスのディスプレイ表示名
SERVICE_NOTIFICATIONS_ENABLED 通知が有効になっているか 0:無効, 1:有効
SERVICE_FLAP_DETECTION_ENABLED
SERVICE_PASSIVE_CHECKS_ENABLED
SERVICE_EVENT_HANDLER_ENABLED
SERVICE_ACTIVE_CHECKS_ENABLED
SERVICE_RETAIN_STATUS_INFORMATION
SERVICE_RETAIN_NONSTATUS_INFORMATION
SERVICE_OBSESS_OVER_SERVICE
SERVICE_FAILURE_PREDICTION_ENABLED
SERVICE_NOTES
SERVICE_NOTES_URL
SERVICE_ACTION_URL
SERVICE_ICON_IMAGE
SERVICE_ICON_IMAGE_ALT
SERVICE_OUTPUT
SERVICE_LONG_OUTPUT
SERVICE_PERFDATA
SERVICE_PROCESS_PERFORMANCE_DATA
SERVICE_CURRENT_STATE サービスの状態 0:OK, 1:WARNING, 2:CRITICAL, 3:UNKNOWN
SERVICE_CURRENT_CHECK_ATTEMPT
SERVICE_MAX_CHECK_ATTEMPTS
SERVICE_LAST_CHECK
SERVICE_LAST_STATE_CHANGE
SERVICE_CHECK_TYPE
SERVICE_LATENCY
SERVICE_EXECUTION_TIME
SERVICE_NEXT_CHECK
SERVICE_HAS_BEEN_CHECKED
SERVICE_LAST_HARD_STATE
SERVICE_LAST_HARD_STATE_CHANGE
SERVICE_LAST_NOTIFICATION
SERVICE_STATE_TYPE
SERVICE_IS_FLAPPING
SERVICE_PROBLEM_HAS_BEEN_ACKNOWLEDGED
SERVICE_SCHEDULED_DOWNTIME_DEPTH
SERVICE_SHOULD_BE_SCHEDULED
SERVICE_STATUS_UPDATE_TIME
SERVICE_CHECK_SOURCE

POSTメソッドでAPIコール

POSTリクエストでもGETリクエストと同じことができます。 POSTと聞いてデータを更新できるかと思ったら違って残念な気持ちになりました。

curl \
-d target=service \
-d 'filters_json={"type":"AND","field":[{"type":"atom","field":["HOST_CURRENT_STATE"],"method":["="],"value":[0]},{"type":"OR","field":[{"type":"atom","field":["SERVICE_CURRENT_STATE"],"method":["="],"value":[1]},{"type":"atom","field":["SERVICE_CURRENT_STATE"],"method":["="],"value" : [2] }]}]}' \
-d columns[0]=SERVICE_NAME \
-d columns[1]=HOST_NAME \
-d columns[2]=SERVICE_CURRENT_STATE \
-d columns[3]=HOST_NAME \
-d columns[4]=HOST_CURRENT_STATE \
-d columns[5]=HOSTGROUP_NAME \
-d 'order=SERVICE_CURRENT_STATE;DESC' \
-d countColumn=SERVICE_ID \
-d 'authkey=<APIキー>' \
http://icinga.example.com/icinga-web/web/api/xml

fileter_jsonを整形するとこのようになっています。

{
    "field": [
        {
            "field": [
                "HOST_CURRENT_STATE"
            ], 
            "method": [
                "="
            ], 
            "type": "atom", 
            "value": [
                0
            ]
        }, 
        {
            "field": [
                {
                    "field": [
                        "SERVICE_CURRENT_STATE"
                    ], 
                    "method": [
                        "="
                    ], 
                    "type": "atom", 
                    "value": [
                        1
                    ]
                }, 
                {
                    "field": [
                        "SERVICE_CURRENT_STATE"
                    ], 
                    "method": [
                        "="
                    ], 
                    "type": "atom", 
                    "value": [
                        2
                    ]
                }
            ], 
            "type": "OR"
        }
    ], 
    "type": "AND"
}

APIからIcingaの外部コマンドを実行する

Nagios外部コマンドファイルが用意されているようにIcingaも同じ外部コマンドファイルが用意されています。
なんと、その外部コマンドをAPIで操作できるのです。 これで外部サーバから通知のオン/オフなどの操作ができるようになります。

URL構成

http://icinga.example.com/icinga-web/web/api/cmd/cmd=COMMAND_NAME/authkey=AUTH_KEY/target=TARGET/data=DATA

パラメータ 説明
COMMAND_NAME コマンド名 DISABLE_HOST_NOTIFICATIONS: 通知をオフにする, ENABLE_HOST_NOTIFICATIONS: 通知をオンにする
AUTH_KEY APIキー 先ほど発行したキー
TARGET コマンド実行の対象 JSON形式でURLエンコードする
DATA コマンド実行のデータ JSON形式でURLエンコードする

  • 監視対象サーバ(host1)の通知をオフにする
# ホスト
$ curl  http://icinga.example.com/icinga-web/web/api/cmd/cmd=DISABLE_HOST_NOTIFICATIONS/authkey=<APIキー>/target=%5B%7B%22instance%22%3A%22default%22%2C%22host%22%3A%22host1%22%7D%5D/data=%7B%22host%22%3A%22host1%22%7D
{"success":true}

# サービス
curl http://icinga.example.com/icinga-web/web/api/cmd/cmd=DISABLE_HOST_SVC_NOTIFICATIONS/authkey=<APIキー>/target=%5B%7B%22instance%22%3A%22default%22%2C%22host%22%3A%22host1%22%7D%5D/data=%7B%22host%22%3A%22host1%22%7D
{"success":true}

targetパラメータはURLエンコードされており、デコードするとこのようになります。

[{"instance":"default","host":"host1"}]

dataパラメータはこのようになっています。

{"host":"host1"}

APIリクエストによる詳細ログを見ることができます。

# /var/log/icinga-web/debug.log

[Thu Jun 11 16:18:30 2015] [debug] Write session update: hf349klcer3llm5k4du413uar7 (AppKitDoctrineSessionStorage::sessionWrite(), line 143)
[Thu Jun 11 16:18:30 2015] [debug] Auth.Dispatch: Starting authenticate (username=user1)
[Thu Jun 11 16:18:30 2015] [info] Auth.Dispatch: Converting username to lowercase
[Thu Jun 11 16:18:30 2015] [debug] Auth.Dispatch: Userdata found in db (uid=2)
[Thu Jun 11 16:18:30 2015] [debug] Auth.Provider: Object (name=auth_key) initialized
[Thu Jun 11 16:18:30 2015] [debug] Auth.Dispatch: Authoritative provider found (provider=auth_key, authid=api test)
[Thu Jun 11 16:18:30 2015] [debug] Auth.Dispatch: Successful authentication (provder=auth_key)
[Thu Jun 11 16:18:30 2015] [info] User user1 (test, api) logged in! (ip=::1)
[Thu Jun 11 16:18:30 2015] [info] Array
(
    [0] => Array
        (
            [instance] => default
            [host] => host1
        )

)

[Thu Jun 11 16:18:30 2015] [info] Array
(
    [0] => Array
        (
            [instance] => default
            [host] => host1
        )

)

[Thu Jun 11 16:18:30 2015] [debug] Trying to send commands, targets: [{"instance":"default","host":"host1"}] , data: {"host":"host1"}  (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:18:30 2015] [debug] Setting up console for instance default  (Cronks_System_CommandSenderModel::getConsoleInstance(), line 79)
[Thu Jun 11 16:18:30 2015] [debug] Submitting command DISABLE_HOST_NOTIFICATIONS to {"instance":"default","host":"host1"} (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:18:30 2015] [debug] Sending icinga-command [1434007110] DISABLE_HOST_NOTIFICATIONS;host1 (Api_Commands_CommandDispatcherModel::submitCommand(), line 83)
[Thu Jun 11 16:18:30 2015] [debug] ["Finished submitting command"] (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:18:30 2015] [info] User api test (test, api) logged out! (ip=::1)

[Thu Jun 11 16:41:13 2015] [debug] Write session update: user2 (AppKitDoctrineSessionStorage::sessionWrite(), line 143)
[Thu Jun 11 16:41:13 2015] [debug] Auth.Dispatch: Starting authenticate (username=user1)
[Thu Jun 11 16:41:13 2015] [info] Auth.Dispatch: Converting username to lowercase
[Thu Jun 11 16:41:13 2015] [debug] Auth.Dispatch: Userdata found in db (uid=2)
[Thu Jun 11 16:41:13 2015] [debug] Auth.Provider: Object (name=auth_key) initialized
[Thu Jun 11 16:41:13 2015] [debug] Auth.Dispatch: Authoritative provider found (provider=auth_key, authid=api test)
[Thu Jun 11 16:41:13 2015] [debug] Auth.Dispatch: Successful authentication (provder=auth_key)
[Thu Jun 11 16:41:13 2015] [info] User user1 (test, api) logged in! (ip=::1)
[Thu Jun 11 16:41:13 2015] [info] Array
(
    [0] => Array
        (
            [instance] => default
            [host] => host1
        )

)

[Thu Jun 11 16:41:13 2015] [info] Array
(
    [0] => Array
        (
            [instance] => default
            [host] => host1
        )

)

[Thu Jun 11 16:41:13 2015] [debug] Trying to send commands, targets: [{"instance":"default","host":"host1"}] , data: {"host":"host1"}  (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:41:13 2015] [debug] Setting up console for instance default  (Cronks_System_CommandSenderModel::getConsoleInstance(), line 79)
[Thu Jun 11 16:41:13 2015] [debug] Submitting command DISABLE_HOST_SVC_NOTIFICATIONS to {"instance":"default","host":"host1"} (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:41:13 2015] [debug] Sending icinga-command [1434008473] DISABLE_HOST_SVC_NOTIFICATIONS;host1 (Api_Commands_CommandDispatcherModel::submitCommand(), line 83)
[Thu Jun 11 16:41:13 2015] [debug] ["Finished submitting command"] (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:41:13 2015] [info] User api test (test, api) logged out! (ip=::1)

ソース改変

連続でAPIを叩くとたまにエラーになる場合があるので、ソースを改変する。

# /usr/share/icinga-web/app/modules/Api/models/Commands/CommandDispatcherModel.class.php

 77             AppKitLogger::debug("Sending icinga-command %s",$string);
 78             $cmd = $this->getContext()->getModel($commandClass[0],$commandClass[1],
 79                                                  array(
 80                                                          //"command" => "printf",
 81                                                          "command" => "echo",   // <-こちらに修正
 82                                                          "arguments" => array($string)
 83                                                  )
 84                                                 );

何をしているかというと、外部コマンドファイル(/var/spool/icinga/cmd/icinga.cmd)にパイプで命令を渡すコマンドを修正しています。
printfからechoに変更することで、意図的に改行を挟むようにし、連続して実行してもエラーにならなくなりました。

# 修正前
printf '[1434600538] ENABLE_HOST_NOTIFICATIONS;host1'  > /var/spool/icinga/cmd/icinga.cmd

# 修正後
echo '[1434600538] ENABLE_HOST_NOTIFICATIONS;host1'  > /var/spool/icinga/cmd/icinga.cmd

参考URL