Sensu-go を試してみた

Setting up Sensu-go
================

この記事は Sensu-go をとりあえず使えたところまでのコマンドログです。

## 経緯
子供たちにせがまれて家庭内マイクラサーバを立てまして、使えないと非難されるので監視してみました。笑

世の中には途方もない手間のかかったゴイスー素敵なマップを無償公開されている方々が、しかもたくさんおられて、家庭内サーバでは子供たちがJava版マインクラフトのカスタムマップをNintendo Switchから使えるようプロトコル変換して遊ばせています。

監視には sensu-go を使いました。ドキュメント通りやって、うまくいきました。良いドキュメントと思います。ただ、ググっても解説が少なかったので書き残します。お役に立てば幸いです。
@see https://docs.sensu.io/sensu-go/latest/

監視結果は Slack 通知することにしました。通知は意図でチャネルを変えて３パターン（ops/dev/そのほかと）通知できるそうです。設定は全てYAMLかJSONで記述することができ、git保存も可能です。この辺りの自在感が好ましい監視ソフトです。

## 導入した環境
家庭内サーバは今のところ家庭内LAN (１ギガイーサネット)のみで運用しており、インターネットに出していません。ここは、近所の子が遊び出したら考えることにします。

サーバ上のサービスはローカルホストで以下が同居しています；

 - java8 のマイクラサーバ (paper-spigot + 20程度のプラグイン)
 - dynmapだとかをプロキシする nginx
 - ユーザデータの保存だとかLuckpermsなどが使う MySQL8 

サーバは Parallels という仮想マシンソフトの上のVMで、OSは ubuntu 20.04、標準ユーザ?は mc です。ホストマシンは Core i7 の MBP(2018くらい) です。

（後ほど必要になりますが、データベースの接続情報やDB名やらは適宜置き換えてご参照くださいませ）

## 観測の仕組みについて

観測の仕組みは以下でシンプルに説明されていました。
@see https://docs.sensu.io/sensu-go/latest/observability-pipeline/

Sensu-go の観測の仕組みは backend <-> agent 間の Publish/Subscribe型の非同期メッセージングで、backend がトピックをブロードキャストし、agentが自分がsubscribeしているトピックについてチェックと、backendへのイベント登録をする、という流れだと読みました。イベント登録を受けて backend はイベントを選別・整形・対応するそうです。

agentの内側が Un*x らしいパイプ処理になっているらしく、値の収集元とagentとのデータ変換を柔軟に実装できる様子でした。

## 積み残し
なお sensu-go の魅力の一つは（sensu と違って RabbitMQ 等の）ミドルウェア不要なことでした。
また Sensu-go 自体の導入やプラグインの導入も統一されたリポジトリと操作で一気通貫なことが簡単でした(DevOpsのツールとしては意外にw)

ただし、今回監視のみなので事足りましたが（我が家、気付いたら再起動するだけですので）、 sensu-go のみですと監視データの蓄積は行わないそうで、記録を残してビジュアル化する場合には追加のセットアップが必要だそうです（fluentdみたいに観測値をDBへ転送・保存し、別途ビジュアル化のツールを使うそうです）。これは後日の課題といたしました。

#以下ログ
以下 ssh した bash でのコマンドのログです。


## Installing utilities and repositories
```
$ sudo apt update -y
 :

$ sudo apt-get install gnupg curl wget unzip -y
 :

$ curl -s https://packagecloud.io/install/repositories/sensu/stable/script.deb.sh | sudo bash
 :

```

## Installing sensu-go backend
```
$ sudo apt install sensu-go-backend
 :

$ sudo curl -L https://docs.sensu.io./sensu-go/latest/files/backend.yml -o /etc/sensu/backend.yml
 :

$ sudo systemctl start sensu-backend

$ sudo systemctl enable sensu-backend
 :

$ sudo systemctl status sensu-backend
    # check active
 :

$ sensu-backend init --interactive
? Cluster Admin Username: mc
? Cluster Admin Password: **
? Retype Cluster Admin Password: **
 :

$ curl http://localhost:3000
Client sent an API request to the web application port!
```

## Installing sensuctl
```
$ sudo apt install sensu-go-cli
 :

$ sensuctl configure
? Authentication method: username/password
? Sensu Backend URL: http://127.0.0.1:8080
? Namespace: default
? Preferred output format: tabular
? Username: mc
? Password: **
```

## Installing sensu-go agent
```
$ sudo apt install sensu-go-agent
 :

$ sudo curl -L https://docs.sensu.io/sensu-go/latest/files/agent.yml -o /etc/sensu/agent.yml
 :

$ sudo systemctl start sensu-agent

$ sudo systemctl enable sensu-agent
 :

$ sudo systemctl status sensu-agent
    # check active
 :

$ sensuctl entity update mc
? Entity Class: agent
? Subscriptions: system,webserver,database
Updated

$ sensuctl entity list
  ID   Class    OS                Subscriptions                        Last Seen            
 ──── ─────── ─────── ───────────────────────────────────── ─────────────────────────────── 
  mc   agent   linux   system,webserver,database,entity:mc   2021-03-29 18:20:39 +0900 JST  

$ curl -X POST \
-H 'Content-Type: application/json' \
-d '{
  "check": {
    "metadata": {
      "name": "example-check"
    },
    "status": 1,
    "output": "You should receive this example event"
  }
}' \
http://127.0.0.1:3031/events

    # test warning

    # warning can resolve by console (namespace -> events then check resolve).

```

## Check nginx for example
```
$ sensuctl asset add ncr-devops-platform/nagiosfoundation -r nagiosfoundation
 :

$ sensuctl check create nginx_service \
--command 'check_service --name nginx' \
--interval 15 \
--subscriptions webserver \
--runtime-assets nagiosfoundation

$ sudo systemctl stop nginx

$ sensuctl event list
  Entity         Check                                          Output                                   Status   Silenced             Timestamp                             UUID                  
 ──────── ──────────────────── ──────────────────────────────────────────────────────────────────────── ──────── ────────── ─────────────────────────────── ────────────────────────────────────── 
  mc       keepalive            Keepalive last sent from mc at 2021-03-29 18:32:39 +0900 JST                  0   false      2021-03-29 18:32:39 +0900 JST   3b42374e-d856-475b-9ece-eb2bd9d38839  
  mc       nginx_service        CheckService CRITICAL - nginx not in a running state (State: inactive)        2   false      2021-03-29 18:32:34 +0900 JST   68ca677a-b8c4-4619-bac9-654a50dd6615  
                                                                                                                                                                                                   
$ sudo systemctl start nginx

$ sensuctl event list
 :

```

## Check MySQL for example
i've installed MySQL8. if so maybe, you shall make mysql speak utf8 (not utf8mb4).
write below on ubuntu 20.04 (***be careful with changing character set.* :)

```/etc/mysql/conf.d/charset.cnf
[client]
default-character-set=utf8

[mysql]
default-character-set=utf8

[mysqld]
collation-server = utf8_general_ci
character-set-server = utf8
```

don't forget restarting mysqld.

and i have a file, having cred, says ~/backupDB.cnf to connect mysql. this is a very private file.

```~/backupDB.cnf
$ cd ~
$ touch backupDB.cnf 
$ chmod o-rw backupDB.cnf 
$ vi backupDB.cnf 
$ cat backupDB.cnf 
[client]
user = (YOUR MYSQL USER)
password = (YOUR MYSQL PASSWD)
host = 127.0.0.1
```

so i did ↓.

ah, yes, it's dirty way... consider your way...

```
$ id
uid=1000(mc) gid=1000(mc) groups=1000(mc),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lxd)

$ ps aux|grep sensu|grep -v grep
sensu       4195  4.2  1.5 5456808 126372 ?      Ssl  16:42  10:46 /usr/sbin/sensu-backend start -c /etc/sensu/backend.yml
sensu       5507  0.1  0.6 128848 53488 ?        Ssl  16:50   0:22 /usr/sbin/sensu-agent start -c /etc/sensu/agent.yml

$ usermod -aG mc sensu

$ id sensu
uid=997(sensu) gid=998(sensu) groups=998(sensu),mc(1000)

$ sudo systemctl restart sensu-backend

$ sudo systemctl restart sensu-agent
```

installing mysql plugin will be just cute a little.

