ELK Stackでシステム監視 FilebeatでFreeBSDのCPU温度取得+Kibanaグラフ化

この記事ではFreeBSDでCPUコアの温度を取得してログ化し、Filebeatで取得、Logstashで加工してKibanaでグラフ表示できるようにする。

FreeBSDでCPU温度取得

CPUの温度確認の記事と変わらないが以下。

# kldstat
Id Refs Address            Size     Name
 1   27 0xffffffff80200000 1f67a88  kernel
 2    1 0xffffffff82169000 12c80    linprocfs.ko
 3    4 0xffffffff8217c000 df88     linux_common.ko
 4    1 0xffffffff8218a000 2678     accf_http.ko
 5    1 0xffffffff8218d000 4d18     coretemp.ko     ←coretemp.koがある
 6    1 0xffffffff82192000 39d8     cc_htcp.ko
 7    1 0xffffffff82221000 34d5c    pf.ko
 8    1 0xffffffff82256000 42864    linux.ko
 9    1 0xffffffff82299000 3c93f    linux64.ko

上のようにcoretempが読み込まれていない場合は以下。

# kldload coretemp                        #←coretempモジュール読み込み         
# sysctl dev.cpu | grep temperature       #←温度表示
dev.cpu.7.temperature: 26.0C
dev.cpu.6.temperature: 26.0C
dev.cpu.5.temperature: 26.0C
dev.cpu.4.temperature: 26.0C
dev.cpu.3.temperature: 25.0C
dev.cpu.2.temperature: 25.0C
dev.cpu.1.temperature: 26.0C
dev.cpu.0.temperature: 26.0C

このように表示されるなら次回以降のOS起動時に自動的にcoretempモジュールが読み込まれるように設定する。

/boot/loader.conf (1行追加)
coretemp_load="YES"

これで温度が取れるようになったが、先のような結果出力ではログとしては使いにくい。そこでスクリプトにした。

/usr/local/etc/sbin/get_temperature.sh (新規作成)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/bin/sh
ncpu=$( sysctl hw.ncpu | awk '{ print $2 }' )
out=$( /bin/date '+%Y-%m-%d %H:%M:%S' )
i=0

while [ ${i} -lt ${ncpu} ]
do
 out="${out} `sysctl dev.cpu.${i}.temperature | sed -e 's|.*: \([0-9.]*\)C|\1|'`"
 i=`expr ${i} + 1`
done

echo ${out} > /tmp/temperature.tmp
sleep 15
rm /tmp/temperature.tmp

スクリプトには実行権限を付けておく。
CPUのコア数を確認してコア数だけ順番に温度を取る。以下のようなログが出力される。

2018-03-14 11:24:00 26.0 27.0 28.0 28.0 27.0 28.0 30.0 28.0
日付 時刻 0 1 2 3 4 5 6 7 ←CPUコアの温度

日付と時刻は出力する必要はないのだが、一応付けた。各CPUコアの温度の間に半角スペースを挟んで並べた。
で、仮にこのスクリプトを1分ごとに実行するとして、出力を追記にするとファイルが肥大するのが嫌いなので毎回上書きにしたかったのだが、それをするとFilebeatがファイルの更新に気付いてくれないようなので、出力後15秒で一旦ファイルを削除することにした。ということは、温度記録を蓄積するログではないので/var/logに置くのではなく/tmpに一時ファイルとして置くようにした。

このスクリプトをcronで1分毎など好みの間隔で定期実行させる。

Filebeatの設定

/usr/local/etc/filebeat.yml (途中追加・変更)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
filebeat.prospectors:
#前回の記事のFail2ban用の設定ここから
- type: log
  enabled: true
  paths:
    - /var/log/fail2ban.log
  fields:
    type: fail2ban
  include_lines: [' Ban ']
  exclude_lines: ['Restore']
  processors:
    - drop_fields:
         fields: ['offset', 'source']
#前回の記事のFail2ban用の設定ここまで
#今回の記事用の設定はここから
- type: log
  enabled: true
  paths:
    - /tmp/temperature.tmp              #←ログファイル
  fields:
    type: temperature                   #fields.typeを temperature とした
  processors:
    - drop_fields:                      #出力したくないフィールドの指定
         fields: ['offset', 'source']   #今回はoffsetとsourceを出力しない
#←今回の記事用の設定はここまで

filebeat.config.modules:
  path: ${path.config}/beats/file_*.yml #今回も使わない
  reload.enabled: false

output.logstash:
  hosts: ["192.168.2.24:5043"]          #Logstash稼働ホストのテスト用ポートに出力
#  hosts: ["192.168.2.24:5044"]         #Logstash稼働ホストの本番用ポート

#logging.level: debug
logging.selectors: ["*"]
logging.to_syslog: false                #Filebeatのログをsyslogに出力しない
logging.to_files: false                 #Filebeatのログファイルは出力しない
logging.files:
  path: /var/log
  name: filebeat.log

Filebeatを再起動する。
# service filebeat restart

Filebeat(のホスト)側はこれだけ。

Logstashの設定

今回もFilebeatから送られてきたデータを加工するのでfilter部に追記する。

/usr/local/etc/logstash/test.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
input {
    beats {
        port => 5043    #テスト用ポート
    }
}

