読者です 読者をやめる 読者になる 読者になる

中年engineerの独り言 - crumbjp

LinuxとApacheの憂鬱

簡単SNMP(監視カスタマイズ編)

SNMPで誤解してた部分を勉強し直したついでに纏めてみた。

前回の続き

net-snmp の監視項目をカスタマイズする方法

主に3通りの方法がある。

/etc/snmp/snmpd.conf exec コマンド

net-snmpに任意のコマンドを登録する。

/etc/snmp/snmpd.conf
# exec コマンド名 コマンド [引数..]
exec cmd1 /bin/echo FOO
exec cmd2 /bin/echo BAR
exec cmd3 /bin/cat /etc/issue

net-snmp拡張 [enterprises]-[ucdavis]-[extTable]以下に結果が配置される。

$ snmpwalk -v 3  -lauthNoPriv -u myuser -a MD5 -A mypasswd 127.0.0.1 1.3.6.1.4.1.2021.8.1
UCD-SNMP-MIB::extIndex.1 = INTEGER: 1
UCD-SNMP-MIB::extIndex.2 = INTEGER: 2
UCD-SNMP-MIB::extIndex.3 = INTEGER: 3
UCD-SNMP-MIB::extNames.1 = STRING: cmd1
UCD-SNMP-MIB::extNames.2 = STRING: cmd2
UCD-SNMP-MIB::extNames.3 = STRING: cmd3
UCD-SNMP-MIB::extCommand.1 = STRING: /bin/echo
UCD-SNMP-MIB::extCommand.2 = STRING: /bin/echo
UCD-SNMP-MIB::extCommand.3 = STRING: /bin/cat
UCD-SNMP-MIB::extResult.1 = INTEGER: 0
UCD-SNMP-MIB::extResult.2 = INTEGER: 0
UCD-SNMP-MIB::extResult.3 = INTEGER: 0
UCD-SNMP-MIB::extOutput.1 = STRING: FOO
UCD-SNMP-MIB::extOutput.2 = STRING: BAR
UCD-SNMP-MIB::extOutput.3 = STRING: CentOS release 6.2 (Final)
UCD-SNMP-MIB::extErrFix.1 = INTEGER: noError(0)
UCD-SNMP-MIB::extErrFix.2 = INTEGER: noError(0)
UCD-SNMP-MIB::extErrFix.3 = INTEGER: noError(0)
UCD-SNMP-MIB::extErrFixCmd.1 = STRING:
UCD-SNMP-MIB::extErrFixCmd.2 = STRING:
UCD-SNMP-MIB::extErrFixCmd.3 = STRING:
/etc/snmp/snmpd.conf extend コマンド

基本的には上記execと変わらない。OIDが固定できるので重宝する。

/etc/snmp/snmpd.conf
extend ext1 /bin/cat /etc/hosts

この様に名前="ext1" がOIDとなるので参照し易い。

$ snmpwalk -v 3  -lauthNoPriv -u myuser -a MD5 -A mypasswd 127.0.0.1 1.3.6.1.4.1.8072.1.3.2
NET-SNMP-EXTEND-MIB::nsExtendNumEntries.0 = INTEGER: 1
NET-SNMP-EXTEND-MIB::nsExtendCommand."ext1" = STRING: /bin/cat
NET-SNMP-EXTEND-MIB::nsExtendArgs."ext1" = STRING: /etc/hosts
NET-SNMP-EXTEND-MIB::nsExtendInput."ext1" = STRING:
NET-SNMP-EXTEND-MIB::nsExtendCacheTime."ext1" = INTEGER: 5
NET-SNMP-EXTEND-MIB::nsExtendExecType."ext1" = INTEGER: exec(1)
NET-SNMP-EXTEND-MIB::nsExtendRunType."ext1" = INTEGER: run-on-read(1)
NET-SNMP-EXTEND-MIB::nsExtendStorage."ext1" = INTEGER: permanent(4)
NET-SNMP-EXTEND-MIB::nsExtendStatus."ext1" = INTEGER: active(1)
NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."ext1" = STRING: 127.0.0.1   localhost localhost.localdomain
NET-SNMP-EXTEND-MIB::nsExtendOutputFull."ext1" = STRING: 127.0.0.1   localhost localhost.localdomain
::1         localhost localhost.localdomain
NET-SNMP-EXTEND-MIB::nsExtendOutNumLines."ext1" = INTEGER: 2
NET-SNMP-EXTEND-MIB::nsExtendResult."ext1" = INTEGER: 0
NET-SNMP-EXTEND-MIB::nsExtendOutLine."ext1".1 = STRING: 127.0.0.1   localhost localhost.localdomain
NET-SNMP-EXTEND-MIB::nsExtendOutLine."ext1".2 = STRING: ::1         localhost localhost.localdomain
Sub agent

自前のモジュール(プログラム)を組み込む。

手法は大きく分けて、以下の3つ

静的リンク
net-snmp再ビルドする方法。
運用負担が大きすぎて使い難い。
動的リンク
snmpd.confにdlmodを指定し(??.so)とリンクする。
snmpdの再起動でプログラムの変更が反映できる
しかしバグったプログラムを組み込むとsnmpd諸共落ちるのでイマイチ。
プロセス間通信
Sub-Agentを別プロセスとして立て、ソケット通信する。
一番運用し易いと思われる。Sub-Agent自体が落ちた場合はその範囲のMIBが読めなくなるのでsub-agentプロセスの監視も楽。

Sub agent 作成

流れ
  1. MIBを作る
  2. mib2cを使ってスケルトンを生成
  3. コーディング
  4. net-snmp-configでコンパイル
  5. snmpd.confにagentXを設定
  6. Sub agent を起動
MIBを作る

/usr/share/snmp/mibs/MY-MIB.txt

MY-MIB DEFINITIONS ::= BEGIN

IMPORTS
    OBJECT-TYPE, MODULE-IDENTITY,Integer32, enterprises
        FROM SNMPv2-SMI;

mymib MODULE-IDENTITY
    LAST-UPDATED "1303120000Z"
    ORGANIZATION "Crumb"
    CONTACT-INFO ""
    DESCRIPTION  ""
    ::= { enterprises 9999 }

myobj OBJECT IDENTIFIER ::= { mymib 1 }

myint1 OBJECT-TYPE
    SYNTAX      Integer32 (0..2147483647)
    MAX-ACCESS  read-write
    STATUS      current
    DESCRIPTION
        "Custom Integer 1"
    ::= { myobj 1 }

mystring2 OBJECT-TYPE
    SYNTAX      OCTET STRING (SIZE(0..1024))
    MAX-ACCESS  read-write
    STATUS      current
    DESCRIPTION
        "Custom String 2"
    ::= { myobj 2 }
END
mib2c
net-snmpのヘッダをインストール
# yum install net-snmp-deval

mib2cというツールを使い、スケルトンを作ります。

mib2cインストール
# yum install net-snmp-perl
mib2c実行
$ mkdir /tmp/mymib
$ MIBS="+/usr/share/snmp/mibs/MY-MIB.txt" mib2c mymib
プロンプトでは
2) Net-SNMP style code
と
1) If you're writing code for some generic scalars
(by hand use: "mib2c -c mib2c.scalar.conf mymib")
を選択
コーディング
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "mymib.h"

long    myint1    = 0; // 追記:initial = 1
char *mystring2 = 0;   // 追記:initial = NULL

void
init_mymib(void)
{
    const oid myint1_oid[] = { 1,3,6,1,4,1,9999,1,1 };
    const oid mystring2_oid[] = { 1,3,6,1,4,1,9999,1,2 };

  DEBUGMSGTL(("mymib", "Initializing\n"));

    netsnmp_register_scalar(
        netsnmp_create_handler_registration("myint1", handle_myint1,
                               myint1_oid, OID_LENGTH(myint1_oid),
                               HANDLER_CAN_RWRITE
        ));
    netsnmp_register_scalar(
        netsnmp_create_handler_registration("mystring2", handle_mystring2,
                               mystring2_oid, OID_LENGTH(mystring2_oid),
                               HANDLER_CAN_RWRITE
        ));
    mystring2=(char*)malloc(1);
    mystring2[0]=0; // ""
}

int
handle_myint1(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info   *reqinfo,
                          netsnmp_request_info         *requests)
{
    int ret;

    switch(reqinfo->mode) {

        case MODE_GET:
            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
                                     (u_char *) &myint1, // myint1アドレスを指定
                                     sizeof(myint1) );   // myint1サイズを指定
            break;

        case MODE_SET_RESERVE1:
            ret = netsnmp_check_vb_type(requests->requestvb, ASN_INTEGER);
            if ( ret != SNMP_ERR_NOERROR ) {
                netsnmp_set_request_error(reqinfo, requests, ret );
            }
            break;

        case MODE_SET_RESERVE2:
            break;

        case MODE_SET_FREE:
            break;

        case MODE_SET_ACTION:
            break;

        case MODE_SET_COMMIT:
            myint1 = *requests->requestvb->val.integer; // 指定された値で更新
            break;

        case MODE_SET_UNDO:
            break;

        default:
            snmp_log(LOG_ERR, "unknown mode (%d) in handle_myint1\n", reqinfo->mode );
            return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}
