MAGAZINE
ルーターマガジン
Ruby
クローラーの負荷監視に便利なSNMPの簡易Rubyラッパー
2019.04.24
こんにちは。エンジニアのAraoです。今回は、簡易的なSNMPのRubyラッパーを紹介します。随分と間が空いてしまいましたが、SNMPでサーバーのモニタリングの続きになります。
本記事で実現したいこと
前回記事で、Net-SNMPを用いて、同一ネットワーク内のLinuxサーバーのCPU使用率、ロードアベレージ、ストレージの使用率、メモリ使用率を監視する方法を紹介しました。
今回は、それらのサーバー監視を、Rubyスクリプト内で実行できるようにすることが目標になります。Rubyスクリプト内で監視を行うことにより、監視対象サーバーの状態を定期的にログファイルに吐き出し、閾値を超えていた場合はSlackやメールで異常を知らせる、などといったことが、お手軽にできるようになります。
Rubyスクリプト内でSNMPを使うためのクラスの実装
snmp_ruby_wrapper.rb
require 'open3' class SnmpError < StandardError end # snmpwalkコマンドの終了ステータスが0以外だった場合、 # 例外SnmpErrorをraiseする # エラーメッセージはsnmpwalkコマンドの標準エラー出力 # 例:NetSNMPのデフォルトタイムアウト10秒を超えてもレスポンスがない場合、 # Timeout: No Response from server_name (SnmpError) # がraiseされる class Snmp SNMPWALK_VALUE_ONLY = 'snmpwalk -Ovq -v 2c -c internal' # 値のみを取得するsnmpwalkコマンド SNMPWALK_QUICK_PRINT = 'snmpwalk -Oq -v 2c -c internal' # OIDも一緒に取得するsnmpwalkコマンド CPU_USAGE_OID = 'HOST-RESOURCES-MIB::hrProcessorLoad' # CPU使用率 # マルチコアの場合それぞれの使用率が返ってくる(INTEGER) LOAD_AVERAGE_OID = 'UCD-SNMP-MIB::laLoad' # ロードアベレージ # 1分間、5分間、15分間が返ってくる(STRING) STORAGE_NAME_OID = 'HOST-RESOURCES-MIB::hrStorageDescr' # ストレージ名 # 物理メモリなどの情報も含む(STRING) STORAGE_SIZE_OID = 'HOST-RESOURCES-MIB::hrStorageSize' # ストレージの総容量 # 物理メモリなどの情報も含む(INTEGER) STORAGE_USED_OID = 'HOST-RESOURCES-MIB::hrStorageUsed' # ストレージの使用量 # 物理メモリなどの情報も含む(INTEGER) MEMORY_TOTAL_OID = 'UCD-SNMP-MIB::memTotalReal' # メモリ総容量 # 物理メモリの総容量(INTEGER) MEMORY_AVAIL_OID = 'UCD-SNMP-MIB::memAvailReal' # メモリの空き容量 # 物理メモリの空き容量(INTEGER) def initialize(server_name) @server_name = server_name # 監視対象サーバー名 end def get_cpu_usage # CPU使用率(%)を取得するメソッド # 返り値はIntegerのArray(マルチコアの場合に対応するため) cpu_usages, error_message, exit_status = Open3.capture3("#{SNMPWALK_VALUE_ONLY} #{@server_name} #{CPU_USAGE_OID}") unless exit_status == 0 then raise SnmpError.new(error_message.chomp) end cpu_usages.split.map{|cpu_usage| cpu_usage.to_i } end def get_load_average # 1分間のロードアベレージを取得するメソッド # 返り値はFloat load_averages, error_message, exit_status = Open3.capture3("#{SNMPWALK_VALUE_ONLY} #{@server_name} #{LOAD_AVERAGE_OID}") unless exit_status == 0 then raise SnmpError.new(error_message.chomp) end load_averages.split.first.to_f end def get_storage_usage # スラッシュ始まりのファイルシステムごとの使用率(%)を取得するメソッド # 返り値は以下形式のArray # [ # {name: '/dev/sda1(String)', usage: 12.3(Float)}, # {name: '/dev/sdb1(String)', usage: 45.6(Float)}, # ... # ] storage_usages = Array.new storages = Hash.new storage_names_with_id, error_message, exit_status = Open3.capture3("#{SNMPWALK_QUICK_PRINT} #{@server_name} #{STORAGE_NAME_OID}") unless exit_status == 0 then raise SnmpError.new(error_message.chomp) end storage_names_with_id.split("\n").each{|storage_name_with_id| storage_name_with_id.match(/^#{STORAGE_NAME_OID}\.(\d+)\s(.+)$/) id = $1 storage_name = $2 if storage_name.match(/^\//) then storages.store(id, {name: storage_name}) end } storage_sizes_with_id, error_message, exit_status = Open3.capture3("#{SNMPWALK_QUICK_PRINT} #{@server_name} #{STORAGE_SIZE_OID}") unless exit_status == 0 then raise SnmpError.new(error_message.chomp) end storage_sizes_with_id.split("\n").each{|storage_size_with_id| storage_size_with_id.match(/^#{STORAGE_SIZE_OID}\.(\d+)\s(.+)$/) id = $1 storage_size = $2.to_f if storages.has_key?(id) then storages[id][:size] = storage_size end } storage_useds_with_id, error_message, exit_status = Open3.capture3("#{SNMPWALK_QUICK_PRINT} #{@server_name} #{STORAGE_USED_OID}") unless exit_status == 0 then raise SnmpError.new(error_message.chomp) end storage_useds_with_id.split("\n").each{|storage_used_with_id| storage_used_with_id.match(/^#{STORAGE_USED_OID}\.(\d+)\s(.+)$/) id = $1 storage_used = $2.to_f if storages.has_key?(id) then storages[id][:used] = storage_used end } storages.each_value{|name_size_used| name = name_size_used[:name] usage = name_size_used[:used] / name_size_used[:size] * 100 storage_usages.push({name: name, usage: usage}) } storage_usages end def get_memory_usage # メモリ使用率(%)を取得するメソッド # 返り値はFloat memory_total, error_message, exit_status = Open3.capture3("#{SNMPWALK_VALUE_ONLY} #{@server_name} #{MEMORY_TOTAL_OID}") unless exit_status == 0 then raise SnmpError.new(error_message.chomp) end memory_avail, error_message, exit_status = Open3.capture3("#{SNMPWALK_VALUE_ONLY} #{@server_name} #{MEMORY_AVAIL_OID}") unless exit_status == 0 then raise SnmpError.new(error_message.chomp) end memory_total_f = memory_total.to_f memory_avail_f = memory_avail.to_f 100 - memory_avail_f / memory_total_f * 100 end end
使用例
monitor.rb
require './snmp_ruby_wrapper.rb' # 使用例 # 定期監視する場合、各自で閾値や通知先を設定してください snmp_web102 = Snmp.new('web102') pp snmp_web102.get_cpu_usage pp snmp_web102.get_load_average pp snmp_web102.get_storage_usage pp snmp_web102.get_memory_usage実行結果
$ ruby monitor.rb [16] 0.26 [{:name=>"/", :usage=>17.468030286989382}, {:name=>"/dev/shm", :usage=>0.0}, {:name=>"/run", :usage=>11.34152585765489}, {:name=>"/sys/fs/cgroup", :usage=>0.0}, {:name=>"/run/user/0", :usage=>0.0}] 73.48760925655539
簡単な解説
大体のことはコード内の説明に書いてあるので、エラー処理の部分だけちょっと解説しておきます。最初はRuby側でタイムアウトしたときに例外(SnmpError)を発生させようとしていたのですが、Ruby内部で呼び出しているSNMPコマンド側でもエラーが発生するとのことだったので、SNMPコマンドが正常終了しなかった場合に例外を発生させるようにしました。
web4649
のロードアベレージが1を超えていたり、メモリ使用率が90%を超えていたり、そもそもSNMPでの監視ができない場合にどこかへ通知したいのなら、こんな感じにすれば良いと思います。
monitor_web4649.rb
require './snmp_ruby_wrapper.rb' snmp_web4649 = Snmp.new('web4649') begin load_average = snmp_web4649.get_load_average if load_average > 1.0 then alert("ロードアベレージが1を超えています!ロードアベレージ:#{load_average}") end memory_usage = snmp_web4649.get_memory_usage if memory_usage > 90.0 then alert("メモリ使用率が90%を超えています!メモリ使用率:#{memory_usage}") end rescue SnmpError alert("web4649が止まっているかもしれません!") end
まとめ
Net-SNMPのRubyラッパーを作ってあげることで、サーバーを監視してそのログを溜めたり、異常があったときにどこかへ通知することが簡単にできるようになりました。皆さんもこれを機に、サーバー監視に挑戦してみてください。
CONTACT
お問い合わせ・ご依頼はこちらから