mattintosh note

Hello Raspberry Pi!

Zabbix Sender 無しで Zabbix Server にデータを送る

Zabbix API について色々調べてたけど…

「あれ?これってアイテムのデータ追加できなくない?」

という問題にぶち当たった。Slack みたいにデータを追加できるものかと思ったのだが、どうも "request": "sender data" を受け付けない。Zabbix のドキュメントルートを探してみても sender data という文字列は見当たらない。ということは Zabbix Sender が使えないデバイスはデータの追加が出来ないのでは?

いやいやでも Python のモジュールとかあるじゃん。

ってことで Python 系の Zabbix モジュールを色々探して読んでみた。

github.com

このコードを見て、サーバーに送る部分で何をやってるかというと、

  1. Zabbix サーバーの 10051 ポートに接続する
  2. JSON ぶん投げる

以上。マジでこれしかやってないっぽい。

ってことは nc コマンドで接続して JSON 投げるだけで出来るんじゃね?

$ nc 10.0.3.3 10051 <<!
{
    "request": "sender data"
,   "data": [
        {
            "host": "raspberrypi"
        ,   "key": "room_temperature"
        ,   "value": "0"
        }
    ]
,   "clock": `date +%s`
}
!
ZBXDZ{"response":"success","info":"processed: 1; failed: 0; total: 1; seconds spent: 0.000074"}

Zabbix Sender 要らないやんけええええええええ

Zabbix Sender とは一体…。

つまり Zabbix Sender さんのお仕事は -s-k-o オプションで受けった引数から JSON を生成して -z のサーバに投げてるだけということだ。これなら Zabbix Sender をインストールできないデバイスでも使えそう。

FlashAir の Lua でも使おうと思ってるので Lua でソケット通信を簡単に書いてみる。Lua は全然やったことがないから作法とかがよくわからない。

#!/usr/bin/lua

local socket = require("socket")
local client = assert(socket.connect("10.0.3.3", 10051))
local json = [[
{
    "request": "sender data"
,   "data": [
        {
            "host": "alarmpi"
        ,   "key": "room_temperature"
        ,   "value": "0"
        }
    ]
}
]]
client:send(json)
socket.sleep(0.5)
local line = client:receive()
print(line)
client:close()

一応 Zabbix にデータは入るんだけど、client:receive()nil で返ってきちゃうなぁ…。そもそも FlashAir で socket が使えるのかどうかわからんな…。HTTP リクエストはできるから REST かなぁ。

$ lua zabbix_sender.lua
nil

Zabbix で SNMP Agent と通信する

Ubuntu 16.04 LTS で SNMP Agent を構築していく。

snmpsnmpdsnmp-mibs-downloader をインストールする。

$ sudo apt-get install snmp snmpd snmp-mibs-downloader

/etc/snmp/snmp.confmibs : 行をコメントアウトする。(+ALL でもいい?)

$ diff -u <(zcat /etc/snmp/snmp.conf.gz) /etc/snmp/snmp.conf
--- /dev/fd/63  2017-07-07 16:04:36.230571510 +0000
+++ /etc/snmp/snmp.conf 2017-07-07 16:04:15.106009951 +0000
@@ -1,4 +1,4 @@
 # As the snmp packages come without MIB files due to license reasons, loading
 # of MIBs is disabled by default. If you added the MIBs you can reenable
 # loading them by commenting out the following line.
-mibs :
+#mibs :

/etc/snmp/snmpd.confagentAddress 行と rocommunity public 行を編集する。本来は IP アドレスを制限すべきだが、LXC 内のネットワークを使用するので無制限とした。

$ diff -u <(zcat /etc/snmp/snmpd.conf.gz) /etc/snmp/snmpd.conf
--- /dev/fd/63  2017-07-07 16:07:20.342934468 +0000
+++ /etc/snmp/snmpd.conf        2017-07-07 16:06:45.642011890 +0000
@@ -12,9 +12,9 @@
 #
 
 #  Listen for connections from the local system only