```
$ sensuctl asset add sensu/sensu-ruby-runtime -r ruby-plugins
 :

$ sensuctl asset add sensu-plugins/sensu-plugins-mysql:3.2.0 -r mysql-plugins
 :

$ sensuctl check create mysql_connections \
--command 'check-mysql-connections.rb --warnnum 50 --critnum 75 --host=127.0.0.1 --ini=/home/mc/backupDB.cnf' \
--interval 15 \
--subscriptions database \
--runtime-assets ruby-plugins,mysql-plugins

$ sensuctl event list
 :

```


## Send Slack alerts with handlers

https://docs.sensu.io/sensu-go/latest/observability-pipeline/observe-filter/route-alerts/

i choose sending alert per contact. it'll be done a little bit more.

First, Create three slack channels. (for ops, for dev, and for the others), then create webhook of three.
- #minecraft-server-ops
- #minecraft-server-dev
- #minecraft-server-monitoring

and...

```
$ sensuctl asset add nixwiz/sensu-go-fatigue-check-filter -r fatigue-filter
 :

$ vi sensu-filter-fatigue_check.yaml
$ cat sensu-filter-fatigue_check.yaml
type: EventFilter
api_version: core/v2
metadata:
  name: fatigue_check
  namespace: default
spec:
  action: allow
  expressions:
  - fatigue_check(event)
  runtime_assets:
  - fatigue-check-filter

$ sensuctl create -f sensu-filter-fatigue_check.yaml 
 :
```

```
$ sensuctl asset add sensu/sensu-go-has-contact-filter -r contact-filter
 :

$ vi sensu-filter-contact.yaml
$ cat sensu-filter-contact.yaml
---
type: EventFilter
api_version: core/v2
metadata:
  name: contact_fallback
spec:
  action: allow
  runtime_assets:
    - contact-filter
  expressions:
    - no_contacts(event)
---
type: EventFilter
api_version: core/v2
metadata:
  name: contact_ops
spec:
  action: allow
  runtime_assets:
    - contact-filter
  expressions:
    - has_contact(event, "ops")
---
type: EventFilter
api_version: core/v2
metadata:
  name: contact_dev
spec:
  action: allow
  runtime_assets:
    - contact-filter
  expressions:
    - has_contact(event, "dev")

$ sensuctl create -f sensu-filter-has_contact.yaml 
 :
```

```
$ sensuctl asset add sensu/sensu-slack-handler -r sensu-slack-handler
 :

$ vi sensu-handler-slack.yaml
    # be care of slack channels and webhooks.
$ cat sensu-handler-slack.yaml
---
type: Handler
api_version: core/v2
metadata:
  name: slack_fallback
spec:
  command: sensu-slack-handler --channel "#minecraft-server-monitoring"
  env_vars:
  - SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T0000/B000/XXXXXXXX
  filters:
  - is_incident
  - not_silenced
  - fatigue_check
  - contact_fallback
  runtime_assets:
  - sensu-slack-handler
  type: pipe
---
type: Handler
api_version: core/v2
metadata:
  name: slack_ops
spec:
  command: sensu-slack-handler --channel "#minecraft-server-ops"
  env_vars:
  - SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T0000/B000/XXXXXXXX
  filters:
  - is_incident
  - not_silenced
  - fatigue_check
  - contact_ops
  runtime_assets:
  - sensu-slack-handler
  type: pipe
---
type: Handler
api_version: core/v2
metadata:
  name: slack_dev
spec:
  command: sensu-slack-handler --channel "#minecraft-server-dev"
  env_vars:
  - SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T0000/B000/XXXXXXXX
  filters:
  - is_incident
  - not_silenced
  - fatigue_check
  - contact_dev
  runtime_assets:
  - sensu-slack-handler
  type: pipe
---
type: Handler
api_version: core/v2
metadata:
  name: slack
  namespace: default
spec:
  handlers:
  - slack_ops
  - slack_dev
  - slack_fallback
  type: set
 
$ sensuctl create -f sensu-handler-slack.yaml
 :
```

Let's say test.

