AWS Elasticsearch Service をお試してで使ってみたけど用途に対してコスパが悪いので、余ってる Raspberry Pi 3 Model B で運用することにした。Elasticsearch と Kibana を1台の Raspberry Pi 3 Model B で稼働させるのは重いので現在は ASUS Tinker Board や PINE64 Rock64 4GB を使ったり、Kibana だけ手元の Ubuntu で実行していたりする。
各種バージョン
Elasticsearch や Kibana は依存関係のバージョンが厳しいので(例えば Node.js のバージョンが 8.15.0 だと動かない、など)今回は下記の通りに合わせる。Java は Elasticsearch、Node.js は Kibana で使用する。
- Elasticsearch: 6.5.2
- OpenJDK: 1.8.0
- Kibana: 6.5.2
- Node.js: 8.14.0
- curator: 5.6.0
Elasticsearch のセットアップ
Debian 系で Elasticsearch を使う場合は DEB パッケージを使うか、MACOS/LINUX 用の TAR を解凍して使う方法がある。今回は保存用のストレージ等も分けるので TAR ファイルを使う方法にする。
Java Runtime Environment 8 をインストールする。特に Java で開発する予定もないのでヘッドレス版。
※そのうち直るかもしれないが、openjdk-8-jre-headless
のインストール後処理でエラーが発生する。もう一度 openjdk-8-jre-headless
をインストールすれば正常に終了する。
sudo apt-get update sudo apt-get install openjdk-8-jre-headless
Elasticsearch をダウンロードする。
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.5.2.tar.gz tar xf elasticsearch-6.5.2.tar.gz
elasticsearch-6.5.2/config/jvm.options
で JVM のメモリ設定を変更する。Raspberry Pi 3 Model B はメモリが 1 GB なので 512 MB くらいにしておく。
#-Xms1g #-Xmx1g -Xms512m -Xmx512m
Elasticsearch の設定を elasticsearch-6.5.2/config/elasticsearch.yml
で行う。xpack.ml.enabled: false
の設定が無いと起動しない。localhost 以外からのアクセスも許可するので transport.host
と transport.tcp.port
を設定する。
xpack.ml.enabled: false network.host: 0.0.0.0 http.port: 9200 transport.host: localhost transport.tcp.port: 9300
Elasticsearch を起動する。起動まで数分かかる。
elasticsearch-6.5.2/bin/elasticsearch
localhost:9200
にアクセスして Elasticsearch が起動しているか確認する。Raspberry Pi では avahi-daemon が有効になっているのでクライアントが対応していれば raspberrypi.local:9200
でもアクセスできる
curl localhost:9200
{ "name" : "Z0Eokyo", "cluster_name" : "elasticsearch", "cluster_uuid" : "NV6kGw3iQBiF7voqb6tIcA", "version" : { "number" : "6.5.2", "build_flavor" : "default", "build_type" : "tar", "build_hash" : "9434bed", "build_date" : "2018-11-29T23:58:20.891072Z", "build_snapshot" : false, "lucene_version" : "7.5.0", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search" }
Kibana のセットアップ
Kibana には ARM 版が無いので Linux x86_64 版をダウンロードする。
wget https://artifacts.elastic.co/downloads/kibana/kibana-6.5.2-linux-x86_64.tar.gz tar xf kibana-6.5.2-linux-x86_64.tar.gz
こちらもアクセス制限は設けないので kibana-6.5.2-linux-x86_64/config/kibana.yml
に server.host: 0.0.0.0
を設定する。
server.host: 0.0.0.0
Raspberry Pi などの ARM デバイスで Kibana を使う場合、同梱されている Node.js が x86_64 用だったりするので Node.js を別途用意する必要がある。ネットで一通り見た感じ「node/bin/node
をバックアップして…」とか書かれているが、あまり好きな方法ではないかな。
kibana-6.5.2-linux-x86_64/bin/kibana
を見てみると、自動で node
ファイルを探すようになっているが、実行できなければ which
で他の場所にある node
ファイルを探しに行くようになっている。
#!/bin/sh SCRIPT=$0 # 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" || NODE=$(which node) if [ ! -x "$NODE" ]; then echo "unable to find usable node.js executable." exit 1 fi NODE_ENV=production exec "${NODE}" $NODE_OPTIONS --no-warnings "${DIR}/src/cli" ${@}
で、同梱されている node
実行ファイルは ARM 上では実行できない。
$ node/bin/node -bash: node/bin/node: cannot execute binary file: Exec format error
同梱されている node
は使わないので実行権限を削除する。
chmod -x kibana-6.5.2-x86_64/node/bin/node
https://nodejs.org/dist/v8.14.0/ からデバイスに合ったバイナリをダウンロードする。
wget https://nodejs.org/dist/v8.14.0/node-v8.14.0-linux-armv7l.tar.xz tar xf node-v8.14.0-linux-armv7l.tar.xz
Node.js のインストールは下記のようなインストールスクリプトを使う方法もあるが、Kibana が Node.js のバージョンをチェックしているため、APT でアップデートが発生すると動かなくなってしまう可能性があり、Kibana との組み合わせではこの方法は向いていないと思われる。
curl -sL https://deb.nodesource.com/setup_8.x | bash - apt-get install -y nodejs
node
にパスを通して Kibana を起動する。
PATH=$HOME/node-v8.14.0-linux-armv7l/bin:$PATH kibana-6.5.2-x86_64/bin/kibana
Python でプログラムを書く
Python 3 用の PIP をインストールする。
sudo apt-get install python3-pip
今回はユーザ環境でしか使わないので ~/.config/pip/pip.conf
を下記のように設定しておく。「GPIO にアクセスするのに root 権限必要なんじゃ!」っていうデバイスの倍は sudo でどうぞ。
[global] user = true
PIP をアップグレードする。
pip3 install -U pip
PATH
を通しておく。
PATH=$HOME/.local/bin:$PATH
Python 3 用の elasticsearch
モジュールをインストールする。
pip3 install elasticsearch
まずは簡単に CPU の温度を投げるだけのプログラムを書いてみる。Python でタイムゾーンを扱う場合、datetime.now(timezone(timedelta(hours=+9)))
と書くのが最初は少々面倒に感じるが、慣れれば定型文のように感じる。
とりあえずデータを投げる場合
#!/usr/bin/env python3 from elasticsearch import Elasticsearch from datetime import datetime, timedelta, timezone es = Elasticsearch('localhost:9200') with open('/sys/class/thermal/thermal_zone0/temp') as f: timestamp, cpu_temp = datetime.now(timezone(timedelta(hours=+9))), int(f.read()) body = { '@timestamp': timestamp.isoformat(), 'temperature': cpu_temp, } response = es.index(index='foo', doc_type='_doc', body=body) print(response)
{'_version': 1, '_type': '_doc', 'result': 'created', '_seq_no': 0, '_index': 'foo', '_shards': {'failed': 0, 'total': 2, 'successful': 1}, '_id': 'RFfzK2gBykMjt7Ru8Nx9', '_primary_term': 1}
インデックスのセッティングとマッピングを追加する場合はこんな感じ。実際には毎回データを投げるわけではなく elasticsearch.helpers
を使って1秒間隔で取得したデータを60秒間隔でまとめて Elasticsearch に投げるようにしている。
セッティングとマッピングを行う場合
#!/usr/bin/env python3 from elasticsearch import Elasticsearch from datetime import datetime, timedelta, timezone import socket hostname = socket.gethostname() es = Elasticsearch('localhost:9200') # CPU の温度を取得 with open('/sys/class/thermal/thermal_zone0/temp') as f: timestamp, cpu_temp = datetime.now(timezone(timedelta(hours=+9))), int(f.read()) # インデックス名の生成(logstash-%Y.%m.%d と同書式) es_index = '-'.join([hostname, timestamp.strftime('%Y.%m.%d')]) # インデックス作成 if not es.indices.exists(index=es_index): es_setting = { 'settings': { 'number_of_shards' : 1, 'number_of_replicas': 0, } } es_mapping = { '_doc': { '_all': { 'enabled': False, } } } es.indices.create(index=es_index, body=es_setting) es.indices.put_mapping(index=es_index, doc_type='_doc', body=es_mapping) # ポストデータ作成 es_body = { '@timestamp': timestamp.isoformat(), 'hostname' : hostname, 'name' : 'temperature', 'value' : cpu_temp, } # ポスト response = es.index(index=es_index, doc_type='_doc', body=es_body) print(response)
elasticsearch.helpers で Bulk API
適当に書いたので真似しない方がいいかもしれない。
#!/usr/bin/env python3 from datetime import datetime, timedelta, timezone from elasticsearch import Elasticsearch, helpers from time import sleep import socket hostname = socket.gethostname() es = Elasticsearch() def getCpuTemp(): with open('/sys/class/thermal/thermal_zone0/temp') as f: timestamp, temp = datetime.now(timezone(timedelta(hours=+9))), int(f.read()) return timestamp, temp data = [] while True: timestamp, cpu_temp = getCpuTemp() es_index = '-'.join([hostname, timestamp.strftime('%Y.%m.%d')]) if not es.indices.exists(index=es_index): es.indices.create(index=es_index, body={'settings':{'number_of_shards': 1, 'number_of_replicas': 0}}) es.indices.put_mapping(index=es_index, doc_type='_doc', body={'_doc':{'_all':{'enabled': False}}}) source = { '@timestamp' : timestamp.isoformat(), 'hostname' : hostname, 'temperature': cpu_temp, } data.append({ '_index' : es_index, '_type' : '_doc', '_source': source, }) if len(data) >= 60: try: helpers.bulk(es, data) data = [] except: pass sleep(1)
X-Pack Monitoring を無効にする
Monitoring を使うと細かくヘルスチェックデータを保存してくれるけど Raspberry Pi 3 Model B ではそれ自体が重い(CPU クロックが常に上がりっぱなし)。しかもインデックスのサイズが毎日 900 MB くらいになる。
クラスタを組んでいるわけでもないし、個人で使う分には必要ないので X-Pack Monitoring を無効にする。
elasticsearch-6.5.2/config/elasticsearch.yml
xpack.monitoring.enabled: false
家の外からのアクセス
家の中であればプライベート IP アドレスで問題ないんだけど、外でちょっと人に見せたりとかする場合に。めんどくさくて家には VPN 入れてないので SSH でやっている。SSH のローカルフォワードで Elasticsearch と Kibana を動かしている Raspberry Pi にそれぞれ接続している。
例えば Elasticsearch が入った Raspberry Pi が 192.168.1.1
、Kibana が入った Raspberry Pi が 192.168.1.2
であれば下記のように ssh_config を設定すればよい。
Host home-bastion Hostname XXX.XXX.XXX.XXX Port XXXXX LocalForward 9200 192.168.1.1:9200 LocalForward 5601 192.168.1.2:5601
これで手元の Ubuntu からは curl で localhost:9200
にアクセスすれば Elasticsearch に繋がるし、ブラウザで localhost:5601
にアクセスすれば Kibana に繋がる。