muninでRDSのグラフ作成

GWの予定が何もない@mikedaです。だれか飲みに行きましょう。

3週間前にmuninを導入したので、今日はそこで使ってるRDS監視の仕組みを紹介します。 『munin-nodeがインストール出来ないモノをどう監視するか』という問題について、だいたい同じ感じで使えるかな。

実際のグラフ

RDSが普通にノードとして並んでいて、

rds_munin01

選択するとCPU使用率、クエリ数などが見れるようになってます。

rds_munin02

rds_munin03

では以降は細かい設定について。

muninサーバの設定

pluginの配置

[root@mon01 plugins]# pwd
/etc/munin/plugins
[root@mon01 plugins]# ll rds_*
... rds_heyazine-db01_cloudwatch -> /usr/share/munin/plugins/rds_cloudwatch
... rds_heyazine-db01_commands -> /usr/share/munin/plugins/rds_commands
... rds_heyazine-db01_slowquery -> /usr/share/munin/plugins/rds_slowquery
... rds_recs-db1_cloudwatch -> /usr/share/munin/plugins/rds_cloudwatch
... rds_recs-db1_commands -> /usr/share/munin/plugins/rds_commands
... rds_recs-db1_slowquery -> /usr/share/munin/plugins/rds_slowquery

現状の仕様だと、RDSのノード名をファイル名に含める形でシンボリックリンクを作成しています。

munin.conf

[root@mon01 ~]# cat /etc/munin/munin.conf 
includedir /etc/munin/conf.d
graph_strategy cgi
cgiurl_graph /munin-cgi/munin-cgi-graph
html_strategy cron

[heyazine;]

### EC2
[heyazine;heyazine-app01]
  address 10.0.0.101
  use_node_name yes

### RDS
[heyazine;heyazine-db01]
  address 127.0.0.1
  use_node_name no

...

RDSはaddressが127.0.0.1、use_node_nameをnoに設定します。 これで、プラグインのconfig実行時に『host_name "RDS名"』を出力してやると仮想ノードが作れます。

CloudWatchを使うプラグイン

CPU使用率、空きディスク容量、空きメモリ、Latency、IOPS、などなどのグラフを作っています。

munin-runの実行結果

[root@mon01 ~]# munin-run rds_heyazine-db01_cloudwatch config
host_name heyazine-db01

multigraph rds_CPUUtilization
graph_title CPU Utilization
graph_args --base 1000
graph_vlabel %
graph_category rds
CPUUtilization.label CPUUtilization
CPUUtilization.min 0
CPUUtilization.type GAUGE

multigraph rds_FreeStorage
graph_title Free Storage
graph_args --base 1024
graph_vlabel Bytes
graph_category rds
FreeStorageSpace.label FreeStorageSpace
FreeStorageSpace.min 0
FreeStorageSpace.type GAUGE

...
[root@mon01 ~]# munin-run rds_heyazine-db01_cloudwatch
multigraph rds_CPUUtilization
CPUUtilization.value 11.97

multigraph rds_FreeStorage
FreeStorageSpace.value 97459675136.0

multigraph rds_FreeMemory
FreeableMemory.value 1017327616.0

multigraph rds_SwapUsage
SwapUsage.value 73728.0

multigraph rds_Latency
ReadLatency.value 0.0
WriteLatency.value 0.01243260417939631

multigraph rds_Throughput
ReadThroughput.value 0.0
WriteThroughput.value 1134931.0620175672

multigraph rds_IOPS
ReadIOPS.value 0.0
WriteIOPS.value 87.83457821103588

multigraph rds_Connections
DatabaseConnections.value 7.0

プラグインのコード


#!/usr/bin/ruby
require 'aws-sdk'
require 'pp'

graph_category = 'rds'
graphs = [
  { title: 'CPU Utilization', vlabel: '%',           base: 1000, metrics: %w( CPUUtilization ) },
  { title: 'Free Storage',    vlabel: 'Bytes',       base: 1024, metrics: %w( FreeStorageSpace ) },
  { title: 'Free Memory',     vlabel: 'Bytes',       base: 1024, metrics: %w( FreeableMemory ) },
  { title: 'Swap Usage',      vlabel: 'Bytes',       base: 1024, metrics: %w( SwapUsage ) },
  { title: 'Latency',         vlabel: 'sec',         base: 1000, metrics: %w( ReadLatency WriteLatency ) },
  { title: 'Throughput',      vlabel: 'bytes/sec',   base: 1000, metrics: %w( ReadThroughput WriteThroughput ) },
  { title: 'IOPS',            vlabel: 'io/s',        base: 1000, metrics: %w( ReadIOPS WriteIOPS) },
  { title: 'Connections',     vlabel: 'connections', base: 1000, metrics: %w( DatabaseConnections ) },
]

$0 =~ /rds_([^_]+)_cloudwatch$/ or exit 1
db = $1

if ARGV.shift == 'config'
  puts "host_name #{db}"
  graphs.each do |graph|
    puts "multigraph rds_#{graph[:title].delete(' ')}"
    puts "graph_title #{graph[:title]}"
    puts "graph_args --base #{graph[:base]}"
    puts "graph_vlabel #{graph[:vlabel]}"
    puts "graph_category #{graph_category}"
    graph[:metrics].each do |metric|
      puts "#{metric}.label #{metric}"
      puts "#{metric}.min 0"
      puts "#{metric}.type GAUGE"
    end
    puts ""
  end
  exit 0
