ようへいの日々精進XP

よかろうもん

Nagios のアラート通知をホスト単位で有効化, 無効化, ホストの情報を取得するコマンドラインツールを作った 〜 Kubernetes も触ってみたよ 〜

tl;dr

シェルスクリプトで作成してたものを, 諸般の事情から, 勉強がてら Go で書き直してみました. 記事のタイトル通り, Nagios のアラート通知を有効化, 無効化, ホストの情報 (Host State Information) を取得するだけの機能を持っています.

作ったもの

github.com

テレビでシン・ゴジラを見ながら作ったので...名前は godzilla っぽくしてみました.

自分の知る限りでは...

Nagios は他の監視ツールのように, REST API で監視や通知の状態を操作することはオフィシャルでは出来ないという認識でして, シェルスクリプト版を作成した際にも, 以下のブログ記事を参考にして curl で操作出来るように実装していました.

cloudpack.media

Nagios に用意されている「外部コマンド」を curl または HTTP クライアントで叩くことで, ブラウザ操作以外からの操作を実現しています. 今回は, Go 言語の net/http パッケージを利用しています.

何が大変だったか

Nagios の外部コマンドを Go 言語から叩く部分については, 特に難しいことはありませんでした. 今回は Nagios 自体に Basic 認証が設定されているホストを対象としているので, リクエストヘッダに認証情報を追加してあげることで, 通知の有効, 無効, ホストの情報を取得することは出来ました.

ところが, レスポンスはシンプルとは言え, JSON ではなく HTML で返却される為, HTML を解析する必要がありました...これが, 今回の鬼門で一番時間が掛かりました. HTML 解析には, 以下のように net/html を利用しています.

...
func ParseCheckHostStatus(targetHost string, body io.Reader) {
    z := html.NewTokenizer(body)
    keys := []string{}
    values := []string{}

    // 苦肉の HTML 解析...
    for z.Token().Data != "html" {
        tt := z.Next()
        if tt == html.StartTagToken {
            t := z.Token()
            var key string
            var value string
            if t.Data == "td" {
                inner1 := z.Next()
                if inner1 == html.TextToken {
                    text := ((string)(z.Text()))
                    if strings.TrimSpace(text) != "" {
                        key = strings.TrimSpace(text)
                        if strings.HasSuffix(key, ":") || strings.HasSuffix(key, "?") {
                            keys = append(keys, strings.Trim(key, ":?"))
                        } else {
                            values = append(values, strings.Trim(key, ":?"))
                        }
                    }
                }

                if inner1 == html.StartTagToken {
                    if z.Token().Data == "div" {
                        inner2 := z.Next()
                        if inner2 == html.TextToken {
                            text := (string)(z.Text())
                            if strings.TrimSpace(text) != "" {
                                value = strings.TrimSpace(text)
                                values = append(values, strings.Trim(value, ":?"))
                            }
                        }
                    }
                }
            }
        }
    }

    m := map[string]string{}
    for i := 0; i < len(keys); i++ {
        m[keys[i]] = values[i]
    }
    // fmt.Println(m)
    data := map[string]interface{}{"Host": targetHost, "Status": m}
    data_json, _ := json.Marshal(data)
    fmt.Println(string(data_json))
}
...

下図のような Host State Information の情報を解析して JSON で出力しているクソコードです. 苦肉の対応が随所に見られていて, 本当にこれで良いのか悩みどころではあります.

f:id:inokara:20181217000113p:plain

ハンズオン的な

KubernetesNagios を立ち上げる

Docker for MacKubernetes を有効にして, 以下のようなマニフェストを書きました.

$ cat nagios.yml
---
apiVersion: v1
kind: Service
metadata:
  name: nagios-externalip
spec:
  type: LoadBalancer
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
  selector:
    app: nagios-pod
---
apiVersion: v1
kind: Pod
metadata:
  name: nagios-pod
  labels:
    app: nagios-pod
spec:
  containers:
    - name: nagios-container
      image: jasonrivers/nagios:latest
      ports:
      - containerPort: 80

Docker Compose っぽいなあというのがファーストインプレッションです.

以下のように実行して, 環境を起動します.

kubectl apply -f nagios.yml

LoadBalancer サービスや Nagios 環境の Pod が正常に起動すると http://localhost:8080Nagios の Web コンソールにアクセスすることが出来ます. 今回, Nagios コンテナは以下のコンテナを利用しています.

github.com

Nagios のバージョンは 4.4.2 となります. また, nagilla は以下の環境で実行しています.

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.13.6
BuildVersion:   17G65

$ nagilla -version
0.0.1

ホストの情報を取得

nagilla を始める為には, direnv が必要になります. また, 以下のように設定ファイルを JSON で書いておく必要があります. 詳細は README をご確認下さい.

以下を実行して, ホストの状態を確認してみます.

$ nagilla -hosts=localhost -check | jq .
{
  "Host": "localhost",
  "Status": {
    "Active Checks": "ENABLED",
    "Check Latency / Duration": "0.001 / 4.150 seconds",
    "Check Type": "ACTIVE",
    "Current Attempt": "1/10  (HARD state)",
    "Event Handler": "ENABLED",
    "Flap Detection": "ENABLED",
    "Host Status": "UP",
    "In Scheduled Downtime": "NO",
    "Is This Host Flapping": "NO",
    "Last Check Time": "12-16-2018 15:06:01",
    "Last Notification": "N/A (notification 0)",
    "Last State Change": "12-16-2018 10:37:29",
    "Last Update": "12-16-2018 15:08:07  ( 0d  0h  0m  3s ago)",
    "Next Scheduled Active Check": "12-16-2018 15:11:01",
    "Notifications": "DISABLED",
    "Obsessing": "ENABLED",
    "Passive Checks": "ENABLED",
    "Performance Data": "rta=0.077000ms;3000.000000;5000.000000;0.000000 pl=0%;80;100;0",
    "Status Information": "PING OK - Packet loss = 0%, RTA = 0.08 ms"
  }
}

-hosts に対象のホスト名 (Nagios に登録しているホスト名) を指定します. hosts の複数形にしているのは, 将来的にはカンマ区切りで複数指定出来るようにしたいと考えている為です. 現在, 上記のホストは通知 (Notifications) は無効 (DISABLED) になっているようです.

通知の有効化

通知を有効化します.

$ nagilla -hosts=localhost -enable
localhost
上記のホストの通知を有効しますか?(y/n): y
通知を有効にします.
通知を有効にしました.

有効化を改めて -check オプションで確認してみます.

$ nagilla -hosts=localhost -check | jq '.Status.Notifications'
"ENABLED"

上記の通り, ENABLED (有効) になっているようです.

通知の無効化

通知を無効化します.

$ nagilla -hosts=localhost -disable
localhost
上記のホストの通知を無効しますか?(y/n): y
通知を無効にします.
通知を無効にしました.

無効化を改めて -check オプションで確認してみます.

$ nagilla -hosts=localhost -check | jq '.Status.Notifications'
"DISABLED"

いい感じです. ひとまず, 単一ホストの通知の有効化, 無効化は行えるようです.

以上

とにかくコードは汚いですが, やりたいことがコマンド一発で実行出来るのは嬉しいですね. ただし, HTML のパースは個人的には苦行でした... まだまだ, 修行が必要なようです. ところで, Kubernetes を初めて触ってみました. Kubernetes は概念からちゃんと勉強していきたいけど, 取り急ぎ, 検証やギョームで Docker で動かしている諸々を Kubernetes で動かしていきたいと考えています.