AWS CloudWatch からの Zabbix 連携
みなさんこんにちは。ふりっぱぁです。Zabbix、活用できていますか?
私はと言うと、新バージョンとなる3.0.xや3.2.xの流れについていくことがなかなか厳しく、未だに2.0.xを利用している状態だったりします(汗)
さて、個人でも手軽に始められるクラウドサービスの代表格となってしまったAWS、EC2やRDS等、さまざまなサービスが提供され、皆さん便利に活用されているかと思います。
今回は、その中でも、稼働監視やパフォーマンス監視に利用できるサービス、AWS CloudWatchが収集してくれている項目(メトリクス)のデータを、Zabbixと連携させるネタで行ってみようと思います。
既に多くの方々がブログ等で、CloudWatchのメトリクス値を取り込む工夫を公開されていますが、実際に使う過程において、少しでもスッキリさせられないかと考えてみました。
本記事の想定する環境
- Zabbix 3.2.2-1 (ZABBIX SIA rpm パッケージ配布版)
- CentOS 7.3.1611
- apache httpd / mariadb / php / ruby 等は CentOS7 標準構成
- ruby gems (
gem install xxxx
でインストール)- zabbixapi(2.4.9)
- open4(1.3.4)
- right_aws(3.1.0)
- ruby gems (
よく見かける構成と、その問題点
CloudWatchのメトリクス1項目に対し、UserParameterやexternalscriptsの1アイテムとして設定し、Zabbixで収集させる
- アイテムの設定が煩雑
CloudWatchでは1インスタンスあたりで10を超える数のメトリクスが提供されています。
1項目を1アイテムとしてZabbixに設定するとなると、個々の項目に関する設定をWebUIから入れる必要があり、結構煩雑です。 - 外部スクリプトの実行回数が多くなる
CloudWatchからデータを取り込むためのスクリプトを、インスタンス数xメトリクス数 の回数だけ実行させなければいけません。
インスタンス数が少ないうちは良いのですが、ソコソコの数になってくると、zabbix_server 側の poller プロセスのビジー率が上がってしまい、データの取込がモタついたりすることもあります。 - zabbix_agentd のタイムアウト
zabbix_agentdから外部のスクリプトを実行させるような構成とした場合、zabbix_agentd.confのTimeoutパラメータの設定値を超えるような時間が掛かってしまうと、タイムアウトとなってしまって、結果値の取込に失敗してしまいます。
もちろん設定最大値となる30秒を超える時間が掛からなければ良いわけですが、ネットワーク等、周囲の環境によっては起こりうるわけで、対処できれば良いですよね。 - 最新のデータしか取り込めない
外部スクリプトとして実行した場合、その時点でCloudWatchから得た最新値を、その時点の値として登録することしかできません。
各仮想ホスト内部のステータス情報はzabbix_agentdを用いて収集しているとしても、CloudWatchに蓄積された値を取り込むために、リアルタイムの値に束縛されるのは避けたいし、連携させようと思い立った時点で「CloudWatchの履歴値」として保存されている過去の値データも、可能ならばZabbix側へ取り込んでしまいたい
今回めざす構成 - CloudWatch からの取込スクリプト
- 煩雑なアイテム設定等はしたくない
可能な限り、各インスタンス単位等のアイテム設定は弄りたくない。 - 1回の実行で、全部片付けてしまいたい
EC2インスタンスが複数ある場合でも、1回の実行でとにかく全部済ませてしまいたい。 - インスタンスタイプ毎に若干異なる構成にフィットさせたい
インスタンスタイプ毎に異なることがあるメトリクス構成や、EBSのストレージに関する情報も、インスタンス情報の一部として取り込んでしまいたい - 複数のAWSアカウントをまとめて処理
監視対象ホストごとに異なるAWSアカウントが混在しており、まとめて管理したい
そこで、テンプレート・Zabbix API・ホストマクロ・zabbix_sender を組み合わせ利用して、一回の実行だけで、一括してCloudWatchのデータをZabbixへ登録するようなrubyスクリプトを作成しました。
事前にZabbixサーバへ投入する設定
1. AWS のアカウント情報を登録するための、ダミーとなるホスト
AWSの機能を呼び出すためには、ACCESS_KEY・SECRET_KEYと、2つのキー情報が必要となります。
これをスクリプト本体に直接記載してしまっても良いのですが、誰かが盗み見てしまうかもしれません。
そこで、スクリプトには直接記載せず、Zabbixのホストマクロとして登録することを考えてみます。
監視対象としたいホストに対するホストマクロとして、これらの値を設定する場合、WebUIで当該ホストに対する読み書き権限を持っているZabbixユーザ全員が、その内容を参照・編集できてしまいます。
また、監視対象としたいホストが多い場合、すべてのホストに対して、これらキー情報を登録しなければいけません。
そこで、ZabbixのWebUIからダミーとなるホストを1つ登録し、そのホストに対するマクロとしてキー情報を登録することにします。
このダミーのホストに対しては、スクリプト内部からZabbix API経由でアクセスしてキー情報を得るZabbixユーザのみが権限を持っていれば良いため、ある程度、情報を隠匿することが可能です。
新規ホストとして作成・登録し、AWSアカウントが複数ある場合には、それらが識別できるようなホスト名を付与します。
画像で示すように、2つのキー情報を、ホストに対するマクロ設定として設定します。
2. CloudWatch からの情報連携を行うホストに適用するテンプレート
各インスタンスを示すホストに適用し、スクリプトから値を投げ込むターゲットを特定するためのテンプレートを作成します。
このテンプレートを適用したホストが、CloudWatchからの値を連携したいホスト、ということになります。
ほとんどの設定はスクリプト内部からZabbix APIを利用して作成してしまうため、ここで行う設定はほんの少しです。
- 新規テンプレート「T_AWS_EC2」を作成・登録します
- CloudWatchから取り込むデータを保管するアイテムを所属させるため、アプリケーション「EC2」を設定します。
3. 監視対象としたい インスタンス に対応する ホスト
インスタンス内部のゲストOSにzabbix_agentdをインストールして、通常のZabbix監視を実施しているならば、特に意識することはありません。
既に設定済のホストを、ほぼそのまま利用することが可能です。
- 先の手順(2.)で作成した「T_AWS_EC2」テンプレートを適用します
- ホストに対して、先の手順(1.)で作成した関連AWSアカウント情報を含むホストの名称を、マクロとして設定します
- ホストに対して、AWS上でEC2インスタンスに関連付けられている「Name」タグの値を、マクロとして設定します
スクリプトの配置と実行
スクリプトは、cronでの実行を基本と考えて実装しています。
上で示したような設定を施した全てのホストに関するCloudWatchのデータを、たった1度の実行で一括で取り込んでしまうことから、Zabbixの特定ホストに紐付くものではなくなってしまったためです。
Zabbixサーバとなっているホストに導入してしまうのが簡単で良いかと思います。
CloudWatchから得た値の格納先となるZabbixのアイテムを、実行時にスクリプト内部からZabbix APIで作成しています。
そのため、初回の実行時や、インスタンスの構成が変化した時には、若干、実行に時間が掛かります。
特に引数は必要ありません。ruby
コマンドにスクリプトファイル名を与えて実行するだけとなります。
user ~$ ruby aws_ec2info.rb
cronに設定する場合には、以下のような感じになるでしょうか。
15 * * * * zabbix ruby /opt/zabbix/scripts/aws_ec2info.rb </dev/null >/dev/null 2>&1
処理のおおまかな流れ
- Zabbix APIを利用してZabbixへ接続
- T_AWS_EC2テンプレートを探す
- T_AWS_EC2テンプレートが適用された「CloudWatch連携対象ホスト」を探す
- 「CloudWatch連携対象ホスト」に設定されたホストマクロの値から「Nameタグの値」を得る
- 「CloudWatch連携対象ホスト」に設定されたホストマクロの値から「AWSアカウント情報ホスト」の名称を得る
- 「AWSアカウント情報ホスト」を探す
- 「AWSアカウント情報ホスト」に設定されたホストマクロの値から「ACCESS_KEY/SECRET_KEY」を得る
- 「ACCESS_KEY/SECRET_KEY」と「Nameタグの値」からAWSへアクセスし、関連する期間のCloudWatchメトリクス値を得る
- 「CloudWatch連携対象ホスト」に、メトリクス値を保存するためのアイテム設定が無い場合、APIを用いて作成する
- アイテム設定を作成した場合、Serverの設定がリフレッシュされるまで、少しsleepする
- 「CloudWatch連携対象ホスト」の収集値として、zabbix_senderでデータを投げ込む
実行すると‥
下図のように、EC2・EBSのメトリクス値が、対象ホストのアイテムとして登録されます。
EBSを利用したストレージをたくさんアタッチしているようなインスタンスでは、その個数だけの項目が生成されている‥はずです。
スクリプトのソースコード
以下、ソースコードです。
#!/bin/env ruby -Ku
# -*- encoding: utf-8 -*-
begin
require 'rubygems'
rescue LoadError
end
require 'logger'
require 'pp'
require 'zabbixapi'
require 'open4'
require 'right_aws'
require 'time'
SUCCESS = 0
FAILURE = 1
SELF_EXT = File.extname(__FILE__)
SELF_BASE = File.basename(__FILE__, SELF_EXT)
LOGFILE = "/var/log/#{SELF_BASE}.log"
class AWSEC2Info
attr_accessor(:logger)
attr_accessor(:zbxapi)
attr_accessor(:EC2Info)
ZABBIX_API_URL = 'http://%s/zabbix/api_jsonrpc.php'
ZABBIX_LOGINID = 'Admin'
ZABBIX_PASSWORD = 'zabbix'
ZABBIX_SENDER_PATH = '/usr/bin/zabbix_sender'
ZABBIX_SERVER_CONF_PATH = '/etc/zabbix/zabbix_server.conf'
ZABBIX_AGENTD_CONF_PATH = '/etc/zabbix/zabbix_agentd.conf'
ZABBIX_ATTEMPTS_MAX = 3
ZABBIX_ATTEMPTS_WAIT = 1
ZABBIX_TEMPLATE_NAME = 'T_AWS_EC2'
ZABBIX_ITEM_KEY_FORMAT_EC2 = 'user.ec2.info[%s]'
ZABBIX_ITEM_KEY_FORMAT_EBS = 'user.ec2.info[%s,%s]'
ZABBIX_ITEM_TYPE_TRAPPER = 2
ZABBIX_ITEM_STATUS_ACTIVE = 0
ZABBIX_ITEM_VALUE_TYPE_FLOAT = 0
ZABBIX_ITEM_DELAY = 0
ZABBIX_ITEM_HISTORY = 30
ZABBIX_ITEM_TRENDS = 365
ZABBIX_ITEM_APPNAME = 'EC2'
ZABBIX_ITEM_CREATED = 0
ZABBIX_ITEM_UPDATED = 1
MACROKEY_AWS_ACCESS_KEY = '{$ACCESS.KEY}'
MACROKEY_AWS_SECRET_KEY = '{$SECRET.KEY}'
MACROKEY_AWS_ACCOUNT_HOST = '{$ACCOUNT.HOST}'
MACROKEY_EC2_INSTANCE_NAME = '{$INSTANCE.NAME}'
# AWS EC2 GetData
AWS_EC2_SERVER = 'ec2.ap-northeast-1.amazonaws.com'
TAGKEY_NAME = 'Name'
# AWS CloudWatch GetData
AWS_ACW_SERVER = 'monitoring.ap-northeast-1.amazonaws.com'
GETDATA_EC2_NAMESPACE = 'AWS/EC2'
GETDATA_EBS_NAMESPACE = 'AWS/EBS'
# 収集データサンプルの時間間隔 : 300 sec
GETDATA_PERIOD = 300
# 収集データの時間範囲 : 2 時間分
GETDATA_RANGE = (60 * 60 * 2)
# 収集データの時間基準点 : 現在時刻
GETDATA_OFFSET = (60 * 60 * 24 * 0)
GETDATA_ENDTIME = (Time.now.utc - GETDATA_OFFSET)
GETDATA_STARTTIME = (GETDATA_ENDTIME - GETDATA_RANGE)
# statistics : for use, xxx.downcase.to_sym
GETDATA_STAT_AVG = 'Average' # :average
GETDATA_STAT_MIN = 'Minimum' # :minimum
GETDATA_STAT_MAX = 'Maximum' # :maximum
GETDATA_STAT_SUM = 'Sum' # :sum
GETDATA_UNIT_MAP = {
'Percent' => '%',
'Bytes' => 'B',
'Bytes/Second' => 'Bps',
'Seconds' => 's'
}
GETDATA_NAME_UNKNOWN_EC2 = 'CloudWatch EC2 項目 [%s]'
GETDATA_NAME_UNKNOWN_EBS = 'CloudWatch EBS 項目 [%s,%s]'
# name => Monitor item display name
# statistics => Value statistics
# default : GETDATA_STAT_AVG
# unit => Value custom unit
# default : '' <empty string>
# if exists AWS unit result in GETDATA_UNIT_MAP, then determine by map.
# if set this element, forcibly use it.
GETDATA_MEASURES = {
# EC2
'CPUUtilization' => { :name => 'CloudWatch CPU 使用率' },
'CPUCreditBalance' => { :name => 'CloudWatch CPU クレジット残高' },
'CPUCreditUsage' => { :name => 'CloudWatch CPU クレジット消費' },
'DiskReadOps' => { :name => 'CloudWatch Ephemeral ストレージ 読込 IO 件数', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD, :unit => 'IOPS' },
'DiskWriteOps' => { :name => 'CloudWatch Ephemeral ストレージ 書込 IO 件数', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD, :unit => 'IOPS' },
'DiskReadBytes' => { :name => 'CloudWatch Ephemeral ストレージ 読込バイト数', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD, :unit => 'Bps' },
'DiskWriteBytes' => { :name => 'CloudWatch Ephemeral ストレージ 書込バイト数', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD, :unit => 'Bps' },
'NetworkIn' => { :name => 'CloudWatch ネットワーク データ受信量', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD, :unit => 'Bps' },
'NetworkOut' => { :name => 'CloudWatch ネットワーク データ送信量', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD, :unit => 'Bps' },
'NetworkPacketsIn' => { :name => 'CloudWatch ネットワーク パケット受信量', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD },
'NetworkPacketsOut' => { :name => 'CloudWatch ネットワーク パケット送信量', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD },
'StatusCheckFailed' => { :name => 'CloudWatch EC2 ステータスチェック(総合)' },
'StatusCheckFailed_Instance' => { :name => 'CloudWatch EC2 ステータスチェック(インスタンス)' },
'StatusCheckFailed_System' => { :name => 'CloudWatch EC2 ステータスチェック(システム)' },
# EBS
'VolumeReadBytes' => { :name => 'CloudWatch EBS 読込バイト数 [%s]', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD, :unit => 'Bps' },
'VolumeWriteBytes' => { :name => 'CloudWatch EBS 書込バイト数 [%s]', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD, :unit => 'Bps' },
'VolumeReadOps' => { :name => 'CloudWatch EBS 読込 IO 件数 [%s]', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD, :unit => 'IOPS' },
'VolumeWriteOps' => { :name => 'CloudWatch EBS 書込 IO 件数 [%s]', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD, :unit => 'IOPS' },
'VolumeTotalReadTime' => { :name => 'CloudWatch EBS 消費時間率(読込) [%s]', :divide => (GETDATA_PERIOD/100), :unit => '%' },
'VolumeTotalWriteTime' => { :name => 'CloudWatch EBS 消費時間率(書込) [%s]', :divide => (GETDATA_PERIOD/100), :unit => '%' },
'VolumeIdleTime' => { :name => 'CloudWatch EBS 消費時間率(アイドル) [%s]', :divide => (GETDATA_PERIOD/100), :unit => '%' },
'VolumeQueueLength' => { :name => 'CloudWatch EBS IO キュー件数 [%s]', :statistics => GETDATA_STAT_MAX },
'VolumeThroughputPercentage' => { :name => 'CloudWatch EBS 提供 IOPS 率 [%s]' },
'VolumeConsumedReadWriteOps' => { :name => 'CloudWatch EBS 消費 IOPS 数 [%s]', :statistics => GETDATA_STAT_SUM, :divide => GETDATA_PERIOD, :unit => 'IOPS' }
}
def execcmd(_cmdline = '', _str_stdin = '')
_str_stdout = ''
_str_stderr = ''
_status = Open4::popen4(_cmdline) {|_pid, _stdin, _stdout, _stderr|
_str_stdin.each_line {|_s|
_stdin.puts _s
}
_stdin.close_write
_str_stdout = _stdout.read
_str_stderr = _stderr.read
}
return _status, _str_stdout, _str_stderr
end
def get_server_conf_freq()
File.open(ZABBIX_SERVER_CONF_PATH) {|_file|
_file.each do |_s|
if /^CacheUpdateFrequency=/ =~ _s
return _s.split('=')[1].chomp.to_i
end
end
}
# if not defined, use default
return 60
end
def get_server_ip()
File.open(ZABBIX_AGENTD_CONF_PATH) {|_file|
_file.each do |_s|
if /^ServerActive=/ =~ _s
_serverinfo = _s.split('=')[1].chomp
_x = _serverinfo.split(':')[0]
return '127.0.0.1' if _x.nil? || _x.empty?
return _x
end
end
}
# if not defined, use default
return '127.0.0.1'
end
def get_server_port()
File.open(ZABBIX_AGENTD_CONF_PATH) {|_file|
_file.each do |_s|
if /^ServerActive=/ =~ _s
_serverinfo = _s.split('=')[1].chomp
_x = _serverinfo.split(':')[1]
return '10051' if _x.nil? || _x.empty?
return _x
end
end
}
# if not defined, use default
return '10051'
end
def api_login()
_attempts = 0
begin
_attempts += 1
@zbxapi = ZabbixApi.connect(
:url => ZABBIX_API_URL%[get_server_ip()],
:user => ZABBIX_LOGINID,
:password => ZABBIX_PASSWORD)
rescue
if _attempts <= ZABBIX_ATTEMPTS_MAX then
@logger.warn('Zabbix Login Failure, so retry it.(fail count : %d)'%[_attempts])
sleep(ZABBIX_ATTEMPTS_WAIT)
retry
else
@logger.error('Zabbix Login Failure, retry failed.(fail count : %d)'%[_attempts])
raise 'Zabbix Login Failure'
end
end
end
def api_call(_method = '', _params = Hash.new())
return @zbxapi.query(
:method => _method,
:params => _params)
end
def api_hosts_get(_hostname = '', _templateid = '')
_desc = Hash.new()
_desc.store('filter', {'host' => _hostname}) if !_hostname.empty?
_desc.store('templated_hosts', true)
_desc.store('selectApplications', 'extend')
_desc.store('selectMacros', 'extend')
_desc.store('templateids', _templateid) if !_templateid.empty?
_desc.store('output', ['hostid', 'host'])
return api_call('host.get', _desc)
end
def api_host_get(_hostname = '', _templateid = '')
return api_hosts_get(_hostname, _templateid).first
end
def api_item_get(_hostid = 0, _itemkey = '')
_data = api_call('item.get',
{'hostids' => _hostid,
'filter' => { 'type' => ZABBIX_ITEM_TYPE_TRAPPER,
'status' => ZABBIX_ITEM_STATUS_ACTIVE,
'key_' => _itemkey }})
return (_data.nil? || _data.empty?) ? nil : _data.first
end
def api_item_create_update(_info = Hash.new())
_command = (_info.has_key?(:itemid) ? 'item.update' : 'item.create')
_units = (_info.has_key?(:itemunit) ? _info[:itemunit] : '')
_desc = Hash.new()
_desc.store('itemid', _info[:itemid]) if _info.has_key?(:itemid)
_desc.store('hostid', _info[:hostid])
_desc.store('applications', [_info[:appid]])
_desc.store('key_', _info[:itemkey])
_desc.store('name', _info[:name])
_desc.store('units', _units)
_desc.store('delay', ZABBIX_ITEM_DELAY)
_desc.store('type', ZABBIX_ITEM_TYPE_TRAPPER)
_desc.store('value_type', ZABBIX_ITEM_VALUE_TYPE_FLOAT)
_desc.store('history', ZABBIX_ITEM_HISTORY)
_desc.store('status', ZABBIX_ITEM_STATUS_ACTIVE)
_desc.store('trends', ZABBIX_ITEM_TRENDS)
api_call(_command, _desc)
return !_info.has_key?(:itemid)
end
def get_local_time(_timestr = '')
return Time.parse(_timestr).getlocal
end
def translate_local_unixtime(_timestr = '')
return get_local_time(_timestr).to_i
end
def get_aws_hostinfo(_hostname = '')
raise '[ERROR] can not determine hostname.' if _hostname.nil? || _hostname.empty?
_ec2_host = api_host_get(_hostname)
@EC2Info[_hostname].store(:hostid, _ec2_host['hostid'])
_ec2_host['applications'].each {|_appinfo|
@EC2Info[_hostname].store(:appid, _appinfo['applicationid']) if _appinfo['name'] == ZABBIX_ITEM_APPNAME
}
_ec2_host['macros'].each {|_macinfo|
@EC2Info[_hostname].store(:aws_host, _macinfo['value']) if _macinfo['macro'] == MACROKEY_AWS_ACCOUNT_HOST
@EC2Info[_hostname].store(:instname, _macinfo['value']) if _macinfo['macro'] == MACROKEY_EC2_INSTANCE_NAME
}
return SUCCESS
end
def get_aws_keyinfo(_hostname = '')
raise '[ERROR] can not determine EC2 hostname.' if _hostname.nil? || _hostname.empty?
raise '[ERROR] can not determine AWS hostname.' if ! @EC2Info[_hostname].has_key?(:aws_host)
_aws_host = api_host_get(@EC2Info[_hostname][:aws_host])
_aws_host['macros'].each {|_macinfo|
@EC2Info[_hostname].store(:aws_akey, _macinfo['value']) if _macinfo['macro'] == MACROKEY_AWS_ACCESS_KEY
@EC2Info[_hostname].store(:aws_skey, _macinfo['value']) if _macinfo['macro'] == MACROKEY_AWS_SECRET_KEY
}
return SUCCESS
end
def call_sender(_str_stdin = '')
return if _str_stdin.nil? || _str_stdin.empty?
_cmdline = ZABBIX_SENDER_PATH.dup
_cmdline << ' --zabbix-server '
_cmdline << get_server_ip()
_cmdline << ' --port '
_cmdline << get_server_port()
_cmdline << ' --with-timestamps'
_cmdline << ' --input-file -'
_status, _str_stdout, _str_stderr = execcmd(_cmdline, _str_stdin)
raise '[ERROR] Failed to execute Zabbix Sender.' if _status.to_i != 0
end
def initialize
@EC2Info = Hash.new()
end
def run(_logger = nil, _args = Array.new())
raise '[ERROR] can not initialize logger.' if _logger.nil?
raise '[ERROR] can not determine host.' if _args.nil?
_args.uniq!
_result = SUCCESS
begin
@logger = _logger
_infos = Array.new()
_created = false
api_login()
_template = api_host_get(ZABBIX_TEMPLATE_NAME)
raise '[ERROR] can not determine template host.' if _template.nil? || _template.empty?
@EC2Info.store(:templateid, _template['hostid'])
_zbxhosts = api_hosts_get('', @EC2Info[:templateid])
_zbxhosts.each {|_zbxhost|
_host = _zbxhost['host']
next if (!_args.empty?) && (!_args.include?(_host))
@EC2Info.store(_host, Hash.new())
get_aws_hostinfo(_host)
next if ! @EC2Info[_host].has_key?(:hostid)
next if ! @EC2Info[_host].has_key?(:appid)
next if ! @EC2Info[_host].has_key?(:aws_host)
next if ! @EC2Info[_host].has_key?(:instname)
get_aws_keyinfo(_host)
next if ! @EC2Info[_host].has_key?(:aws_akey)
next if ! @EC2Info[_host].has_key?(:aws_skey)
_ec2 = RightAws::Ec2.new(@EC2Info[_host][:aws_akey], @EC2Info[_host][:aws_skey], {:logger => @logger, :server => AWS_EC2_SERVER})
_instances = _ec2.describe_instances
_instances.each {|_instance|
_equals = false
_instance[:tags].each {|_tkey, _tval|
_equals = true if ((_tkey == TAGKEY_NAME) && (_tval == @EC2Info[_host][:instname]))
}
next if ! _equals
@EC2Info[_host].store(:instid, _instance[:aws_instance_id])
@EC2Info[_host].store(:devices, Hash.new())
_instance[:block_device_mappings].each {|_device|
@EC2Info[_host][:devices].store(_device[:ebs_volume_id], _device[:device_name].gsub(/[0-9]/, ''))
}
}
_options = Hash.new()
_options.store(:start_time, GETDATA_STARTTIME)
_options.store(:end_time, GETDATA_ENDTIME)
_options.store(:period, GETDATA_PERIOD)
_acw = RightAws::AcwInterface.new(@EC2Info[_host][:aws_akey], @EC2Info[_host][:aws_skey], {:logger => @logger, :server => AWS_ACW_SERVER})
_metrics = _acw.list_metrics
next if _metrics.nil? || _metrics.empty?
@EC2Info[_host].store(:metrics, Array.new())
_metrics.each {|_metric|
next if _metric[:namespace].nil? || _metric[:namespace].empty?
next if _metric[:dimentions].nil? || _metric[:dimentions].empty?
next if _metric[:measure_name].nil? || _metric[:measure_name].empty?
case _metric[:namespace]
when GETDATA_EC2_NAMESPACE then
next if !_metric[:dimentions].has_key?('InstanceId')
next if (_metric[:dimentions]['InstanceId'] != @EC2Info[_host][:instid])
_options.store(:namespace, GETDATA_EC2_NAMESPACE)
_options.store(:dimentions, Hash.new())
_options[:dimentions].store(:InstanceId, _metric[:dimentions]['InstanceId'])
when GETDATA_EBS_NAMESPACE then
next if !_metric[:dimentions].has_key?('VolumeId')
next if ! @EC2Info[_host][:devices].has_key?(_metric[:dimentions]['VolumeId'])
_options.store(:namespace, GETDATA_EBS_NAMESPACE)
_options.store(:dimentions, Hash.new())
_options[:dimentions].store(:VolumeId, _metric[:dimentions]['VolumeId'])
else
next
end
_key_sym = GETDATA_STAT_AVG.downcase.to_sym
_options.delete(:statistics) if _options.has_key?(:statistics)
if GETDATA_MEASURES.has_key?(_metric[:measure_name]) then
if GETDATA_MEASURES[_metric[:measure_name]].has_key?(:statistics) then
_key_sym = GETDATA_MEASURES[_metric[:measure_name]][:statistics].downcase.to_sym
_options.store(:statistics, GETDATA_MEASURES[_metric[:measure_name]][:statistics])
end
end
_options.store(:measure_name, _metric[:measure_name])
_metadata = _acw.get_metric_statistics(_options)
next if _metadata[:datapoints].nil? || _metadata[:datapoints].empty?
_info = Hash.new()
_info.store(:label, _metadata[:label])
_info.store(:unit, _metadata[:datapoints].first[:unit])
_info.store(:host, _host)
_info.store(:instance, @EC2Info[_host][:instid])
_info.store(:hostid, @EC2Info[_host][:hostid])
_info.store(:appid, @EC2Info[_host][:appid])
_info.store(:itemunit, GETDATA_UNIT_MAP[_info[:unit]]) if GETDATA_UNIT_MAP.has_key?(_info[:unit])
case _metric[:namespace]
when GETDATA_EC2_NAMESPACE then
_info.store(:itemkey, ZABBIX_ITEM_KEY_FORMAT_EC2%[_info[:label]])
if GETDATA_MEASURES.has_key?(_info[:label]) then
_info.store(:name, GETDATA_MEASURES[_info[:label]][:name])
_info.store(:itemunit, GETDATA_MEASURES[_info[:label]][:unit]) if GETDATA_MEASURES[_info[:label]].has_key?(:unit)
else
_info.store(:name, GETDATA_NAME_UNKNOWN_EC2%[_info[:label]])
end
when GETDATA_EBS_NAMESPACE then
_info.store(:volumeid, _metric[:dimentions]['VolumeId'])
_info.store(:volpath, @EC2Info[_host][:devices][_info[:volumeid]])
_info.store(:itemkey, ZABBIX_ITEM_KEY_FORMAT_EBS%[_info[:label], _info[:volpath]])
if GETDATA_MEASURES.has_key?(_info[:label]) then
_info.store(:name, GETDATA_MEASURES[_info[:label]][:name]%[_info[:volpath]])
_info.store(:itemunit, GETDATA_MEASURES[_info[:label]][:unit]) if GETDATA_MEASURES[_info[:label]].has_key?(:unit)
else
_info.store(:name, GETDATA_NAME_UNKNOWN_EBS%[_info[:label], _info[:volpath]])
end
end
_info.store(:values, Array.new())
_metadata[:datapoints].each {|_point|
_value = Hash.new()
_value.store(:clock, translate_local_unixtime(_point[:timestamp]))
if GETDATA_MEASURES.has_key?(_info[:label]) then
if GETDATA_MEASURES[_info[:label]].has_key?(:divide) && (GETDATA_MEASURES[_info[:label]][:divide] != 0) then
_value.store(:value, _point[_key_sym] / GETDATA_MEASURES[_info[:label]][:divide])
else
_value.store(:value, _point[_key_sym])
end
else
_value.store(:value, _point[_key_sym])
end
_info[:values].push(_value)
}
_info[:values].sort! {|_a, _b| _a[:clock] <=> _b[:clock] }
_item = api_item_get(_info[:hostid], _info[:itemkey])
_info.store(:itemid, _item['itemid']) if ! _item.nil?
_info.store(:created, api_item_create_update(_info))
_created = true if _info[:created]
_infos.push(_info)
}
}
sleep(get_server_conf_freq() * 2) if _created
_sendstr = ''
_infos.each {|_info|
_info[:values].each {|_value|
_sendstr << [_info[:host], _info[:itemkey], _value[:clock], sprintf('%.4f', _value[:value])].join(' ')
_sendstr << "\n"
}
}
call_sender(_sendstr) if !_sendstr.empty?
rescue => err
@logger.error(err.message)
_result = FAILURE
raise
end
return _result
end
end
# main #################################################################
_result = SUCCESS
begin
begin
logger = Logger.new(LOGFILE)
logger.level = Logger::WARN
rescue
begin
Dir::mkdir(LOGDIR)
retry
rescue
raise "[ERROR] Can not open '#{LOGFILE}'."
end
end
# ARGV[0] : hostname
# empty value : all host mode
# 'hostname' : specified host only mode
_obj = AWSEC2Info.new
_result = _obj.run(logger, ARGV)
rescue => err
warn "[ERROR] catch error in #{__FILE__}"
warn err.message
warn err.backtrace
_result = FAILURE
end
puts _result
exit(_result)
# end of file
書いてみて‥ - 年の瀬所感
いやはや、書きかけてから、(ノ∀`)アチャーな感じでした(笑
気になった部分を直し直して書いているうちに、どんどんスクリプトが大掛かりなモノになっちまって‥。
スクリプト、結構ややこしいかもしれません。
不明な点や不審な点、ありましたらどんどん声を掛けていただければと思います。
クリスマスももうすぐとなって、Advent Calendarも、かなり大詰めです。
今年も1年間、Zabbix含め、たくさんのソフトに囲まれて過ごすことができました。
また来年も、楽しいモノを、楽しく使える1年になることを祈りつつ‥
明日のAdvent Calendarへバトンを繋ぎます。よろしくお願いいたします。