ZabbixでAWS ALBの監視をする

## 前提

- zabbix3.4
- ALBに紐づいているTargetGroupのヘルスチェックが通っているかを確認したい

## 構成

AWS ALBは`HealthyHostCount`という情報をCloud watchに保存しています。
Cloud Watchからの情報はAPIを経由して取得する必要があります。
なので今回は
1. external scriptsに`APIを打ってzabbix senderでcloud watchの情報を送るスクリプト`を配置する
2. zabbix server側でzabbix trapperを使ってzabbix senderで送られた情報をキャッチする
3. そのitemでアラートを出すようにする

zabbix trapper等の情報は以下が詳しいです。
https://www.zabbix.com/documentation/3.0/manual/config/items/itemtypes/trapper

## zabbix senderでcloud watchの情報を送る

基本pythonで以下の感じで実装をしました。

1. シェルスクリプトでpythonを叩く
2. zabbixで設定されているHost情報から target groupの情報を抜く(ZabbixのMacroとかにTarget情報をいれてもOK)
3. cloud watchから情報を抜く(今回はメトリクス情報もAPIから毎回引っこ抜いています。)
4. `alb.metrics_name`という形に生成
5. subprocessでzabbix senderを実行。

aws_monitoring.py

```py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys, subprocess, datetime
from boto3.session import Session

def _exponential_backoff(func, trial_count=5):
  last_raised = None
  for x in xrange(trial_count):
    try:
      results = func()
      return results
    except Exception as e:
      print('[{0}/{1}] Exponential Backoff Failed'.format(x + 1, trial_count))
      last_raised = e
      time.sleep(2 ** x)
  raise last_raised

def get_client(akey, sakey, region, SERVICE='ec2'):
  client = Session(
    aws_access_key_id     = akey,
    aws_secret_access_key = sakey,
    region_name           = region
  ).client(SERVICE)
  return client

def send_data(hostname, key, value):
  cmd =['zabbix_sender', '-z','127.0.0.1', '-s', str(hostname), '-k', str(key), '-o', str(value), '-vv']
  result = subprocess.call( cmd, shell=False )

def get_cw_columnlist(client, TARGET_GROUP):
  metricslist = _exponential_backoff(lambda: client.list_metrics(
      Namespace  = 'AWS/ApplicationELB',
      Dimensions = [{
        'Name'  : 'TargetGroup',
        'Value' : TARGET_GROUP,
      }],
    )
  ) 
  columnlist = [] 
  for metrics in metricslist['Metrics']:
    columnlist.append(metrics['MetricName'])
  
  columnlist_unique = list(set(columnlist))

  return columnlist_unique

def send_items(cw_client, alb_client, ELB_NAME):
  alb_info = _exponential_backoff(lambda: alb_client.describe_target_groups(Names=[ELB_NAME]))
  TARGET_GROUP = alb_info['TargetGroups'][0]['TargetGroupArn'].rsplit(':',1)[1] 
  for column in get_cw_columnlist(cw_client, TARGET_GROUP):
    
    unit = _select_unit(column)

    cw_values = _exponential_backoff(lambda: cw_client.get_metric_statistics(
        Namespace  = 'AWS/ApplicationELB',
        MetricName = column,
        Dimensions = [{
          'Name'  : 'TargetGroup',
          'Value' : TARGET_GROUP,
        }],
        StartTime  = get_nowtime_utc() - datetime.timedelta(minutes=9),
        EndTime    = get_nowtime_utc(),
        Period     = 300,
        Statistics = [unit],
      )
    )['Datapoints']
    
    key   = 'alb.' + column 
    value = 0

    if cw_values != []:
      value = sorted(cw_values,key=lambda x:x['Timestamp'],reverse=True)[0][unit]
    print(key, value)
    send_data(ELB_NAME, key, value)

def _select_unit(column):
  unitlist = {
    'HTTPCode_Backend_2XX':'Sum',
    'HTTPCode_Backend_4XX':'Sum',
    'HTTPCode_Backend_5XX':'Sum',
    'RequestCount':'Sum',
    'HealthyHostCount':'Maximum',
    'UnhealthyHostCount':'Maximum',
    'TargetResponseTime': 'Average',
    'RequestCountPerTarget': 'Sum'
  }
  if column not in unitlist:
    return 'Average'
  else:
    return unitlist[column]

if __name__ == '__main__':
  main()

def main():
  AKEY     = sys.argv[1]
  SAKEY    = sys.argv[2]
  HOSTNAME = sys.argv[3]
  REGION   = 'ap-northeast-1'
  ELB_NAME = HOSTNAME
  cw_client = get_client(AKEY, SAKEY, REGION, SERVICE='cloudwatch')
  alb_client = get_client(AKEY, SAKEY, REGION, SERVICE='elbv2')
  send_items(cw_client, alb_client, ELB_NAME)
```

aws_monitoring.sh (アクセスキーはインスタンスプロファイル/credential等使用していれば不必要です)

```bash
AWS_KEY=hoge
AWS_SAKEY=fuga
python aws_monitoring.py $AWS_KEY $AWS_SAKEY $1
```
## senderするスクリプトを定期実行する

定期実行するには
- cronとして仕込む 
- zabbix itemの`External check`で定期実行させる。

今回は`External check`を使って実装します（以下が詳しいです）

https://www.zabbix.com/documentation/3.4/manual/config/items/itemtypes/external
https://www.zabbix.com/documentation/3.4/manual/appendix/command_execution

1. `zabbix_server.cfg`の`ExternalScripts`に上記のスクリプトを配置します
2. zabbix itemを設定します
  - type: External check
  - key: aws_monitoring.sh[{HOST.HOST} (ここの引数は適宜変えてください)
  - Update interval: 300 (監視間隔なので適宜変えてください)

## zabbix trapperを配置する

1. zabbix itemを設定します (HealthyHostCountの場合)
  - type: Zabbix Trapper
  - key: alb.HealthyHostCount
  - Type of information: Numeric(float)

配置すれば終了。あとはLatest Dataでデータが反映されているか確認しましょう。

## 結論

この辺りを応用するとcloud watchの情報を使ってzabbixで監視ができる。
cloud watchのAPIは有料なので長い期間を連続して取得しないようにする。
https://aws.amazon.com/jp/cloudwatch/pricing/