【ちょっとお遊び】旅客機のフライト情報をZabbixで監視(ハイジャック検知も!?) その① -緯度・経度・飛行方向・スコークコードを監視-

Advent Calendarの埋まり具合がやばそうなので、ちょっとお試しで旅客機のフライト情報を監視してみます。
今回の記事ではひとまずフライト情報を取得して、監視結果として登録するところまで。次の記事でZabbixのマップに飛行状況を動的にプロットするところまでチャレンジしてみます。Zabbixの機能をいろいろと活用しているのでZabbixの機能のおさらいにはなるかも。

# 試したこと

* flightrader24から特定の便名のフライト情報をPythonスクリプトで収集
* 収集処理をZabbixに登録し、定期実行
* 依存アイテムを活用し、1回のスクリプト実行で、緯度・経度・飛行方向・スコークコードの3監視結果を一括収集
* ホストインベントリの自動登録機能を利用し、緯度・経度・飛行方向をインベントリとして管理
* スコークコードの異常(7500:ハイジャック、7600:通信機故障、7700:緊急事態)を検知

# flightrader24から特定の便名のフライト情報をPythonスクリプトで収集

飛行情報は、flightrader24というサイトから収集させていただきました。

[flightrader24](https://www.flightradar24.com/)

このサイトでは、フライト情報をライブ配信しており、以下のURLでjson形式で取り出すことができます。

```
http://data-live.flightradar24.com/zones/fcgi/feed.js?adsb=1&mlat=1&faa=1&flarm=1&estimated=1&air=1&gnd=1&vehicles=1&gliders=1&array=1
```

```:response
{"full_count":12459,"version":5,"copyright":"The contents of this file and all derived data are the property of Flightradar24 AB for use exclusively by its products and applications. Using, modifying or redistributing the data without the prior written permission of Flightradar24 AB is not allowed and may result in prosecutions.","aircraft":[["1eafe981","3443CE",28.9494,-13.6094,39,0,32,"7777","F-GCRR5","GRND","NAVAIDS",1543875834,"","","",1,0,"7777XLAN",0]
,["1eafe9eb","1130C6",59.7814,30.2791,318,25,0,"0000","F-ULSS1","","",1543875833,"LED","","",1,0,"PARROT03",0]
,["1eafed7d","456002",42.6897,23.4134,293,0,0,"0000","F-LBSF4","","",1543875834,"","","",1,0,"DGV3R",0]
,["1eafeeb5","4A7C24",44.5009,26.0672,271,0,0,"7777","F-LRBS4","","",1543875833,"BBU","","",1,0,"",0]
,["1eafef31","71D423",37.4558,126.4807,180,0,0,"0000","F-RKSI1","","",1543875831,"ICN","","",1,0,"",0]
・・・略
```

このaircraftの情報が各旅客機毎のフライト情報になります。

詳細はこちらの記事が参考になります。

* https://www.mk-mode.com/octopress/2016/02/08/fr24-getting-flight-info/


# 収集処理をZabbixに登録

このURLからの情報収集をPythonスクリプト(Python3.6)で実装します。

```python
#!/usr/bin/python

import urllib.request
import json
import sys

class Flights:
    def __init__(self, flight_number):
        self.flight_number = flight_number
        self.latitude = 0.0
        self.longitude = 0.0
        self.direction = 0
        self.squawk = "0000"

    def get_info(self):
        url = "http://data-live.flightradar24.com/zones/fcgi/feed.js?adsb=1&mlat=1&faa=1&flarm=1&estimated=1&air=1&gnd=1&vehicles=1&gliders=1&array=1"
        headers = {"User-Agent": "curl/7.54.0"}
        response_body = {}
        request = urllib.request.Request(url=url, headers=headers)
        try:
            with urllib.request.urlopen(request) as response:
               response_body = json.loads(response.read().decode("utf-8"))
        except Exception as e:
            print(e)

        for flight_info in response_body['aircraft']:
            if flight_info[17] == self.flight_number:
                self.latitude = flight_info[2]
                self.longitude = flight_info[3]
                self.squawk = flight_info[7]
                direction = flight_info[4]
                if (direction >= 0 and direction < 23) or (direction >= 338):
                    self.direction = 1
                elif (direction >= 23 and direction < 68):
                    self.direction = 2
                elif (direction >= 68 and direction < 113):
                    self.direction = 3
                elif (direction >= 113 and direction < 158):
                    self.direction = 4
                elif (direction >= 158 and direction < 203):
                    self.direction = 5
                elif (direction >= 203 and direction < 248):
                    self.direction = 6
                elif (direction >= 248 and direction < 293):
                    self.direction = 7
                elif (direction >= 293 and direction < 338):
                    self.direction = 8

if __name__ == "__main__":
    args = sys.argv
    flight_number = ""
    if len(args) > 1:
        flight_number = sys.argv[1]
    else:
        flight_number = input("Please input flight number(IATA code): ")
    flight = Flights(flight_number)
    flight.get_info()
    response = {
            "flignt_number": flight_number,
            "latitude": flight.latitude,
            "longitude": flight.longitude,
            "direction": flight.direction,
            "squawk": flight.squawk
    }
    print(json.dumps(response))
```

引数として便名(IATAコード)を入力して実行すると以下のような形で緯度・経度・飛行方向のJSONを返します。

```json
{"flignt_number": "JAL3", "latitude": 68.267, "longitude": -134.8682, "direction": 7, "squawk": "0000"}
```

directionは元データとしてはdegreeで取得できますが、このあとのマップ作成のために、360°を8等分した方向フラグに変換しています。(上:1、右上:2、右:3、右下:4、下:5、左下:6、左:7、左上:8)

## ホスト登録

まず、フライトと紐づくホストを1個登録します。

![host_config.png](https://qiita-image-store.s3.amazonaws.com/0/45851/4eb472f3-25aa-0569-6832-ae3cd54289c9.png)

便名をホスト名と一致させて、後のアイテムの登録時に便名を引き渡せるようにしておきます。
また、インベントリは自動設定にしておきます。

![hostinventory_config.png](https://qiita-image-store.s3.amazonaws.com/0/45851/89dc293f-21a7-6c0f-4b67-e7f625818ec8.png)

## アイテム登録

先程作成したスクリプトを、Zabbixサーバ内の外部チェックスクリプト配置先に置き、Zabbix実行ユーザが実行できる権限を付与しておきます。
外部チェックアイテムを以下のように登録します。

![item_config1.png](https://qiita-image-store.s3.amazonaws.com/0/45851/847ad0af-3b41-569e-2f76-118bef588b81.png)

すると、1分間隔で以下のようなJSONの文字列が収集されます。

![history_1.png](https://qiita-image-store.s3.amazonaws.com/0/45851/e2beff02-610d-af72-b421-257bf2177b92.png)


# 依存アイテムを登録

次に、依存アイテムを登録していきます。先程の監視結果だと、1監視アイテムにJSON文字列が登録されるのみなので、JSONの中身を項目毎に分離して各監視アイテムの結果として格納していきます。

## 緯度(latitude)の依存アイテム

![latitude_item1.png](https://qiita-image-store.s3.amazonaws.com/0/45851/14908f6a-604c-d08b-181d-9ff9b1f8d68d.png)

アイテムタイプを「依存アイテム」に設定し、マスターアイテムとして先程登録した外部チェックアイテムを指定します。
ホストインベントリフィールドの自動設定として「緯度」を選択しておきます。

さらに、保存前処理として以下のようにJSONパスのlatitudeの項目を取得するようにします。

![latitude_item2.png](https://qiita-image-store.s3.amazonaws.com/0/45851/6ab31547-b131-4257-d01a-98723af19a41.png)

## 経度(longitude)の依存アイテム

こちらも同様に依存アイテムを登録します。
詳細は割愛します。

## 飛行方向(direction)の依存アイテム

設定は同じように行います。
ホストインベントリフィールドの自動設定として、飛行方向に直接合致する項目はないので、「場所」の項目に登録しておきます。

![item_direction.png](https://qiita-image-store.s3.amazonaws.com/0/45851/5fc821a3-9dd1-5266-fc15-331426c14c15.png)

## スコークコード(squawk)の依存アイテム

こちらも同様に設定します。データ型は文字列を指定しておきます。

これらの設定でそれぞれこんな感じで個々の値を個別の監視アイテムの結果として取り出せます。

![history_2.png](https://qiita-image-store.s3.amazonaws.com/0/45851/882905f6-4f64-bad9-1203-0d27a32ce359.png)

# スコークコードにトリガーを設定

7500ハイジャック！、7600通信機器故障！、7700緊急事態！このあたりのコードが発行されるとあぶない状況なので、トリガーを設定します。

スコークコードは文字列型で登録しているので、正規表現で上記コードの発生をチェックするトリガー(regexp)を指定します。

![trigger_config.png](https://qiita-image-store.s3.amazonaws.com/0/45851/7788217b-4a0c-5402-2449-c3ac61504d01.png)

# まとめ

依存アイテムを使うと1回のデータ取得処理でも簡単に複数アイテムの監視ができます。次回はこの緯度・経度・飛行方向の情報をうまくZabbixのマップに適用して、見れるようにしてみるのでお楽しみに。