```
$ curl -X POST \
-H 'Content-Type: application/json' \
-d '{
  "check": {
    "metadata": {
      "name": "example-check"
    },
    "status": 1,
    "output": "You should receive this example event in the Slack channel specified by your slack_fallback handler.",
    "handlers": ["slack"]
  }
}' \
http://127.0.0.1:3031/events

$ curl -X POST \
-H 'Content-Type: application/json' \
-d '{
  "check": {
    "metadata": {
      "name": "example-check",
      "labels": {
        "contacts": "dev"
      }
    },
    "status": 1,
    "output": "You should receive this example event in the Slack channel specified by your slack_dev handler.",
    "handlers": ["slack"]
  }
}' \
http://127.0.0.1:3031/events

$ curl -X POST \
-H 'Content-Type: application/json' \
-d '{
  "check": {
    "metadata": {
      "name": "example-check",
      "labels": {
        "contacts": "ops"
      }
    },
    "status": 1,
    "output": "You should receive this example event in the Slack channel specified by your slack_ops handler.",
    "handlers": ["slack"]
  }
}' \
http://127.0.0.1:3031/events

$ curl -X POST \
-H 'Content-Type: application/json' \
-d '{
  "check": {
    "metadata": {
      "name": "example-check",
      "labels": {
        "contacts": "ops,dev"
      }
    },
    "status": 1,
    "output": "You should receive this example event in the Slack channel specified by your ops adn dev handler.",
    "handlers": ["slack"]
  }
}' \
http://127.0.0.1:3031/events
```

So we can create checks for agent.

```
$ vi sensu-checks.yaml
$ cat sensu-checks.yaml
---
type: CheckConfig
api_version: core/v2
metadata:
  created_by: mc
  name: nginx_service
  namespace: default
  annotations:
    fatigue_check/occurrences: '1'
    fatigue_check/interval: '3600'
    fatigue_check/allow_resolution: 'false'
  labels:
    contacts: ops
spec:
  check_hooks: null
  command: check_service --name nginx
  env_vars: null
  handlers:
  - slack
  high_flap_threshold: 0
  interval: 15
  low_flap_threshold: 0
  output_metric_format: ""
  output_metric_handlers: null
  proxy_entity_name: ""
  publish: true
  round_robin: false
  runtime_assets:
  - nagiosfoundation
  secrets: null
  stdin: false
  subdue: null
  subscriptions:
  - webserver
  timeout: 0
  ttl: 0
---
type: CheckConfig
api_version: core/v2
metadata:
  created_by: mc
  name: mysql_alive
  namespace: default
  annotations:
    fatigue_check/occurrences: '1'
    fatigue_check/interval: '3600'
    fatigue_check/allow_resolution: 'false'
  labels:
    contacts: ops
spec:
  check_hooks: null
  command: check-mysql-alive.rb --database=playerdata --host=127.0.0.1 --ini=/home/mc/backupDB.cnf
  env_vars: null
  handlers:
  - slack
  high_flap_threshold: 0
  interval: 15
  low_flap_threshold: 0
  output_metric_format: ""
  output_metric_handlers: null
  proxy_entity_name: ""
  publish: true
  round_robin: false
  runtime_assets:
  - ruby-plugins
  - mysql-plugins
  secrets: null
  stdin: false
  subdue: null
  subscriptions:
  - database
  timeout: 0
  ttl: 0
---
type: CheckConfig
api_version: core/v2
metadata:
  created_by: mc
  name: mysql_connections
  namespace: default
  annotations:
    fatigue_check/occurrences: '1'
    fatigue_check/interval: '3600'
    fatigue_check/allow_resolution: 'false'
  labels:
    contacts: ops, dev
spec:
  check_hooks: null
  command: check-mysql-connections.rb --warnnum 50 --critnum 75 --host=127.0.0.1 --ini=/home/mc/backupDB.cnf
  env_vars: null
  handlers:
  - slack
  high_flap_threshold: 0
  interval: 15
  low_flap_threshold: 0
  output_metric_format: ""
  output_metric_handlers: null
  proxy_entity_name: ""
  publish: true
  round_robin: false
  runtime_assets:
  - ruby-plugins
  - mysql-plugins
  secrets: null
  stdin: false
  subdue: null
  subscriptions:
  - database
  timeout: 0
  ttl: 0

$ sensuctl check delete nginx_service
? Are you sure you would like to delete resource 'nginx_service'? Yes
Deleted

$ sensuctl check delete mysql_connections
? Are you sure you would like to delete resource 'mysql_connections'? Yes
Deleted

$ sensuctl create -f sensu-checks.yaml

$ sudo systemctl stop nginx; \
sleep 20; \
sudo systemctl start nginx

```


__END__