int
handle_mystring2(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info   *reqinfo,
                          netsnmp_request_info         *requests)
{
    int ret;

    switch(reqinfo->mode) {

        case MODE_GET:
            snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
                                     (u_char *)mystring2,  // mystring2アドレスを指定
                                     strlen(mystring2+1)); // mystring2サイズを指定
            break;
        case MODE_SET_RESERVE1:
            ret = netsnmp_check_vb_type(requests->requestvb, ASN_OCTET_STR);
            if ( ret != SNMP_ERR_NOERROR ) {
                netsnmp_set_request_error(reqinfo, requests, ret );
            }
            break;

        case MODE_SET_RESERVE2:
            break;

        case MODE_SET_FREE:
            break;

        case MODE_SET_ACTION:
            break;

        case MODE_SET_COMMIT:
            free(mystring2);  // 現在の値をクリア
            mystring2 = strdup((char*)requests->requestvb->val.string);  // 指定された値で更新
            break;

        case MODE_SET_UNDO:
            break;

        default:
            snmp_log(LOG_ERR, "unknown mode (%d) in handle_mystring2\n", reqinfo->mode );
            return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}

本来はハンドラが呼ばれる順も気にしながら書くのだが、ココでは最小限の

  • MODE_GET
  • MODE_SET_COMMIT

しか扱わない。

GETで値を返して、SET_COMMITで指定された値を保存するだけ。

コンパイル
net-snmp-config --compile-subagent mymib.c

実行ファイル mymib が出来る。

agentX設定
/etc/snmp/snmpd.conf
次の行を指定。
master agentx

snmpd を再起動するとソケット(/var/agentx/master)が出来る。
このソケットがagentX sub agent のI/Fとなる。
/var/agentx のパーミッションでrootしかアクセスできないソケットになっている。

Sub agent 起動
# ./mymib
NET-SNMP version 5.5 AgentX subagent connected

試しにsnmpwalkしてみると

snmpwalk -v 3  -lauthNoPriv -u myuser -a MD5 -A mypasswd 127.0.0.1 1.3.6.1.4.1.9999
MY-MIB::myint1.0 = INTEGER: 0
MY-MIB::mystring2.0 = ""

ちゃんとコーディングで指定した初期値が取れました。

値更新

監視するだけならばsnmpgetされた時にsub agentで何かを監視して値を更新すれば良い。
しかしsnmpsetを利用するとある程度の運用をリモートで行う事もできる。

/etc/snmp/snmpd.conf
書き込み用ビューを設定
view    rwview    included   1.3.6.1.4.1.9999

snmpd再起動

値更新
sub agentの幾つかのハンドラが動き、エラーが無ければ最終的にSET_COMMITハンドラが動く。
例えば『INTEGER:99が指定された際にはapacheの再起動を行う。』ようなコードを書いておけば
リモートからのsnmpsetでapacheの再起動の運用が出来る。
$ snmpset -v 3  -lauthNoPriv -u myuser -a MD5 -A mypasswd 127.0.0.1 1.3.6.1.4.1.9999.1.1.0 i 1
MY-MIB::myint1.0 = INTEGER: 1
$ snmpset -v 3  -lauthNoPriv -u myuser -a MD5 -A mypasswd 127.0.0.1 1.3.6.1.4.1.9999.1.2.0 s aaaaaaa
MY-MIB::mystring2.0 = STRING: "aaaaaaa"

更新後の値を確認

$ snmpwalk -v 3  -lauthNoPriv -u myuser -a MD5 -A mypasswd 127.0.0.1 1.3.6.1.4.1.9999
MY-MIB::myint1.0 = INTEGER: 1
MY-MIB::mystring2.0 = STRING: "aaaaaa" 

まとめ

SNMPはMIBツリーの構造が複雑だが、逆に高度に構造化されているので、解ってしまえばシンプル。
ツリー化されてるので拡張性に優れており高度な自動化にはうってつけだろう。
最近は運用面はChef/Capistranoという選択肢もあるが、、監視と運用を一体化できるので、僕はSNMPの方が好みかな。
SNMPモジュールをデプロイする為にChef/Capistranoを使う?