- agentAddress  udp:127.0.0.1:161
+#agentAddress  udp:127.0.0.1:161
 #  Listen for connections on all interfaces (both IPv4 *and* IPv6)
-#agentAddress udp:161,udp6:[::1]:161
+ agentAddress udp:161,udp6:[::1]:161
 
 
 
@@ -47,6 +47,7 @@
 
                                                  #  Full access from the local host
 #rocommunity public  localhost
+ rocommunity public  10.0.3.0/24
                                                  #  Default access to basic system info
  rocommunity public  default    -V systemonly
                                                  #  rocommunity6 is for IPv6

snmpd を再起動する。

$ sudo systemctl restart snmpd

Zabbix Server から snmpwalk を使って接続をテストしてみる。下記のような表示が返ってくればOK。

zabbix-server$ snmpwak -v2c -c public 10.0.3.98
SNMPv2-MIB::sysDescr.0 = STRING: Linux snmp_agent_2 4.4.0-83-generic #106-Ubuntu SMP Mon Jun 26 17:54:43 UTC 2017 x86_64
SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.10
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (115105) 0:19:11.05
SNMPv2-MIB::sysContact.0 = STRING: Me <me@example.org>
SNMPv2-MIB::sysName.0 = STRING: snmp_agent_2
SNMPv2-MIB::sysLocation.0 = STRING: Sitting on the Dock of the Bay
SNMPv2-MIB::sysServices.0 = INTEGER: 72
SNMPv2-MIB::sysORLastChange.0 = Timeticks: (0) 0:00:00.00
SNMPv2-MIB::sysORID.1 = OID: SNMP-MPD-MIB::snmpMPDCompliance
SNMPv2-MIB::sysORID.2 = OID: SNMP-USER-BASED-SM-MIB::usmMIBCompliance
:
:

Zabbix でホストを登録する。今回は SNMP なので「SNMPインターフェイス」で作成する。

f:id:mattintosh4:20170708011456p:plain

テンプレートのタブで「Template SNMP OS Linux」を追加する。

f:id:mattintosh4:20170708011506p:plain

データが「最新データ」に反映されていたらOK。

Raspberry Pi の CPU 温度を Zabbix Server に送る

Zabbix Agent で Raspberry Pi の CPU 使用率やネットワーク使用率は Zabbix Server と連携できるようになったけど、CPU の温度までは連携してくれない。

そこで Zabbix Sender を使って Raspberry Pi から Zabbix Server に能動的にデータを送る。

Raspberry Pi の CPU 温度は vcgencmd measure_temp か、/sys/class/thermal/thermal_zone0/temp で調べることができる。

$ vcgencmd measure_temp; cat /sys/class/thermal/thermal_zone0/temp
temp=52.1'C
52615

これを例えば 52.615 として Zabbix に送るには多少整形が必要になる。シェルによっては浮動小数点数を扱うことができるけど、出来ないという想定で、bcawk を使う方法なんかを書いておく。(文字列として扱うのであればなんとでもできるけど)

bc は追加インストールが必要なことがあるので awk の例。Zabbix Sender は通信がうまく出来たかどうか返してくれるけどとりあえず今日はやらない。

#!/bin/bash
 set -e
#set -x
#set -u

ZABBIX_SERVER=10.0.0.254
KEY=sys__class__thermal__thermal_zone0__temp

temp=$(awk 'BEGIN { getline < "/sys/class/thermal/thermal_zone0/temp"; print $0/1000 }')
test -n "${temp}"

zabbix_sender -z "${ZABBIX_SERVER}" -s "${HOSTNAME}" -k "${KEY}" -o "${temp}"

こっちは bc。キーは別に被らなければ長くなくてもよい。

#!/bin/bash
 set -e
#set -x
#set -u

ZABBIX_SERVER=10.0.0.254
KEY=sys__class__thermal__thermal_zone0__temp