filter {
  #前回の記事のFail2ban用ここから
  if [fields][type] == "fail2ban" {
    grok {
      patterns_dir => ["/usr/local/etc/logstash/patterns"]
      match => {"message" => "%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{TIME},%{NUMBER} fail2ban\.actions%{SPACE}\[%{NUMBER}\]\: NOTICE  \[%{USERNAME:jail}\] Ban %{IP:ip}"}
      remove_field => ["message", "beat", "tags"]
    }
    geoip {
      source => "ip"
    }
  }
  #前回の記事のFail2ban用ここまで

  #今回の記事用の追加設定ここから
  if [fields][type] == "temperature" {
    grok {
      patterns_dir => ["/usr/local/etc/logstash/patterns"]
      match => { "message" => "%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{TIME} %{GREEDYDATA:message}" }
      "overwrite" => "message"
    }
    grok {
      "match" => { "message" => [
        "%{NUMBER:[temperature]core0} %{NUMBER:[temperature]core1} %{NUMBER:[temperature]core2} %{NUMBER:[temperature]core3} %{NUMBER:[temperature]core4} %{NUMBER:[temperature]core5} %{NUMBER:[temperature]core6} %{NUMBER:[temperature]core7}",
        "%{NUMBER:[temperature]core0} %{NUMBER:[temperature]core1} %{NUMBER:[temperature]core2} %{NUMBER:[temperature]core3}",
        "%{NUMBER:[temperature]core0} %{NUMBER:[temperature]core1}",
        "%{NUMBER:[temperature]core0}"
        ]
      }
      remove_field => ["message", "beat", "tags"]
    }
    mutate {
      convert => {
        "[temperature]core0" => "float"
        "[temperature]core1" => "float"
        "[temperature]core2" => "float"
        "[temperature]core3" => "float"
        "[temperature]core4" => "float"
        "[temperature]core5" => "float"
        "[temperature]core6" => "float"
        "[temperature]core7" => "float"
      }
    }
  }
  #今回の記事用の追加設定ここまで
}

output {
    stdout { codec => rubydebug }           #テスト用  コンソールに出力
    #elasticsearch { hosts => [ "localhost:9200" ] }
}

今回の追加部分の処理は1回めのgrok{}で日時の後の部分のメッセージをmessageとして上書き。これでmessageはCPUのコア数分の温度だけが記載されたものになる。

上の設定の30〜33行ではコア数が8,4,2,1のCPUの温度出力用として4種類(4行)書いた。もっと違うコア数のCPUのホストがあるなら %{NUMBER:[temperature]core番号} をコア数だけ並べた行を追加する。(半角スペースを挟んで並べること)
マッチする行は全体で一致である必要があるようなので、データの要素数(この記事ではCPUのコア数分の温度の数)が不足or過剰だとマッチしてくれないので監視対象ホストのCPUのコア数の全パターンを用意してやらないといけないっぽい。つまりCPUコア数が2のホスト,4のホスト,16のホストがあるなら2,4,16の3パターンで作成する。
近似一致が使えたら設定が楽なんだけどなぁと思ったりもするけど、予想してなかった動作をして後でアレ?ってなるよりは融通が効かない方が良いのかしら?
matchで抽出すると数値に見えるものも文字列型になっているので40〜47行で温度の数値をfloat型に変換する。(最大8コアと想定、足りなければ増やす)

Logstashでデータ受信テスト

テスト用Logstashを起動してFilebeatからログデータを渡してみる。
本番環境に影響させずに試行錯誤を行うためのテスト用Logstashの起動については以前の記事を参照

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
         "fields" => {
        "type" => "temperature"
    },
       "@version" => "1",
     "@timestamp" => 2018-03-14T07:34:00.963Z,
           "host" => "hoge.localnet",
     "prospector" => {
        "type" => "log"
    },
    "temperature" => {
        "core2" => "32.0",
        "core0" => "32.0",
        "core3" => "32.0",
        "core1" => "32.0"
    }
}

CPUコア別の温度が意図したとおり出力できている。(出力の並び順は定まっていない)

Kibanaで温度をグラフ出力

KibanaでCPUの温度をグラフ表示 1
まず、elasticsearchに溜まっている筈のCPU温度情報データを確認する。(データの形が頭に浮かぶなら必須ではない)
今回は監視対象ホスト(hosts)とfields.typeがtemperatureで絞ると温度のデータを見ることができそう。
ただし、取得するデータとしてはfields.typeで絞る必要はなくて直接temperature.core?を取得すれば良さそうなのがわかる。
今回はTimelionでグラフ化するので、ここで絞った条件はSearchオブジェクトとして保存する必要はない。

KibanaでCPUの温度をグラフ表示 2
左列のメニューからVisualizeをクリックし、 (新規作成)を押すと、上の画面が表示される。
今回はTimelionを使う。

KibanaでCPUの温度をグラフ表示 3
とりあえず、単純にCPU1コア分ずつグラフを作成して並べる。hostに対象ホスト名、metric(値)はtemperature.core?の最大値(max)でも指定しておく。
線グラフの線の太さが初期値では太すぎると思うならwidthで好みに調整。最初のグラフ(core0)1本だけは見栄えの為に線グラフの下側を塗りつぶすことにした。fill=?がそれ。数値が小さいと薄い色、10に近いほど濃い色となる。最後のグラフに.yaxis()を付けてグラフで表示する温度の範囲を指定した。今回は0〜60℃ということにした。
希望どおりに表示できるようになったら保存する。

1
2
3
4
.es(interval='1m', q='host:hoge.localnet', metric='max:temperature.core0').lines(width=1, fill=1).label('core0'), 
.es(interval='1m', q='host:hoge.localnet', metric='max:temperature.core1').lines(width=1).label('core1'), 
.es(interval='1m', q='host:hoge.localnet', metric='max:temperature.core2').lines(width=1).label('core2'), 
.es(interval='1m', q='host:hoge.localnet', metric='max:temperature.core3').lines(width=1).label('core3').yaxis(min=0, max=60)

KibanaでCPUの温度をグラフ表示 4
ダッシュボードに貼り付けるとこんな感じ。(赤い四角枠のグラフ)

関連記事: