Raspbian buster でElastic Stack 7.5.1 を動かす (7.2〜からの情報有)

#はじめに
すみません、構築方法でプログラミング技術は記載していません。

最近のIoTではエッジデバイスでのデータ分析が流行っており、5Gの普及と共に加速するものと思われます。
Raspberry Pi 4Bが国内販売された事により、エッジ側の環境でもデータ収集・分析・可視化が現実的になりました。

ここでは、ElasticStackをRaspberry Piで動作させる方法を記載しています。
ElasticStack製品は64bit前提の製品で、ARMアーキテクチャにも正式に対応しておりませんが、移植性の高いJava/JavaScriptプラットフォームの製品ですので、少しの作業で動かす事ができます。

[追記] 7.6.0からは、dockerで構築しています。
[Raspberry Pi 4Bのdocker上でElastic Stack 7.6.0 を動かす](https://qiita.com/Y-Shikase/items/d162805aebb3a1a8447f)

#前提環境
- Raspberry Pi 3B+/4B(RAM 4GB)
  - OS : Raspbian Buster (2020/1/4時点で apt upgrade 実施)

NOOBSでインストールしたパッケージ構成で、Open JDK 11 と Node.js 10.15.2 がインストールされている前提です。

rootのパスワードを設定して、カーネルパラメータの変更をします。

```
pi@raspberrypi:~ $ sudo passwd root
新しいパスワード:
新しいパスワードを再入力してください:
passwd: パスワードは正しく更新されました
pi@raspberrypi:~ $ su -
パスワード:
root@raspberrypi:~# echo "vm.max_map_count=262144" >> /etc/sysctl.conf
root@raspberrypi:~# sysctl -p
vm.max_map_count = 262144
```

一応、各バージョンを確認します。
ポイントは Open JDK のバージョンが「11」である点と、 Node.js のバージョンが「10.15.2」である点です。

```
root@raspberrypi:~# uname -a
Linux raspberrypi 4.19.75-v7l+ #1270 SMP Tue Sep 24 18:51:41 BST 2019 armv7l GNU/Linux
root@raspberrypi:~# java --version
openjdk 11.0.5 2019-10-15
OpenJDK Runtime Environment (build 11.0.5+10-post-Raspbian-1deb10u1)
OpenJDK Server VM (build 11.0.5+10-post-Raspbian-1deb10u1, mixed mode)
root@raspberrypi:~# node -v
v10.15.2
```

Kibanaの7.2〜7.4では、nodegitをコンパイルする必要がありましたが、7.5.1では必要ありません。
一応、以下に7.2〜7.4向けにnodegitをコンパイルする下準備のコマンドを記載しておきます。

```
[Kibana 7.2〜7.4の場合のみ実施]
root@raspberrypi:~# apt install libssh-dev libcurl4-openssl-dev
root@raspberrypi:~# ln -sr /usr/lib/arm-linux-gnueabihf/libcurl.a /usr/local/lib/libcurl.a
root@raspberrypi:~# mkdir -p /usr/local/ssl/lib
root@raspberrypi:~# ln -sr /usr/lib/arm-linux-gnueabihf/libssl.a /usr/local/ssl/lib/libssl.a
root@raspberrypi:~# ln -sr /usr/lib/arm-linux-gnueabihf/libcrypto.a /usr/local/ssl/lib/libcrypto.a
root@raspberrypi:~# ldconfig
```

# Elasticsearchのインストールと設定
Elasticsearchのインストールと設定、およびラズパイではお馴染みのJVMが使うメモリの設定です。
（piユーザでの作業です。）

```
pi@raspberrypi:~ $ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.5.1-linux-x86_64.tar.gz
pi@raspberrypi:~ $ tar xzvf elasticsearch-7.5.1-linux-x86_64.tar.gz
pi@raspberrypi:~ $ ln -s elasticsearch-7.5.1 elasticsearch
pi@raspberrypi:~ $ echo "network.host: 0.0.0.0" >> elasticsearch/config/elasticsearch.yml
pi@raspberrypi:~ $ echo "discovery.type: single-node" >> elasticsearch/config/elasticsearch.yml
pi@raspberrypi:~ $ echo "xpack.ml.enabled: false" >> elasticsearch/config/elasticsearch.yml
pi@raspberrypi:~ $ perl -pi -e "s/Xms1g/Xms512m/" elasticsearch/config/jvm.options
pi@raspberrypi:~ $ perl -pi -e "s/Xmx1g/Xmx512m/" elasticsearch/config/jvm.options
```

「elasticsearch」というディレクトリ名でシンボリックリンクする事で、バージョンが変わった際にsystemdのサービス起動ファイルまで変更したくないからです。
Elasticsearchの設定オプションの説明まではしませんが、「xpack.ml.enabled」は「false」にしてください。
有償ライセンスで利用する機械学習の拡張機能なのですが、32bit環境では動作しないためです。

JVMの利用メモリ設定は「512MB」にしていますが、本格的に使うのであれば、1GBは必要かと思います。
稼働後に、logs/gc.logを監視して、足りないようであれば拡張します。

sudoコマンドで、systemdにサービスとして登録する設定ファイルを作成します。

```
pi@raspberrypi:~ $ sudo vi /etc/systemd/system/elasticsearch.service 
[Unit]
After=syslog.target network.target

[Service]
Environment="JAVA_HOME=/usr/lib/jvm/java-11-openjdk-armhf"
ExecStart=/home/pi/elasticsearch/bin/elasticsearch
WorkingDirectory=/home/pi/elasticsearch
User=pi
Group=pi
Nice=10
SyslogIdentifier=Elasticsearch
StandardOutput=syslog
Restart=on-failure
KillSignal=SIGINT

[Install]
WantedBy=multi-user.target
```

サービスの起動は、systemctlを使用します。

```
pi@raspberrypi:~ $ sudo systemctl start elasticsearch
```

journalctl -fを実行しておき、「started」のメッセージが確認できたら、[Ctrl]+[c]で終わらせます。

```
pi@raspberrypi:~ $ journalctl -f
 1月 04 17:14:58 raspberrypi Elasticsearch[13552]: [2020-01-04T17:14:58,265][INFO ][o.e.n.Node               ] [raspberrypi] started
 1月 04 17:14:59 raspberrypi Elasticsearch[13552]: [2020-01-04T17:14:59,273][INFO ][o.e.l.LicenseService     ] [raspberrypi] license [ca46ae11-79ad-4fb7-bb51-38bfe88a3242] mode [basic] - valid
 1月 04 17:14:59 raspberrypi Elasticsearch[13552]: [2020-01-04T17:14:59,279][INFO ][o.e.x.s.s.SecurityStatusChangeListener] [raspberrypi] Active license is now [BASIC]; Security is disabled
 1月 04 17:14:59 raspberrypi Elasticsearch[13552]: [2020-01-04T17:14:59,424][INFO ][o.e.g.GatewayService     ] [raspberrypi] recovered [3] indices into cluster_state
```

サービス起動時に問題がなければOS起動時に起動するように設定します。

```
pi@raspberrypi:~ $ sudo systemctl enable elasticsearch
```

# Kibanaのインストールと設定

Kibanaのインストールと設定です。ロケールを日本語にしています。

```
pi@raspberrypi:~ $ wget https://artifacts.elastic.co/downloads/kibana/kibana-7.5.1-linux-x86_64.tar.gz
pi@raspberrypi:~ $ tar xzvf kibana-7.5.1-linux-x86_64.tar.gz 
pi@raspberrypi:~ $ ln -s kibana-7.5.1-linux-x86_64 kibana
pi@raspberrypi:~ $ echo 'server.host: "0.0.0.0"' >> kibana/config/kibana.yml
pi@raspberrypi:~ $ echo 'i18n.locale: "ja-JP"' >> kibana/config/kibana.yml
```

Kibanaが使っている、nodeコマンドをOSのものを使うようにシンボリックリンクします。

```
pi@raspberrypi:~ $ rm kibana/node/bin/node
pi@raspberrypi:~ $ ln -sr `which node` kibana/node/bin/node
```

Kibana内のelastic-ctagsにARM用のctagsを配置します。(7.4以降で必要です)
まずは、piユーザのディレクトリにctagsモジュールをダウンロード＆コンパイルしてインストールします。

```
[Kibana 7.4〜の場合のみ実施]
pi@raspberrypi:~ $ npm install ctags
npm WARN npm npm does not support Node.js v10.15.2
npm WARN npm You should probably upgrade to a newer version of node as we
npm WARN npm can't make any promises that npm will work with this version.
npm WARN npm Supported releases of Node.js are the latest release of 4, 6, 7, 8, 9.
npm WARN npm You can find the latest version at https://nodejs.org/

> ctags@3.0.0 install /home/pi/node_modules/ctags
> node-gyp rebuild

make: ディレクトリ '/home/pi/node_modules/ctags/build'　に入ります
  CC(target) Release/obj.target/ctags/src/readtags.o
  CXX(target) Release/obj.target/ctags/src/tags.o
In file included from ../src/tags.h:5,
　　　　　　　　：
npm WARN pi No description
npm WARN pi No repository field.
npm WARN pi No README data
npm WARN pi No license field.

+ ctags@3.0.0
added 10 packages from 13 contributors in 41.862s
```

ctagsのインストールができたら、Kibanaのelastic-ctagsにarm用のディレクトリを作成してコピーします。

```
[Kibana 7.4〜の場合のみ実施]
pi@raspberrypi:~ $ mkdir kibana/node_modules/@elastic/node-ctags/ctags/build/ctags-node-v64-linux-arm
pi@raspberrypi:~ $ cp node_modules/ctags/build/Release/ctags.node kibana/node_modules/@elastic/node-ctags/ctags/build/ctags-node-v64-linux-arm/.
```

Kibanaの7.2〜7.4では、nodegitのコンパイル＆インストールが必要でしたが、7.5.1では必要ありません。
一応、以下に7.2〜7.4向けにnodegitをコンパイルするコマンドを記載しておきます。

```
[Kibana 7.2〜7.4の場合のみ実施]
pi@raspberrypi:~ $ cd kibana/node_modules/@elastic/
pi@raspberrypi:~/kibana/node_modules/@elastic $ rm -rf nodegit/
pi@raspberrypi:~/kibana/node_modules/@elastic $ git clone http://github.com/elastic/nodegit.git
pi@raspberrypi:~/kibana/node_modules/@elastic $ cd nodegit/
pi@raspberrypi:~/kibana/node_modules/@elastic/nodegit $ npm install
```

Elasticsearchの時と同じく、rootユーザで、systemdにサービスとして登録する設定ファイルを作成します。

```
pi@raspberrypi:~ $ sudo vi /etc/systemd/system/kibana.service 
[Unit]
After=elasticsearch.service

[Service]
Environment="NODE_OPTIONS=--max-old-space-size=512"
ExecStart=/home/pi/kibana/bin/kibana
WorkingDirectory=/home/pi/kibana
User=pi
Group=pi
Nice=10
SyslogIdentifier=Kibana
StandardOutput=syslog
Restart=on-failure
KillSignal=SIGINT

[Install]
WantedBy=multi-user.target
```

メモリを512MBに制限していますが、経験上、常用する場合は1GBくらい必要だと思います。
サービスの起動は、systemctlを使用します。

```
pi@raspberrypi:~ $ sudo systemctl start kibana
```

Kibanaの起動はやや遅いです。journalctl -fで起動を待ちましょう。
「Server running at http://0.0.0.0:5601 」が出れば大丈夫だと思います。

```
pi@raspberrypi:~ $ journalctl -f
 1月 04 17:44:57 raspberrypi Kibana[14224]: {"type":"log","@timestamp":"2020-01-04T08:44:57Z","tags":["listening","info"],"pid":14224,"message":"Server running at http://0.0.0.0:5601"}
 1月 04 17:44:57 raspberrypi Kibana[14224]: {"type":"log","@timestamp":"2020-01-04T08:44:57Z","tags":["info","http","server","Kibana"],"pid":14224,"message":"http server running at http://0.0.0.0:5601"}
 1月 04 17:44:57 raspberrypi Kibana[14224]: {"type":"log","@timestamp":"2020-01-04T08:44:57Z","tags":["reporting","error"],"pid":14224,"message":"The Reporting plugin encountered issues launching Chromium in a self-test. You may have trouble generating reports."}
 1月 04 17:44:57 raspberrypi Kibana[14224]: [17.4K blob data]
 1月 04 17:44:57 raspberrypi Kibana[14224]: {"type":"log","@timestamp":"2020-01-04T08:44:57Z","tags":["reporting","warning"],"pid":14224,"message":"See Chromium's log output at \"/home/pi/kibana-7.5.1-linux-x86_64/data/headless_shell-linux/chrome_debug.log\""}
 1月 04 17:44:57 raspberrypi Kibana[14224]: {"type":"log","@timestamp":"2020-01-04T08:44:57Z","tags":["reporting","warning"],"pid":14224,"message":"Reporting plugin self-check failed. Please check the Kibana Reporting settings. Error: Could not close browser client handle!"}
```

環境依存かはわかりませんが、/home/pi/kibana/ディレクトリにコントロールコードのファイル名でファイルが生成される場合があります。このファイルがあるとkibanaが起動しないようなので、削除するようにします。
といっても、簡単に消せるファイルではありません。

まず、ファイル名の正体を調べます。/home/pi/kibanaで ls の後にタブキーを２回押します。

```
pi@raspberrypi:~/kibana $ ls (TABを2回打つ)
^A??^B@???^G@8^L@47^F^D@@@?^B?^B^H^C^D?^B?^B?^B^\^\^A^A^D^\??^B^\??^B^P^A^E??^B??^B??^B^P??^D^P??^D^P^A^F@?^G@?^G@?^G@?4@?4^P^A^F@?^G@?^G@?^G??^Bt+^\^P^G^D@?^G@?^G@?^G^X?@^B^F^X??^G^X??^G^X??^G^B^HR?td^D@?^G@?^G@?^G55^AP?td^D??^P^B??^P^B??^P^B^L^_^]^L^_^]^DQ?td^F^D^D?^B?^B?^B
.i18nrc.json
LICENSE.txt
NOTICE.txt
README.txt
　：
```

「^A」から始まっているので、rm コマンドの次に[Ctrl]+[v],[Ctrl]+[a]と押し、後方文字列は「*」で削除します。
[Ctrl]+[v]でコントロールコードを入力するモードになります。知っておくと便利です。

```
pi@raspberrypi:~/kibana $ rm ^A*
```

kibanaの起動スクリプトの冒頭に、上述のrmコマンドを入れておきます。
しかし、このファイルは一体何なのでしょうか...

```
pi@raspberrypi:~/kibana $ vi bin/kibana
#!/bin/sh
SCRIPT=$0

rm /home/pi/kibana/^A*

# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
while [ -h "$SCRIPT" ] ; do
  ls=$(ls -ld "$SCRIPT")
  # Drop everything prior to ->
  link=$(expr "$ls" : '.*-> \(.*\)$')
  if expr "$link" : '/.*' > /dev/null; then
    SCRIPT="$link"
  else
    SCRIPT=$(dirname "$SCRIPT")/"$link"
  fi
done

DIR="$(dirname "${SCRIPT}")/.."
NODE="${DIR}/node/bin/node"
test -x "$NODE"
if [ ! -x "$NODE" ]; then
  echo "unable to find usable node.js executable."
  exit 1
fi

NODE_OPTIONS="--no-warnings --max-http-header-size=65536 ${NODE_OPTIONS}" NODE_ENV=production exec "${NODE}" "${DIR}/src/cli" ${@}
```

サービス起動時に問題がなければOS起動時に起動するように設定します。

```
pi@raspberrypi:~ $ sudo systemctl enable kibana
```

# 可視化してみる
実際にElasticsearchにデータを投入して可視化してみましょう。
rootユーザで、Load Avg.とCPUの温度を採取して、REST APIでElasticsearchに投入するシェルを作成し、cronで1分間隔で実行するようにします。

```
pi@raspberrypi:~ $ su -
パスワード:
root@raspberrypi:~# mkdir bin
root@raspberrypi:~# cd bin
root@raspberrypi:~/bin# vi rpiinfo.sh 
#!/bin/bash

HOST=`hostname`
DATE=`date --iso-8601="seconds"`

LOAD=`cat /proc/loadavg | cut -d' ' -f1`
CPUTEMP=`vcgencmd measure_temp | cut -d= -f2 | cut -d"'" -f1`
JSON='{"date":"'${DATE}'","'${HOST}'-temp":'${CPUTEMP}',"'${HOST}'-load":'${LOAD}'}'

curl --no-keepalive -s -XPOST -H "Content-Type: application/json" http://localhost:9200/rpi-`hostname`/_doc?pretty -d "${JSON}"
root@raspberrypi:~/bin# chmod +x rpiinfo.sh
```

crontab -e でもいいのですが、個人的にはファイルを登録する手順を愛用しています。

```
root@raspberrypi:~/bin# vi cronfile 
*/1 * * * * /root/bin/rpiinfo.sh >/dev/null 2>&1
root@raspberrypi:~/bin# crontab cronfile 
root@raspberrypi:~/bin# crontab -l
*/1 * * * * /root/bin/rpiinfo.sh >/dev/null 2>&1
```

ネットワーク内の端末のブラウザからKibanaにアクセスします。URLは、「http://<ラズパイのIPアドレス>:5601/」です。
起動したら「開発ツール」にあるコンソールから登録されているクエリ「GET _search」横の再生ボタンを押して実行します。

![スクリーンショット 2019-09-14 17.28.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/e88bbf7c-28d8-9049-bbdb-bc7e2674a00e.png)

結果の下部にシェルから登録されたデータがあれば、無事にデータ投入されています。
次に「管理」の「インデックスパターン」で、Kibanaのインデックスパターンを作成します。時間軸は「date」フィールドです。

![スクリーンショット 2019-09-14 17.34.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/7b921bf5-7da9-bdd8-9932-234c4540df4a.png)

![スクリーンショット 2019-09-14 17.34.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/b45b3e88-1375-ebb1-ae5c-327f117c91d6.png)

左メニューの「可視化」から新規ビジュアライゼーションの作成をします。
折れ線グラフでも作りましょうか。

![スクリーンショット 2019-09-14 17.38.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/3c7dfc6a-95b2-fefd-d213-c390345c493c.png)

メトリックのY軸を集約のタイプを「平均」、フィールドを「<ラズパイのホスト名>-temp」として、バケットのX軸を集約のタイプ「DateHistgram」、フィールを「date」とします。
再生ボタンを押すと、時系列の温度が線グラフとして表示されます。

![スクリーンショット 2019-09-14 17.41.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/f311242e-67a5-c1d5-fa33-9196e8c0843e.png)

このようにElasticsearchへのデータの投入、可視化のパーツを作成を繰り返し、最終的にダッシュボードに集約させる事ができます。

![スクリーンショット 2019-09-14 17.52.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/2c236b21-4438-fa9b-61d0-24dfa8bf40c7.png)

エッジデバイスでは少々重さを感じますが、Elasticsearch+Kibanaは可視化・BIツール類ではメジャーな部類ですので、技術習得しておくのは良いかと思います。
他のクラウドサービスでもそうなのですが、使いこなせるかわからないサービスに課金するのは非常に躊躇します。無料枠があったとしても、制限を気にしながら勉強は結構怖いものがあります。

ElasticStackはオンプレで動かせるパッケージを提供しているため、オンプレで十分に勉強してからクラウドサービスの検討ができる良い製品だと思います。