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

AWS ALB(Application Load Balancer)用のMuninプラグイン作ってみた

AWS Munin

CloudWatchでも確認できるのですが、やはり集中管理できていると便利な場面も多くMunin用のプラグイン作りました。ELBからほぼ流用できるかと思いきや調整が必要でした。

yomon.hatenablog.com

グラフとメトリクス洗い出し

まずは表示したいグラフとメトリクスを洗い出します。

メトリクス一覧取得は以下のコマンドで取得可能です。

aws cloudwatch list-metrics --namespace AWS/ApplicationELB

メトリクスの説明は以下のページです。

docs.aws.amazon.com

今回は以下のように作ることにしました。

グラフ名 メトリクス
Hosts Count HealthyHostCount
UnhealthyHostCount
ALB HTTP Status HTTPCode_ELB_4XX_Count
HTTPCode_ELB_5XX_Count
Host HTTP Stauts HTTPCode_Target_2XX_Count
HTTPCode_Target_3XX_Count
HTTPCode_Target_4XX_Count
HTTPCode_Target_5XX_Count
Request Count RequestCount
Connection Status NewConnectionCount
ActiveConnectionCount
RejectedConnectionCount
TargetTLSNegotiationErrorCount
TargetConnectionErrorCount
ClientTLSNegotiationErrorCount
Processed Bytes ProcessedBytes
Average Latency TargetResponseTime

関連ファイル一覧

関連するファイルや情報は以下の通り。適宜読み替えてください。

内容 ファイル
ロードバランサ名(例) app/load-balancer-name/1234567890123456
ターゲットグループ名(例) targetgroup/target-group-name/9876543210987654
設定ファイル /etc/munin/plugin-conf.d/aws/alb.yml
プラグイン実ファイル /usr/share/munin/plugins/alb_cloudwatch
プラグインリンク /etc/munin/plugins/alb_load-balancer-name_cloudwatch

設定ファイル

設定ファイルはYAML形式です。

ALBの名前は load-balancer-name だけでなく app/load-balancer-name/1234567890123456 まで必要になります。この辺り、そしてターゲットグループもAPIの引数で渡す必要があるので、その辺りを設定ファイルで吸収しています。

Muninのグラフの見た目も draw の項目で最低限調整できるようにしてあります。 LINE STACK AREA などをが設定できます。どんな見た目になるかは以下のサイトなどが参考になります。

Munin Graph Draw Styles - SysMonBlog

設定ファイルの例を以下に貼っておきます。

---
# AWS Settings
common:
  access_key: <Access Key>
  secret_key: <Secret Key>
  region: ap-northeast-1
  category: alb
  namespace: AWS/ApplicationELB
  timespan: 300
  period: 300
#ALB Settings
alb:
- name: load-balancer-name
  fullname: app/load-balancer-name/1234567890123456
  targetgroups:
    - targetgroup/target-group-name/9876543210987654
# Graph definitions
graphs:
- title: Hosts Count
  vlabel: Host Count
  base: 1000
  metrics:
    - metric_name: HealthyHostCount
      label: Healthy Hosts
      statistics: Minimum
      draw: AREASTACK
      targetgroup_required: true
    - metric_name: UnhealthyHostCount
      label: Unhealthy Hosts
      statistics: Maximum
      draw: AREASTACK
      targetgroup_required: true
- title: ALB HTTP Status
  vlabel: Count
  base: 1000
  metrics:
    - metric_name: HTTPCode_ELB_4XX_Count
      label: Sum ALB HTTP 4XXs
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
    - metric_name: HTTPCode_ELB_5XX_Count
      label: Sum ALB HTTP 5XXs
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
- title: Host HTTP Stauts
  vlabel: Count
  base: 1000
  metrics:
    - metric_name: HTTPCode_Target_2XX_Count
      label: Sum HTTP 2XXs
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
    - metric_name: HTTPCode_Target_3XX_Count
      label: Sum HTTP 3XXs
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
    - metric_name: HTTPCode_Target_4XX_Count
      label: Sum HTTP 4XXs
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
    - metric_name: HTTPCode_Target_5XX_Count
      label: Sum HTTP 5XXs
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
- title: Connection Status
  vlabel: Count
  base: 1000
  metrics:
    - metric_name: NewConnectionCount
      label: New Connection
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
    - metric_name: ActiveConnectionCount
      label: Active Connection Count
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
    - metric_name: RejectedConnectionCount
      label: Rejected connections
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
    - metric_name: TargetTLSNegotiationErrorCount
      label: Target TLS Negotiation Errors
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
    - metric_name: TargetConnectionErrorCount
      label: Target connection errors
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
    - metric_name: ClientTLSNegotiationErrorCount
      label: Client TLS Negotiation Errors
      statistics: Sum
      draw: AREASTACK
      targetgroup_required: false
