BLOG
2018 年 02 月 21 日
Zabbix 3.2以降の新機能解説(Zabbix 4.0を見据えて) その11 - IPMI監視対象ホストとIPMI Pollerのキャッシュによるバインディング
こんにちは、MIRACLE ZBX サポートを担当している花島タケシです。 今回は、IPMI 監視対象ホストと IPMI Poller のキャッシュによるバインディングについて解説します。
IPMI 監視対象ホストと IPMI Poller のキャッシュによるバインディング
今回は、IPMI 監視対象ホストと IPMI Poller のキャッシュによるバインディングについて解説します。
IPMI マネージャーの導入
以前のバージョンでも、IPMI 監視のための複数の IPMI Poller プロセスを立ち上げることができました。
しかしながら、下記で報告されているようにこれによる弊害もありました。
https://support.zabbix.com/browse/ZBXNEXT-3386
3.2.10 のコードを見ると、IPMI Poller プロセスは、ipmi_con_s 構造体の *start_con() メンバー関数をコールして接続を行っていますが、*close_connection() メンバー関数をコールしておらずコネクションを閉じていません。(3.2.10 の src/zabbix_server/poller/checks_ipmi.c や OpenIPMI-devel パッケージで提供される /usr/include/OpenIPMI/ipmi_conn.h を参考としてください。)
つまり、上記で報告されているように、IPMP Poller プロセス数分のコネクションを監視対象も保持してしまいます。( これにはパフォーマンスとのトレードオフが考えられます。)
コネクションを閉じないことによる監視対象の負荷という問題を解消するために、コネクション情報を保持するキャッシュが導入され、このキャッシュ情報も含めた管理のために IPMI マネージャーも導入されました。
IPMI マネージャーと IPMI Poller の関係
IPMI マネージャーと IPMI Poller の関係をソースコードを追っていくことで解説していきます。
両者間の通信は UNIX ソケットを使用しており、Poller 側から登録依頼を行い、マネージャーが対象として登録するというところは以前にも紹介したので割愛します。
IPMI マネージャーに関するコードは、src/zabbix_server/ipmi/ipmi_manager.c に記述されています。
952 ZBX_THREAD_ENTRY(ipmi_manager_thread, args)
953 {
...
1001 for (;;)
1002 {
...
1020 scheduled_num += ipmi_manager_schedule_requests(&ipmi_manager, now, &nextcheck);
...
1031 ret = zbx_ipc_service_recv(&ipmi_service, timeout, &client, &message);
...
1046 if (NULL != message)
1047 {
1048 switch (message->code)
1049 {
...
1057 case ZBX_IPC_IPMI_VALUE_RESULT:
1058 ipmi_manager_process_value_result(&ipmi_manager, client, message, now);
1059 polled_num++;
1060 break;
1061 case ZBX_IPC_IPMI_SCRIPT_REQUEST:
1062 ipmi_manager_process_script_request(&ipmi_manager, client, message, now);
1063 break;
1064 case ZBX_IPC_IPMI_COMMAND_RESULT:
1065 ipmi_manager_process_command_result(&ipmi_manager, client, message, now);
1066 }
1067
1068 zbx_ipc_message_free(message);
1069 }
1070
1071 if (NULL != client)
1072 zbx_ipc_client_release(client);
1073
1074 if (now >= nextcleanup)
1075 {
1076 ipmi_manager_host_cleanup(&ipmi_manager, now);
1077 nextcleanup = now + ZBX_IPMI_MANAGER_CLEANUP_DELAY;
1078 }
1079 }
IPMI マネージャーは、デーモン処理のループが始まると、l.1020 の ipmi_manager_schedule_requests() をコールして、監視スケジュールの生成を行います。
849 static int ipmi_manager_schedule_requests(zbx_ipmi_manager_t *manager, int now, int *nextcheck)
850 {
...
856 num = DCconfig_get_ipmi_poller_items(now, items, MAX_POLLER_ITEMS, nextcheck);
857
858 for (i = 0; i < num; i++)
859 {
860 if (FAIL == zbx_ipmi_port_expand_macros(items[i].host.hostid, items[i].interface.port_orig,
861 &items[i].interface.port, &error))
862 {
...
872 }
873
874 request = ipmi_request_create(items[i].host.hostid);
875 request->itemid = items[i].itemid;
876 request->item_state = items[i].state;
877 ipmi_manager_serialize_request(&items[i], 0, &request->message);
878 ipmi_manager_schedule_request(manager, items[i].host.hostid, request, now);
879 }
880
881 zbx_preprocessor_flush();
882 DCconfig_clean_items(items, NULL, num);
883
884 return num;
885 }
l.856 の DCconfig_get_ipmi_poller_items() により、次回監視予定時刻が到来している監視アイテムを Configuration キャッシュから取得します。
当然、IPMI タイプの監視アイテムのみとなります。
従来は、マネージャーが存在していなかったため、各 IPMI Poller プロセスが監視対象となるアイテムを取得するという処理を行っていました。
監視情報を取得するのはマネージャーだけとなっています。
その後、ll.858-879 のループ処理において、各々のアイテムに対しての処理を行います。
l.877 の ipmi_manager_serialize_request() は、マネージャー - Poller 間の通信の無駄を省くための処理で、流れを見ていくという点では重要ではありません。
ただし、メッセージに ZBX_IPC_IPMI_VALUE_REQUEST タグを付与しています。
l.878 の ipmi_manager_schedule_request() において、リクエストの処理を行います。
299 static void ipmi_poller_schedule_request(zbx_ipmi_poller_t *poller, zbx_ipmi_request_t *request)
300 {
301 if (NULL == poller->request && NULL != poller->client)
302 ipmi_poller_send_request(poller, request);
303 else
304 ipmi_poller_push_request(poller, request);
305 }
...
825 static void ipmi_manager_schedule_request(zbx_ipmi_manager_t *manager, zbx_uint64_t hostid,
826 zbx_ipmi_request_t *request, int now)
827 {
828 zbx_ipmi_manager_host_t *host;
829
830 host = ipmi_manager_cache_host(manager, hostid, now);
831 ipmi_poller_schedule_request(host->poller, request);
832 }
l.830 の ipmi_manager_cache_host() が、キャッシュから hostid に紐づいたホスト情報を取得する関数です。
host には、関連する Poller 情報が格納されており、l.831 にてこの Poller に対してリクエストの依頼を行います。
ipmi_poller_schedule_request() では、Poller がリクエストを処理していない状態ならば直接リクエストを送信し、そうでないならキューへ挿入します。
ここまでで、マネージャーから Poller へリクエストを依頼することが行えました。
Poller キューとホストキャッシュの関係
処理すべきリクエストは Poller の処理能力に関係なく、アイテムの更新間隔により生成されます。
Poller が何も処理しなくなったらリクエストを生成しプッシュするという方式でも問題ありませんが、これだと無駄な時間が発生していまいます。
そのため、Poller 毎にキューがあります。
本来の目的である「監視対象ホストに対するコネクションの接続」から、あるホストへの接続は特定の Poller からのみと限定します。
そのため、マネージャーがキャッシュするホスト情報にはバインドする Poller 情報を保持します。
マネージャーが管理する Poller 情報のキャッシングはあまり難しい処理はしていません。
l.830 の ipmi_manager_cache_host() において、マネージャーが管理しているホスト情報 ( リスト ) にホストがない ( キャッシュされていない ) 場合、リストに載せた後に ipmi_manager_get_host_poller() をコールします。
ipmi_manager_get_host_poller() では、ホストが紐付けされれている数が最も少ない Poller が選出されます。
これにより、ホストに紐づいた Poller 情報がキャッシュされます。
キャッシュ情報に載っているときは、その情報を返すだけです。
なお、キャッシュの有効期限は 24 時間となります。
IPMI Poller によるリクエストの処理
Poller プロセスに関する記述は、src/zabbix_server/ipmi/ipmi_poller.c にされています。
212 for (;;)
213 {
...
231 if (SUCCEED != zbx_ipc_socket_read(&ipmi_socket, &message))
232 {
233 zabbix_log(LOG_LEVEL_CRIT, "cannot read IPMI service request");
234 exit(EXIT_FAILURE);
235 }
...
251 switch (message.code)
252 {
253 case ZBX_IPC_IPMI_VALUE_REQUEST:
254 ipmi_poller_process_value_request(&ipmi_socket, &message);
255 polled_num++;
256 break;
257 case ZBX_IPC_IPMI_COMMAND_REQUEST:
258 ipmi_poller_process_command_request(&ipmi_socket, &message);
259 break;
260 case ZBX_IPC_IPMI_CLEANUP_REQUEST:
261 zbx_delete_inactive_ipmi_hosts(time(NULL));
262 break;
263 }
264
265 zbx_ipc_message_clean(&message);
266 }
Poller 側の処理はそれほど複雑ではありません。ソケットからマネージャーが発したメッセージを読み込み、それに応じた処理を行うだけです。
マネージャーからは、ZBX_IPC_IPMI_VALUE_REQUEST タグが付与されたリクエストを発したので、 ipmi_poller_process_value_request() を見ていきます。
92 static void ipmi_poller_process_value_request(zbx_ipc_socket_t *socket, zbx_ipc_message_t *message)
93 {
...
102 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
103
104 zbx_ipmi_deserialize_request(message->data, &itemid, &addr, &port, &authtype,
105 &privilege, &username, &password, &sensor, &command);
...
111 errcode = get_value_ipmi(itemid, addr, port, authtype, privilege, username, password, sensor, &value);
112 ipmi_poller_send_result(socket, ZBX_IPC_IPMI_VALUE_RESULT, errcode, value);
...
121 }
マネージャー側でシリアライズ ( パッケージ化 ) されたメッセージを zbx_ipmi_deserialize_request() で分解します。こちらもあまり重要ではありません。
その後、get_value_ipmi() により監視対象に対して監視を行います。この実装は以前のものと大差ありません。
最後に監視結果を ipmi_poller_send_result() にて ZBX_IPC_IPMI_VALUE_REQUEST タグを付与して、マネージャーに送ります。
監視結果の受け取り
再度マネージャー側の処理に戻ります。
マネージャーは、ipmi_manager_schedule_requests() をコールして Poller 群にリクエストを割り振った後に、l.1031 の zbx_ipc_service_recv() にてメッセージの読み取りを行います。
その後、ll.1046-1069 のブロックにて受け取ったメッセージに対する処理を行います。ZBX_IPC_IPMI_SCRIPT_REQUEST は、Poller 群からのメッセージではありません。
実は、IPMI マネージャーは Poller 群からのみメッセージを受け付けるわけではありません。
Escalator 等からのスクリプト実行メッセージも受け付けます。以前のバージョンから IPMI タイプのスクリプトを設定することができます。
そういった類のスクリプトに対する処理となります。
なお、実行は Poller 群にて行われ、その返信は ZBX_IPC_IPMI_COMMAND_RESULT タグが付与されます。ZBX_IPC_IPMI_COMMAND_RESULT タグの処理は、後述する ZBX_IPC_IPMI_VALUE_RESULT と大差ありません。
ZBX_IPC_IPMI_SCRIPT_REQUEST は、通常監視に対する監視結果の処理となります。
ipmi_manager_process_value_result() が対応する関数となります。
717 static void ipmi_manager_process_value_result(zbx_ipmi_manager_t *manager, zbx_ipc_client_t *client,
718 zbx_ipc_message_t *message, int now)
719 {
...
728 if (NULL == (poller = ipmi_manager_get_poller_by_client(manager, client)))
729 {
730 THIS_SHOULD_NEVER_HAPPEN;
731 return;
732 }
733 itemid = poller->request->itemid;
734
735 zbx_ipmi_deserialize_result(message->data, &ts, &errcode, &value);
736
737 /* update host availability */
...
755 /* add received data to history cache */
...
783 /* put back the item in configuration cache IPMI poller queue */
784 DCrequeue_items(&itemid, &state, &ts.sec, &errcode, 1);
785
786 ipmi_poller_free_request(poller);
787 ipmi_manager_process_poller_queue(manager, poller, now);
788 }
impi_manager_process_value_result() では初め (l.728) に、メッセージ発信元 ( メッセージ読み込み時に確定します ) に関する情報を取得します。
一つの Poller には同時に複数のリクエストを投げることができないため、リクエストに関するアイテム等が一意に確定します。
l.735 では Poller からのメッセージを分解しています。この時点で監視が成功したかがわかります。
ソースコードは端折りましたが、l.737 から監視結果によりホストのステータスを変更し、l.755 にてプロプロセッサーに監視データを渡します。
l.784 にて次回監視時刻を計算し Configuration キャッシュ内のキューの再配置を行います。
この辺りは通常の Poller 等と変わりません。
l.786 にて、リクエスト情報を消去します。
この時点で、メッセージを送ってきた Poller は処理を行っておらず、フリーな状態です。
したがって、l.787 の ipmi_manager_process_poller_queue() により、当該 Poller へのリクエストの割り当てを行います。
以上で、IMPI マネージャーと Poller 群の処理の説明を行いました。
これらと処理の概略図を示して終わりにします。
関連記事
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 1
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 2
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 3
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 4
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 5
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 6
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 7
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 8
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 9
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 10
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 11
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 12
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 13
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 14
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 15
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 16
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 17
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 18
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 19
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 20
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 21
注意事項
- 本ドキュメントの内容は、予告なしに変更される場合があります。
- 本ドキュメントは、限られた評価環境における検証結果をもとに作成しており、全ての環境での動作を保証するものではありません。
- 本ドキュメントの内容に基づき、導入、設定、運用を行なったことにより損害が生じた場合でも、当社はその損害についての責任を負いません。あくまでお客さまのご判断にてご使用ください。