AWSのEC2インスタンスの監視をzabbixへ統合監視するために、zabbixのLLD（ローディスカバリー）機能を使ってみた！

LLDとは？
>ローレベルディスカバリにより、コンピューター上の種々の要素に対してアイテム、トリガー、グラフを自動的に作成できます。例えば、Zabbixは使用しているマシン上のファイルシステムまたはネットワークインターフェースの監視を自動的に開始できます。そのために、各ファイルシステムまたはネットワークインターフェースに対して手動でアイテムを作成する必要はありません。さらに、定期的に実施されるディスカバリの実際の結果に基づいて不要な要素を自動的に削除するように、Zabbixを設定することができます。
[zabbix公式ページ](https://www.zabbix.com/documentation/2.2/jp/manual/discovery/low_level_discovery)

# 目的
今回の目的は、AWSのEC2のリソース情報の監視をzabbixに連携することとします。
その中でLLD機能を使って、アイテムとトリガーの自動作成を実施してみます。

# 手順
以下の手順で実施していきたいと思います。

1. 監視対象のEC2インスタンスのメトリクスの確認。
1. zabbix上でのホストの作成
1. LLDに必要なマクロ情報取得及びメトリクス値取得のためのスクリプトの配置
1. LLDの作成
1. 監視連携確認

# 環境
・監視対象のEC2

```:監視対象
[ec2-user@xxxx ~]$ cat /etc/system-release
Amazon Linux release 2 (Karoo)
```
・zabbixサーバ用のEC2

```:zabbixサーバー
[ec2-user@zb-server ~]$ cat /etc/system-release
Amazon Linux release 2 (Karoo)
[ec2-user@zb-server ~]$ zabbix_server -V
zabbix_server (Zabbix) 4.2.8
```


# 前提
- zabbixサーバにzabbixのインストール
- 監視対象EC2にメモリやディスクのメトリクスを作成していること
 - メモリとディスクのメトリクス追加はLLDの挙動を調べる上で必須ではないと思いますが、今回監視対象としてる私のEC2インスタンス（学習用）にたまたま作成していたので、前提とさせていただきました。こちらの[AWS公式ページ](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/create-cloudwatch-agent-configuration-file-wizard.html)からウィザードを使用したメモリやディスクのメトリクス作成ができますので、ご興味のある方はぜひ参考にしていただければと思います。

# 監視対象サーバに追加されているメトリクスの確認
監視対象サーバに関連するメトリクスをCloudwatchより確認します。私の環境では以下のメトリクスが追加されておりました。
以下の結果から１７個のメトリクスが現在作成されていることがわかりました。これらのメトリクスをzabbixへ連携していきます。

```:Macから実施しました。
~ ❯❯❯ aws cloudwatch list-metrics \   
　　　　　　--dimensions Name=InstanceId,Value=i-xxxxxxxx \
　　　　　　|jq '.Metrics[].MetricName' |awk '{print NR, $0}'
1 "CPUSurplusCreditBalance"
2 "MetadataNoToken"
3 "NetworkPacketsOut"
4 "DiskReadOps"
5 "CPUCreditBalance"
6 "StatusCheckFailed"
7 "NetworkOut"
8 "StatusCheckFailed_Instance"
9 "NetworkIn"
10 "CPUSurplusCreditsCharged"
11 "CPUUtilization"
12 "StatusCheckFailed_System"
13 "DiskWriteBytes"
14 "CPUCreditUsage"
15 "DiskWriteOps"
16 "NetworkPacketsIn"
17 "DiskReadBytes"
```
### コマンドの解説
・`aws cloudwatch list-metrics --dimensions Name=InstanceId,Value=i-xxxxxxxx`
これはvalueで指定したEC2インスタンスのメトリクス一覧を取得するコマンドです。このコマンドを実行するためにはaws cliをインストールしなければいけません。
[awc cliインストール公式ページ](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html)
・`jq '.Metrics[].MetricName'`
`aws cloudwatch list-metrics`により出力されたJSON形式のデータの中から、メトリクス名のみを抜き出しています。
jqをインストールするためには以下を実行してください(mac)。 jqはコマンドライン上でJSONデータを整形できるなどの機能があります。

```
brew install jq 
```
・`awk '{print NR, $0}'`
これはパイプにより渡された文字の前に連番を降っています。
NR（連番） ＄０(パイプにより渡された文字列)
の形式で出力させています。

# zabbix上でのホストの作成
zabbixの管理画面を開き、監視対象のホストを作成します。
zabbix管理画面を開いたら、上タブから”設定”>"ホスト">"ホスト作成"を選択すれば以下のような新規ホスト作成画面になります。
入力項目に以下のように入力します。
<img width="1033" alt="スクリーンショット 2020-06-27 12.57.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/444064/f7b1d008-8256-faef-a122-a20918d50eef.png">
ホスト名：EC2インスタンスIDを入力してください（後ほど出てくるスクリプトと関連するため）
表示名：任意の文字を入力してください。
グループ：任意のグループ名を選択してください。
そのほかはデフォルトのままで追加ボタンを押下します。
以下のように新規に監視対象のEC2インスタンスのホストが作成されたことがわかります。
<img width="1430" alt="スクリーンショット 2020-06-27 13.01.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/444064/ab1526e3-2f4b-dbc5-cdf8-d63cfd364df4.png">

# スクリプトの配置
LLD機能により使用するスクリプトを配置します。
スクリプトは[こちら](https://github.com/tech-sketch/zabbix_aws_template/blob/master/scripts/cloudwatch_zabbix.py)を使わせていただきます。pythonで書かれたコードで、Pythonの公式AWS SDKであるboto3を使用しています。
このスクリプトには二つの役割があります。

1. スクリプト実行により必要なメトリクス情報を取得し、そのメトリクス情報を元にアイテムを自動作成していきます。
2. メトリクス値を取得し、自動的に作成されたアイテムに対して送信する。

それぞれの役割の詳しい説明は後述します。

まず、Python3とPythonの公式AWS SDKであるboto3をinstallします。

```:zabbix-server
[ec2-user@zb-server ~]$ sudo yum install python3 -y
[ec2-user@zb-server ~]$ python3 -V
Python 3.7.6
[ec2-user@zb-server ~]$ pip3 -V
pip 9.0.3 from /usr/lib/python3.7/site-packages (python 3.7)
# ↑pip3を使ってboto3をインストールします
[ec2-user@zb-server ~]$ sudo pip3 install boto3
[ec2-user@zb-server ~]$ pip3 freeze |grep boto3
boto3==1.14.12
# ↑boto3がインストールされたことを確認
```

スクリプトの配置先は、zabbix_server.confに記載されています。
ExternalScriptsは外部チェックのタイプであるアイテムが実行するスクリプトを配置する場所のことです。
デフォルトでは、/usr/lib/zabbix/externalscriptsとなっております。

```:zabbix-server
[ec2-user@zb-server ~]$ sudo grep ExternalScripts /etc/zabbix/zabbix_server.conf
### Option: ExternalScripts
# ExternalScripts=${datadir}/zabbix/externalscripts
ExternalScripts=/usr/lib/zabbix/externalscripts
```


/usr/lib/zabbix/externalscriptsにスクリプトを配置して、必要な権限を付与します。
もちろん、zabbixがスクリプトを実行するため、以下の権限に変更します。

```
[ec2-user@zb-server ~]$ cd /usr/lib/zabbix/externalscripts/
[ec2-user@zb-server externalscripts]$ sudo vim cloudwatch_zabbix.py
（スクリプトの内容をコピペ)
[ec2-user@zb-server externalscripts]$ sudo chmod 755 cloudwatch_zabbix.py 
[ec2-user@zb-server externalscripts]$ sudo chown zabbix:zabbix cloudwatch_zabbix.py 
[ec2-user@zb-server externalscripts]$ ls -l cloudwatch_zabbix.py 
-rwxr-xr-x 1 zabbix zabbix 11147  6月 27 15:58 cloudwatch_zabbix.py
```

スクリプトをpython3で実行するために編集。

```:zabbix-server
[ec2-user@zb-server externalscripts]$ sudo sed -i s/python/python3/ cloudwatch_zabbix.py
[ec2-user@zb-server externalscripts]$ cat cloudwatch_zabbix.py |grep python
#!/bin/env python3
```

スクリプトが動作するかどうかの確認。-iにはインスタンスIDを入力する。

```:zabbix-server
[ec2-user@zb-server externalscripts]$ sudo -u zabbix ./cloudwatch_zabbix.py 'ec2' -i 'i-xxxxxxxxxxx' -r 'ap-northeast-1'
  File "./cloudwatch_zabbix.py", line 171
    print "Can't connect to zabbix server"
                                         ^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print("Can't connect to zabbix server")?
```
と思ったが、エラーが出てている。
スクリプト上で書かれている`print "Can't connect to zabbix server"`が問題とのこと。。
python3では、printの書き方について変更されたとのこと。
`print "hoge"`
↑これはpython2でしか使えない。
`ptint("hoge")`
↑これはpython3・python2の両方で使える。
なので、python3に合わせるためにスクリプト上のprintを`print("文字列")`になるように編集。

```:zabbix-server
[ec2-user@zb-server externalscripts]$ sudo sed -i -r "s/print ([^ \f\n\r\t]+)/print(\1)/g" cloudwatch_zabbix.py
[ec2-user@zb-server externalscripts]$ cat cloudwatch_zabbix.py |grep print
            print("Can't connect to zabbix server")
            print('Data sending failure')
        print(response[13:])
        print(json.dumps(lld_output_json))
```
そのほかにも、python3に対応していないコードがあったので、編集。

```:zabbix-server
[ec2-user@zb-server externalscripts]$ sudo sed -i -r "s/datapoint.has_key\((\"[a-zA-Z]+\")\)/\1 in datapoint/g" cloudwatch_zabbix.py
[ec2-user@zb-server externalscripts]$ grep "datapoint in" cloudwatch_zabbix.py
                for datapoint in stats["Datapoints"]:
        for datapoint in datapoints:
# ↑上のような出力が出ればOK！

[ec2-user@zb-server externalscripts]$ sudo sed -i "173i\        b1, b2 = \('<4sBQ'.encode\('utf-8'\), 'ZBXD'.encode\('utf-8'\))" \
   -e "s/'<4sBQ', 'ZBXD'/b1, b2/" \
   -e "175a\        send_data_string = send_data_string.encode('utf-8')" \
   -e "s/response += data/response += data.decode(encoding='utf-8')/" \
   cloudwatch_zabbix.py &&
   grep -e "enco" -e "struct.pack" cloudwatch_zabbix.py

        b1, b2 = ('<4sBQ'.encode('utf-8'), 'ZBXD'.encode('utf-8'))
        header = struct.pack(b1, b2, 1, len(send_data_string))
        send_data_string = send_data_string.encode('utf-8')
            response += data.decode(encoding='utf-8')
# ↑上のような出力が出ればOK！
```

再度、取得できるか確認。JSON形式のデータが出力されればOK！

```:zabbix-server
[ec2-user@zb-server externalscripts]$ sudo -u zabbix ./cloudwatch_zabbix.py 'ec2' -i 'i-xxxxxxx' -r 'ap-northeast-1'
{"data": [{"{#METRIC.NAME}": "DiskWriteBytes", "{#METRIC.UNIT}": "Bytes", "{#METRIC.NAMESPACE}": "AWS/EC2"}, # ・・・　データが続いていく。
```
はじめに確認した数のメトリクスを取得できているかの確認のために以下のコードを書き込む。

```:zabbix-server
[ec2-user@zb-server externalscripts]$ sudo sed -i "232i\        print(len(lld_output_json[\"data\"]))" cloudwatch_zabbix.py
# ↑　これは取得するメトリクスの数を数えるコードを挿入している
[ec2-user@zb-server externalscripts]$ sudo -u zabbix ./cloudwatch_zabbix.py 'ec2' -i 'i-xxxxxx' -r 'ap-northeast-1'
17
{"data": [{"{#METRIC.NAME}": "CPUSurplusCreditBalance", # ・・・　データが続いていく
```
cloudwatch_zabbix.pyを実行後、17と表示された。
問題なく、はじめに確認した数と一致していることを確認。
確認できたところで、挿入したコードを削除する

```:zabbix-server
[ec2-user@zb-server externalscripts]$ sudo sed -i 232d cloudwatch_zabbix.py
[ec2-user@zb-server externalscripts]$ cat cloudwatch_zabbix.py |grep print
            print("Can't connect to zabbix server")
            print('Data sending failure')
        print(response[13:])
        print(json.dumps(lld_output_json))
# ↑ここでprint(len(lld_output_json[\"data\"]))が表示されていなければ問題なく削除できている！
```

### 外部チェックとは？
zabbixのitemのタイプのことで、zabbix-serverに配置されたスクリプトを実行し、その結果を取り込む機能がある。
https://www.zabbix.com/documentation/2.2/jp/manual/config/items/itemtypes/external

# LLDの作成
スクリプトを配置することができたので、zabbix管理画面に戻り、LLDを作成する。
先ほど作成したホストの画面へ戻る。
<img width="1430" alt="スクリーンショット 2020-06-27 17.23.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/444064/738d0fa2-8e04-f6c8-3e69-392df055a992.png">

”ディスカバリールール”のタブを押下するとディスカバリールールの一覧画面へ遷移するので、右上のディスカバリールールの作成をクリックする。各項目に必要情報の入力。
<img width="1430" alt="スクリーンショット 2020-06-27 18.17.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/444064/13385798-02f4-9ed6-5d2a-7aa85c206bb4.png">
名前：任意の名前を入力
タイプ：外部チェック
キー：cloudwatch_zabbix.py[ec2,-i,{HOST.HOST},-r,ap-northeast-1]
-　 cloudwatch_zabbix.pyは実行するスクリプトのファイル名
-　 []内は、スクリプト実行時の引数を入力する。{HOST.HOST}はホストの名前のこと。デフォルトで使えるzabbixのマクロとなっている。
有効：チェックを外す。
その他は今回はデフォルトのままにする。

追加ボタンをクリックしてLLDを作成する。

ここで設定したLLDのアイテムによりcloudwatch_zabbix.pyが実行され、以下のようなJSON形式のデータが出力される。
"{#xxx.xxx}": "xxxxxxxx"の各データが続いて設定するアイテムプロトタイプのマクロとして利用される。
ここでは、アイテムーを自動追加するためのマクロを作成するアイテムを設定している。

```
{
  "data": [
    {
      "{#METRIC.NAME}": "CPUSurplusCreditBalance",
      "{#METRIC.UNIT}": "Count",
      "{#METRIC.NAMESPACE}": "AWS/EC2"
    },
    {
      "{#METRIC.NAME}": "MetadataNoToken",
      "{#METRIC.UNIT}": "Count",
      "{#METRIC.NAMESPACE}": "AWS/EC2"
    },
    {
      "{#METRIC.NAME}": "NetworkPacketsOut",
      "{#METRIC.UNIT}": "Count",
      "{#METRIC.NAMESPACE}": "AWS/EC2"
    },
```

追加ボタンをクリックすると、以下のような一覧画面に遷移する。
<img width="1430" alt="スクリーンショット 2020-06-27 17.52.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/444064/09cddd1c-f436-bd48-878d-026d099895bc.png">

# アイテムプロトタイプの作成
先ほど追加したec2-metricsのアイテムプロトタイプをクリックし、右上のアイテムプロトタイプの作成をクリックする。
先ほどのLLDにより出力されるマクロに対するアイテムを作成する。
以下のように入力する。
<img width="1430" alt="スクリーンショット 2020-06-27 19.30.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/444064/07f72c73-d24a-f4c9-cb66-906ef9119ef7.png">
名前：cloudwatch metrics {#METRIC.NAME}
タイプ：zabbixトラッパー
キー:cloudwatch.metric[{#METRIC.NAME}]
データ型：数値（浮動小数）
単位：{#METRIC.UNIT}
アプリケーションプロトタイプの作成：{#METRIC.NAMESPACE}
その他はデフォルトのままとする。

"追加"ボタンをクリックする。
ディスカバリリストの一覧のページへいき、"ec2-metrics"をクリック。
一番下の"有効"にチェックを入れ"更新"をクリック。
<img width="1430" alt="スクリーンショット 2020-06-27 19.43.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/444064/458313aa-112c-79e5-467d-2490d192e2f3.png">

すると、自動でアイテムが作成される。
ここでは、最初に作成したLLDのアイテムがスクリプトを実行し、JSONデータ（マクロ）を出力する。そのJSONデータ（マクロ）を使って、アイテムプロトタイプで作成したアイテムを雛形としてアイテムを作成している。
スクリプト内では、{#METRIC.NAME}・{#METRIC.NAMESPACE}・{#METRIC.UNIT}の値となるデータをCloudwatchから取得している。
<img width="1430" alt="スクリーンショット 2020-06-27 18.22.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/444064/e2ed4a4a-4578-856a-e3ac-a15e3cd6e3d6.png">

# データ送信用のアイテムを作成
今のままでは、メトリクスのアイテムが作成されただけで、肝心のメトリクスの値はまだない。
続いて、先ほど自動作成されたアイテムに対してメトリクスの値を送信するためのアイテムを追加する。

上のタブから"設定">"ホスト"をクリックし、ホスト一覧の中から、"ec2-instance[i-xxxxxxxxxx]"をクリックする。
ホストのタブの中から、"アイテム"をクリックし、右上の"アイテムの作成"をクリック。
以下の写真のように各項目を入力していく。
<img width="1430" alt="スクリーンショット 2020-06-27 18.26.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/444064/a9108c00-859e-17d5-da46-f5a923188062.png">
名前：Collect CloudWatch metric stats EC2
タイプ：外部チェック
キー：cloudwatch_zabbix.py[ec2,-i,{HOST.HOST},-r,ap-northeast-1,-m,True]
データ型：テキスト
アプリケーションの作成：collect_cloudwatch_metrics
その他はデフォルトのままで"追加"をクリック。

作成したCollect CloudWatch metric stats EC2のアイテムによりメトリクス値を収集し、zabbixへ値を送信する。
実際に送信されたのかを確認してみる。
　上のタブから"監視データ">"最新データ"をクリック。
<img width="1430" alt="スクリーンショット 2020-06-27 20.28.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/444064/1609b243-e214-97a0-5e19-f75e2af6ce24.png">
このようにそれぞれ自動作成されたアイテムに対して値が送信されていることがわかる！

Collect CloudWatch metric stats EC2のアイテムは以下のようになっている。送信に成功したログが取り込まれている。
<img width="1430" alt="スクリーンショット 2020-06-27 20.30.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/444064/6ce65e92-5d5b-8c44-9fa0-839c236aecf7.png">


# 参考
- AWSリソースのzabbix統合監視について：
https://aws.typepad.com/aws_partner_sa/2016/06/opsjaws-try-ops-with-zabbix-1.html