temp=$(echo "scale=3; $(cat /sys/class/thermal/thermal_zone0/temp)/1000" | bc -l)
test -n "${temp}"

zabbix_sender -z "${ZABBIX_SERVER}" -s "${HOSTNAME}" -k "${KEY}" -o "${temp}"

Zabbix 側でホストのアイテムに「Zabbix トラッパー」を追加しておく。

f:id:mattintosh4:20170706233320p:plain

Zabbix Server との連携確認用に watch で実行させておく。しばらく実行させたままにするなら TMUX などで実行しておけば端末を落としても大丈夫。うまく連携できることを確認したら cron に任せる。

$ watch -pn 10 ./zabbix_sender.sh

Zabbix の「計算アイテム」を使う

Zabbix には「計算アイテム」と呼ばれるものがある。名前の通り、他の値を計算するアイテム。これを使えば 52615 というような温度も Zabbix 側で計算して 52.62 のようにすることができるのでわざわざシェルで整形する必要がない。

#!/bin/bash
 set -e
#set -x
#set -u

ZABBIX_SERVER=10.0.0.254
KEY=sys__class__thermal__thermal_zone0__temp

temp=$(< /sys/class/thermal/thermal_zone0/temp)
test -n "${temp}"

zabbix_sender -z "${ZABBIX_SERVER}" -s "${HOSTNAME}" -k "${KEY}" -o "${temp}"

新しく計算アイテムを作る。

f:id:mattintosh4:20170706233330p:plain

計算式はこんな感じ。

last(sys__class__thermal__thermal_zone0__temp) / 1000

正しくアイテムが登録されれば計算した値を出してくれる。

f:id:mattintosh4:20170706233339p:plain

今回作った計算アイテムを使えば 1/1000 の値でグラフが作れる。

Zabbix Server と Zabbix Agent の連携

Raspberry Pi に Zabbix Agent をインストールして先日構築した Zabbix Server にデータを収集してもらう。

zabbix-agent パッケージをインストール。

$ sudo apt-get install zabbix-agent

/etc/zabbix/zabbix_agentd.conf を編集。バックアップは任意でどうぞ。

$ sudo gzip -k /etc/zabbix/zabbix_agentd.conf
$ sudo -e /etc/zabbix/zabbix_agentd.conf

とりあえず簡単に許可するサーバのみ追加。

Server=127.0.0.1
$ diff -u <(zcat /etc/zabbix/zabbix_agentd.conf.gz) /etc/zabbix/zabbix_agentd.conf
--- /dev/fd/63  2017-07-04 23:02:05.893855427 +0900
+++ /etc/zabbix/zabbix_agentd.conf      2017-07-04 23:01:40.664122452 +0900
@@ -82,7 +82,7 @@
 # Default:
 # Server=
 
-Server=127.0.0.1
+Server=127.0.0.1,10.16.0.152
 
 ### Option: ListenPort
 #      Agent will listen on this port for connections from the server.

Zabbix Agent の起動。