- title: Request Count
  vlabel: Count
  base: 1000
  metrics:
    - metric_name: RequestCount
      label: Sum Count
      statistics: Sum
      draw: LINE2
      targetgroup_required: false
- title: Processed Bytes
  vlabel: Bytes
  base: 1024
  metrics:
    - metric_name: ProcessedBytes
      label: Processed Bytes
      statistics: Sum
      draw: LINE
      targetgroup_required: false
- title: Latency
  vlabel: Count
  base: 1000
  metrics:
    - metric_name: TargetResponseTime
      label: Max Latency
      statistics: Maximum
      draw: AREA
      targetgroup_required: false
    - metric_name: TargetResponseTime
      label: Average Latency
      statistics: Average
      draw: LINE1
      targetgroup_required: false

プラグイン実ファイル

shebangは環境に合わせて修正してください。 aws-sdk のGemが必要になります。

#!/var/lib/munin/rbenv/shims/ruby
require 'aws-sdk'
require 'yaml'

prefix = 'alb'
config = YAML.load_file('/etc/munin/plugin-conf.d/aws/alb.yml')
albs = config['alb']
graphs = config['graphs']

if m = (File.basename(__FILE__)).to_s.match(/alb_([^_]+)_cloudwatch$/)
  lb_name = m[1]
else
  exit 1
end
alb = albs.select{|a| a['name'] == lb_name}.first
lb_full_name = alb['fullname']

if ARGV.shift == 'config'
  puts "host_name #{lb_name}"
  puts ""
  graphs.each do |graph|
    puts "multigraph #{prefix}_#{graph['title'].delete(' ')}"
    puts "graph_title #{graph['title']}"
    puts "graph_args --base #{graph['base']}"
    puts "graph_vlabel #{graph['vlabel']}"
    puts "graph_category #{config['common']['category']}"
    graph['metrics'].each do |metric|
      if metric['targetgroup_required']
        alb['targetgroups'].each do |group|
          target_group_name = group.split('/')[1]
          metric_name = "#{metric['metric_name'].gsub('.','_')}_#{target_group_name}_#{metric['statistics']}"
          puts "#{metric_name}.label #{metric['label']} #{target_group_name}"
          puts "#{metric_name}.min 0"
          puts "#{metric_name}.draw #{metric['draw']}" if !metric['draw'].nil?
          puts "#{metric_name}.type GAUGE"
        end
      else
        metric_name = "#{metric['metric_name'].gsub('.','_')}_#{metric['statistics']}"
        puts "#{metric_name}.label #{metric['label']}"
        puts "#{metric_name}.min 0"
        puts "#{metric_name}.draw #{metric['draw']}" if !metric['draw'].nil?
        puts "#{metric_name}.type GAUGE"
      end
    end
    puts ""
  end
  exit 0
end

cred = Aws::Credentials.new(
  config['common']['access_key'],
  config['common']['secret_key']
)
cw = Aws::CloudWatch::Client.new(region: config['common']['region'], credentials: cred)

graphs.each do |graph|
  puts "multigraph #{prefix}_#{graph['title'].delete(' ')}"
  graph['metrics'].each do |metric|
    cwparams = []
    if metric['targetgroup_required']
     alb['targetgroups'].each do |group|
      cwparams << {
                      metric_name: "#{metric['metric_name'].gsub('.','_')}_#{group.split('/')[1]}_#{metric['statistics']}",
                      dimensions: [{
                        name: 'LoadBalancer', value: lb_full_name
                      }, {
                        name: 'TargetGroup', value: group
                      }]
                   }
     end
    else
     cwparams << {
                      metric_name: "#{metric['metric_name'].gsub('.','_')}_#{metric['statistics']}",
                      dimensions: [{ name: 'LoadBalancer', value: lb_full_name}]
                   }
    end

    cwparams.each do |cwparam|
      data = cw.get_metric_statistics({
        namespace: config['common']['namespace'],
        metric_name: metric['metric_name'],
        start_time: Time.now - config['common']['timespan'],
        dimensions: cwparam[:dimensions],
        end_time: Time.now,
        period: config['common']['period'],
        statistics: [metric['statistics']]
      }).datapoints
      value = data.empty? ? 0 : data.first[metric['statistics'].downcase]
      puts "#{cwparam[:metric_name]}.value #{value}"
    end
  end
  puts ""
end

プラグインのリンク作成

ロードバランサー名(この例では load-balancer-name )を入れる形 でシンボリックリンクはMuninのプラグインフォルダに作成します。

ln -s /usr/share/munin/plugins/alb_cloudwatch /etc/munin/plugins/alb_load-balancer-name_cloudwatch

munin.conf設定

実ノードが無い対象の監視なので、以下のように設定します。

[domain_name;load-balancer-name]
    address 127.0.0.1
    use_node_name no