end

require File.expand_path(File.dirname(File.readlink(__FILE__)) + '/aws_config')
#AWS.config(
#  access_key_id:     'XXXXXXXXXXXX',
#  secret_access_key: 'XXXXXXXXXXXX',
#  region:            'ap-northeast-1'
#)

cw = AWS::CloudWatch.new
metrics = cw.metrics.with_namespace('AWS/RDS')
            .with_dimension('DBInstanceIdentifier', db)

stats = {}
graphs.map{|g| g[:metrics]}.flatten.each do |metric|
  stats[metric] = metrics.with_metric_name(metric).first.statistics(
    :start_time => Time.now - 300,
    :end_time => Time.now,
    :statistics => ['Average']).sort_by{|s| s[:timestamp]}.last[:average]
end

graphs.each do |graph|
  puts "multigraph rds_#{graph[:title].delete(' ')}"
  graph[:metrics].each do |metric|
    puts "#{metric}.value #{stats[metric]}"
  end
  puts ""
end

multigraphというコマンドを使って、1つのプラグインで複数のグラフを作っています。 この手法はいくつかの場合に有用です。

  • いっぱいシンボリックリンク作る手間を省く
  • ログ集計などの重たい処理の回数を減らす
  • 複数グラフを完全に同じタイミングで取得した情報から作る
  • ノード毎のグラフの有無をplugin側で制御する(MySQLスレーブだけスレーブ遅延グラフ作る、Solrのコア数ぶんグラフを作る、など)

mysqlに接続してshow statusの結果を使うプラグイン

標準プラグインをゴニョって使う方法も考えたんですが、まずはクエリ数/スロークエリ数だけ見れればいいかと思ってサクッと別に作りました。 DB負荷が問題になってきたらInnoDB行読み込み数や各種キャッシュヒット率とかもグラフ化したいので、ちょっとやりかた変えるかも。

mysqlに接続するための情報はplugin設定に記載しています

[root@mon01 ~]# cat /etc/munin/plugin-conf.d/rds 
[rds_heyazine-db01_*]
  env.mysqlhost     heyazine-db01.XXXXXX.ap-northeast-1.rds.amazonaws.com
  env.mysqlusername dbuser
  env.mysqlpassword XXXXXXXX
...

munin-runの実行結果

[root@mon01 ~]# munin-run rds_heyazine-db01_commands config
host_name heyazine-db01

graph_args --base 1000
graph_title Queries
graph_vlabel QPS
graph_total Questions
graph_category mysql

select.draw AREA
select.min 0
select.label select
select.type COUNTER

insert.draw STACK
insert.min 0
insert.label insert
insert.type COUNTER

...
[root@mon01 ~]# munin-run rds_heyazine-db01_commands
select.value 19360231
insert.value 957751
insert_select.value 0
update.value 8445730
update_multi.value 0
delete.value 387547
replace.value 0
replace_select.value 0

プラグインのコード

クエリ数取得プラグイン


#!/usr/bin/ruby

require 'mysql2'
require 'pp'

commands = %w( select insert insert_select update update_multi delete replace replace_select )

$0 =~ /rds_([^_]+)_\w+$/ or exit 1
db = $1

if ARGV.shift == 'config'
  puts "host_name #{db}"
  puts 'graph_args --base 1000'
  puts 'graph_title Queries'
  puts 'graph_vlabel QPS'
  puts 'graph_total Questions'
  puts 'graph_category mysql'
  commands.each.with_index do |command, i|
    puts "#{command}.draw #{i == 0 ? 'AREA' : 'STACK'}"
    puts "#{command}.min 0"
    puts "#{command}.label #{command}"
    puts "#{command}.type COUNTER"
  end
  exit 0
end

client = Mysql2::Client.new(
  host:     ENV['mysqlhost']     || 'localhost',
  username: ENV['mysqlusername'] || 'root',
  password: ENV['mysqlpassword'] || ''
)

counts = {}
client.query('SHOW GLOBAL STATUS').each do |row|
  counts[row['Variable_name']] = row['Value']
end

commands.each do |command|
  puts "#{command}.value #{counts['Com_' + command]}"
end

スロークエリ数の取得プラグイン


#!/usr/bin/ruby

require 'mysql2'
require 'pp'

$0 =~ /rds_([^_]+)_slowquery$/ or exit 1
db = $1

if ARGV.shift == 'config'
  puts "host_name #{db}"
  puts 'graph_args --base 1000'
  puts 'graph_title Slow Query'
  puts 'graph_vlabel QPS'
  puts 'graph_category mysql'
  puts "rds_slowquery.min 0"
  puts "rds_slowquery.label Slow Query"
  puts "rds_slowquery.type COUNTER"
  exit 0
end

client = Mysql2::Client.new(
  host:     ENV['mysqlhost']     || 'localhost',
  username: ENV['mysqlusername'] || 'root',
  password: ENV['mysqlpassword'] || ''
)

count = client.query("SHOW GLOBAL STATUS LIKE 'Slow_queries'").first['Value']

puts "rds_slowquery.value #{count}"

まとめ

RDSをmunin使ってこんな感じでグラフ化してますよ!という話でした。

muninはmunin-nodeをインストールできるサーバの監視はとても簡単なのですが、ELB/RDSなどの仮想ノードやネットワーク機器の監視はちょっとトリッキーで面倒くさい感じになっちゃいますね。