$ systemctl start zabbix-agent
$ ^start^status
sudo systemctl status zabbix-agent
● zabbix-agent.service - LSB: Start zabbix-agent daemon
   Loaded: loaded (/etc/init.d/zabbix-agent)
   Active: active (running) since Tue 2017-07-04 23:09:39 JST; 12s ago
  Process: 23551 ExecStop=/etc/init.d/zabbix-agent stop (code=exited, status=0/SUCCESS)
  Process: 23878 ExecStart=/etc/init.d/zabbix-agent start (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/zabbix-agent.service
           ├─23885 /usr/sbin/zabbix_agentd
           ├─23886 /usr/sbin/zabbix_agentd: collector [idle 1 sec]
           ├─23887 /usr/sbin/zabbix_agentd: listener #1 [waiting for connection]
           ├─23888 /usr/sbin/zabbix_agentd: listener #2 [waiting for connection]
           ├─23889 /usr/sbin/zabbix_agentd: listener #3 [waiting for connection]
           └─23890 /usr/sbin/zabbix_agentd: active checks #1 [idle 1 sec]

Jul 04 23:09:39 raspberrypi zabbix-agent[23878]: zabbix_agentd starting...done.
Jul 04 23:09:39 raspberrypi systemd[1]: Started LSB: Start zabbix-agent daemon.
Jul 04 23:09:46 raspberrypi systemd[1]: Started LSB: Start zabbix-agent daemon.

テストしようと思ったけど zabbix_get がねぇ…。わざわざ zabbix-server-* をインストールしなきゃいけないんだろうか…。

Zabbix Server インストール済みのマシンから zabbix_get を叩く。エラーが出なければOK。

$ zabbix_get -s 192.168.1.99 -k system.hostname
raspberrypi

※公式リポジトリには zabbix-agent 単体のパッケージが存在する。

エージェントの登録

[設定]>[ホスト]から[ホストの作成]をクリック。

Zabbix Agent を入れたマシンの情報を追加する。

あとはダッシュボードなどでデータが取得できているか確認しましょう。

Raspberry Pi と Bluetooth 経由でシリアル通信する

前回は USB シリアル変換アダプタによる通信だったので今回は Bluetooth で。

メインの Ubuntu から Raspberry PiBluetooth で接続してシェルで操作できるようにするまで。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 17.04
Release:        17.04
Codename:       zesty
pi@raspberrypi:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 8.0 (jessie)
Release:        8.0
Codename:       jessie

とりあえず bluetoothctl でのペアリングが終わってるものとする。

まず、Bluetooth でシリアル通信を行うには bluetoothd-C オプション付きで起動しなければならない。-C オプションが無い場合、sdptool が使えない。

サービスとして起動している bluetoothd を一旦止める。

pi@raspberrypi:~$ sudo systemctl stop bluetooth

bluetoothd を手動でバックグラウンド起動。アダプタが落ちていることがあるので立ち上げ直しておく。

pi@raspberrypi:~$ sudo bluetoothd -C &
pi@raspberrypi:~$ sudo hciconfig hci0 up

この時点ではまだシリアル通信ができない。

pi@raspberrypi:~$ sudo sdptool browse local | grep -i serial

sdptool で SPP を追加する。add --channel={n} で任意のチャンネルも指定できる。

pi@raspberrypi:~$ sudo sdptool add SP
pi@raspberrypi:~$ sudo sdptool browse local | grep -i serial
Service Name: Serial Port
  "Serial Port" (0x1101)
  "Serial Port" (0x1101)

/dev/rfcomm0 に端末を割り当てて待機状態にする。rfcomm コマンドの使い方がまだ掴めていない。gettyagetty へのリンク。

書式とオプションは以下の通り。

RFCOMM configuration utility ver 5.43
Usage:
        rfcomm [options] <command> <dev>

Options:
        -i, --device [hciX|bdaddr]     Local HCI device or BD Address
        -h, --help                     Display help
        -r, --raw                      Switch TTY into raw mode
        -A, --auth                     Enable authentication
        -E, --encrypt                  Enable encryption
        -S, --secure                   Secure connection
        -M, --master                   Become the master of a piconet
        -L, --linger [seconds]         Set linger timeout
        -a                             Show all devices (default)

Commands:
        bind     <dev> <bdaddr> [channel]       Bind device
        release  <dev>                          Release device
        show     <dev>                          Show device
        connect  <dev> <bdaddr> [channel]       Connect device
        listen   <dev> [channel [cmd]]          Listen
        watch    <dev> [channel [cmd]]          Watch

普通にログインさせる場合。

pi@raspberrypi:~$ sudo rfcomm watch 0 1 agetty rfcomm0 115200 linux

ユーザ pi でオートログインするさせる場合。

pi@raspberrypi:~$ sudo rfcomm watch 0 1 agetty rfcomm0 115200 linux -a pi

認証無しでルートログインさせる場合。

pi@raspberrypi:~$ sudo rfcomm watch 0 1 agetty -n -l /bin/bash rfcomm0 115200 linux

watch ではなく listen もあるが、こっちは接続を切るとサーバ側の rfcomm も終了する。

agetty のオプションがよくわからんのだが、ログイン後に下記のエラーが表示される。apt-get update なんかをやると躓く。

-bash: cannot set terminal process group (1825): Inappropriate ioctl for device
-bash: no job control in this shell

接続元からは screen コマンドなどで接続しにいくので rfcomm connect ではなく rfcomm bind しておく。

■書式

bind <dev> <bdaddr> [channel]

チャンネルは指定しなくても勝手に見つけてくれる。

$ sudo rfcomm bind 0 01:23:45:67:89:AB
$ rfcomm
rfcomm0: 01:23:45:67:89:AB channel 1 clean

これで接続元の /dev/rfcomm0 を使って接続する準備ができた。

screen コマンドで rfcomm bind してある /dev/rfcomm0 に接続する。

$ screen /dev/rfcomm0 115200
Raspbian GNU/Linux 8 raspberrypi rfcomm0

raspberrypi login: 

別の端末で watch なんかで状態のチェックをしておくといいかもしれない。

$ watch rfcomm -a
rfcomm0: B8:27:EB:0D:F6:C4 channel 1 connected [tty-attached]

無事 Raspberry Pi に接続できたが、接続する度に最初に AT AT ??? みたいな AT コマンドを何か送ってるっぽいのだが何だこれ?

USB シリアル変換アダプタで接続しているときは出ないので多分 rfcomm 関係だが AT というワードで探すのは辛い…。なんとか見つけた「Disable AT command on rfcomm connect」に書いてあった。

stackoverflow.com

ModemManager よ、またお前か…。

$ sudo systemctl stop ModemManager
$ sudo systemctl disable ModemManager

コンソールのサイズが小さいのは stty で調整。tmux を使っている場合は一回 detach してから。

$ stty rows 44 cols 148
$ tmux

とりあえずプロセスグループが設定できない問題以外はなんとかなったので /lib/systemd/system/bluetooth.service を変更して起動時に Bluetooth シリアル通信ができるようにしておく。

pi@raspberrypi:~$ diff -u <(zcat /lib/systemd/system/bluetooth.service.gz) /lib/systemd/system/bluetooth.service
--- /dev/fd/63  2017-07-02 14:53:53.958932366 +0900
+++ /lib/systemd/system/bluetooth.service       2017-07-02 13:35:31.721916663 +0900
@@ -5,7 +5,8 @@
 [Service]
 Type=dbus
 BusName=org.bluez
-ExecStart=/usr/lib/bluetooth/bluetoothd
+ExecStart=/usr/lib/bluetooth/bluetoothd -C
+ExecStartPost=/usr/bin/sdptool add SP
 NotifyAccess=main
 #WatchdogSec=10
 #Restart=on-failure
pi@raspberrypi:~$ sudo systemctl daemon-reload
pi@raspberrypi:~$ sudo systemctl restart bluetooth

/etc/systemd/system/rfcomm.service を作成。

[Unit]
Description=RFCOMM service
After=bluetooth.service
Requires=bluetooth.service
 
[Service]
ExecStart=/usr/bin/rfcomm watch 0 1 /sbin/agetty rfcomm0 115200 linux
 
[Install]
WantedBy=multi-user.target
pi@raspberrypi:~$ sudo systemctl daemon-reload
pi@raspberrypi:~$ sudo systemctl start rfcomm
pi@raspberrypi:~$ sudo systemctl enable rfcomm

プロセスグループが設定できない問題はよくわからんが、とりあえず Raspberry Pi にログインしたあとに tmux 起動すればいいっぽいので今日はここまで。(ctrl+a と ctrl+b が…)