Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

Elastic{ON} 2016のため、サンフランシスコにやってきた! #elasticon

おはようございます @cero_t です!
やってきましたサンフランシスコ、今回の目的はいつものJavaOneではなくElastic{ON}です。

f:id:acro-engineer:20160218053113j:plain:w480

行きの飛行機はボーイング787、経由地のロサンゼルスは気温30℃越えと、なんかバカンスに来たとしか思えないような様子ですが、安心してください、仕事しますよ!


さて、
本日2/17(水)から3日間かけて行われるElastic{ON}では、Elasticsearchのテクニカルな情報や、ユースケースなどが発表されます。
Elasticsearchはバージョンアップも頻繁ですし、GitHub上で開発状況やissueもよく見えているので、それなりに今後どうなるかを把握しているつもりではあるのですが、サプライズ情報だったり、Deep Diveな内容に期待をしています。

場所はPier48。桟橋の上でイベントって 何を考えてるのか どういう様子なのか分からないので、その辺りも楽しみです。


なお現在は、イベント前のプレトレーニングを実施中。
ELKのスタートアップハンズオンと、1000台越えのスケーリングに関するセッションの2択。同僚が後者を受講中ですので、あとでフィードバックしてもらおうと思います!

ところでElastic{ON}、なんか良い感じの朝食やエスプレッソマシンが用意されています。
elastic最高かよ。
f:id:acro-engineer:20160218030538j:plain:w320

そんなわけでこれから3日間、このイベントのレポートをしていきたいと思います。
Stay elastic, see you!

レポート一覧

3日目

Elastic{ON} 2016レポート AMA = Ask Me Anything! #elasticon - Taste of Tech Topics
Elastic{ON} 2016レポート いよいよクロージング! #elasticon - Taste of Tech Topics

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、Elasticsearch、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 
データ分析で国内に新規市場を生み出す新サービス開発者WANTED! - Acroquest Technology株式会社の新卒・インターンシップ - Wantedlywww.wantedly.com

思い切ってelasticのTimelionを取り入れてみた

こんにちは。
先日「elasticの新プロダクト「Beats」シリーズに震える!」
を投稿したPlNOKlOです!

AcroquestではENdoSnipeBVDというサービスで経営の可視化を行っており、
私も2年目ながら可視化のお手伝いをさせてもらっています。

今回、とあるお客様から、
稼働率を表現したい」という要望をいただきました。

見たいグラフを打ち合わせした結果、
線グラフエリアグラフを同時に表示する必要がでてきました。

ですがご存知の通り、Kibanaでは
線グラフなら線グラフ、エリアグラフならエリアグラフと、
一つのグラフしか出力できないんです。

ではどうするか・・・

JavaOne2015に登壇し、
私のボスでもある、
@cero_t さんに相談したところ、
「試しにTimelion使ってみたら?」
とアドバイスをもらいました。



Timelion・・・?
Timelionとはなんぞや??

調べたところ、Timelionは「タイムライン」と読み、
完全に独立したデータソースを1つのインターフェイスに集約します。
このインターフェイスは、データの取得、時系列の結合、変換、視覚化を
まとめたシンプルな1行の式言語で駆動します。
Timelionの式はすべてデータソース関数で始まり、
例えば、.elasticsearch(*)(短縮形は.es(*))のように表記します。
1つの図に2つのグラフを組み合わせる場合は、グラフをカンマで区切ることで表示できるようなんです。


ほう。
なるほど。
Timelionは独自の関数を用いることで、
グラフを組み合わせて表示できるものなんですね。


それでは早速触ってみましょう!


Timelionで触ってみた


今回稼働率ということで、
昨年の稼働時間を比較するグラフを作成します。

昨年の稼働時間が20%増えた時間を赤のエリアグラフ、
10%増えた部分を黄色のエリアグラフ、
10%減った部分を青色のエリアグラフ、
そして今年の稼働時間を緑の線グラフとして
表示してみましょう。


f:id:acro-engineer:20160201103205p:plain


Timelionはこういった感じです。
上にあるウインドウで関数を書いてグラフを作成します。

使用できる関数はヘルプから見ることができます。


f:id:acro-engineer:20160201103234p:plain


デフォルトで書いてある「.es(*)」は
elasticsearchのインスタンスからデータを取得するという関数です。

ではまずは今年の稼働時間の線グラフを作成してみましょう。
es関数の中で使用できるのはこちらです。


f:id:acro-engineer:20160201103249p:plain


"index"でindexを指定し、
"q"でQueryを指定し、
"metric"で縦軸の計算方法を指定し、
"timefield"で横軸の時間を指定し、
esの後に"color"関数を加えることで色を指定することができるようですね。

これらを用いて書いたのがこちら。

.es(index='dailymonitoring', q='line:"今年実際値"', metric='sum:hour', timefield='date').color(green)

表示してみると・・・


f:id:acro-engineer:20160201103500p:plain


おおおおおおおおおおおおおおおおおおおおお!!!!!!!!!
簡単ですね!簡単すぎて吼えてしまいますね!!
この調子で次は赤のエリアグラフを出してみましょう!!


それでは同じ要領で.esを書いてみると、

.es(index='dailymonitoring', q='line:"20%増"', metric='sum:hour', timefield='date').color(red)

このようになります。

しかしこのままでは折れ線のままになってしまいます。
なので、ヘルプを見てみましょう。

グラフの形を指定する関数は
".lines"と".bars"の二つが用意されています。



・・・・ん?あれ??
エリアは???



・・・NOOOOOOOOOOOOOOOOOOOOOOOOOOOOO!!!


そう!そうなんです!
エリアグラフを表現できる関数がないんです!!
思わず夜空にライオンのごとく雄叫びです!!
夜空ノムコウ!らいおんハート!!



「Timelionはまだ開発中だからな・・・」
「諦めよう・・・」
なんて弱気な私が囁いてきました。

だがそこに割って入ったのが強気な私!

諦めないで!」と、囁いてきたのは、そうです。
強気な私というか、あれですね。
石鹸のCMの時の真矢みきさんですね。
私の心の中にいる真矢みきさんでした。


元気が出てきました。
私には宝塚出身のスターがバックについていてくれると思うと
心強くなってきました!
きっと夜空に吼えたのも心の中の真矢みきさんです!
深夜を迎えておりますがこの勢いに乗って他の関数も見てみましょう!!


es関数をもう一度見てみましょう。

f:id:acro-engineer:20160201103234p:plain

やはりこの中にはありませんね。

では、エリアグラフは折れ線の下を塗っているグラフなので、
次はlines関数を見てみましょう。

f:id:acro-engineer:20160201103728p:plain

お。

fill : Number between 0 and 10. Use for making area charts

これ怪しいですね。使ってみましょう!

.es(index='dailymonitoring', q='line:"20%増"', metric='sum:hour', timefield='date').lines(fill=10).color(red)

こちらを表示すると・・・


f:id:acro-engineer:20160201103803p:plain


おーー!!!!
できました!!!!!

それではすべての線を合わせてみると・・・


f:id:acro-engineer:20160201103819p:plain


ぅおおおおおおおおおおおおおおおおおおお!!!!!!!!!!!!!!!!!
完成しました!!!
全ては真矢みきさんのおかげです!!!!!


というわけでTimelionだけに吼えまくりでしたが、
エリアグラフと線グラフは、
両方ともlines関数を駆使して出力することで
表示できました!



裏トーク

実は裏では他にもいろいろ試して苦労したことがあるんです。

例えば、グラフのタイトルを出したくても出せなかったり、
Timelionで作成したグラフの説明をするために、
Kibanaのdashboardでvisualizationと一緒に出そうとしても出せなかったり、
表示する期間を保存したくても保存できなかったりと、
なかなかやりたいことができず苦戦しました。

ということで、
Timelionの発展期待しております!(笑)


Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
データ分析で国内に新規市場を生み出す新サービス開発者WANTED! - Acroquest Technology株式会社の新卒・インターンシップ - Wantedlywww.wantedly.com

Elasticsearchを使うときの注意点を社内で発表しました。

こんにちは、fujiiです。


最近、社内でもElasticsearchを使う機会も増えてきてました。

インストールも簡単ですし、
ちょっと設定するだけでログの解析も手軽にできるので、
便利だと思って使い始めるのですが、

・必要以上に負荷のかかる設定になっている。
・システムダウンしたときの復旧を考慮していなかった。
・初期構築などで大量にデータを入れようとしたら、一部データが入っていなかった。

と、後から反省することも多くあります。
(私も最初は知らないことが多く、反省点ばかりでした)


そのため、Elasticsearchを初めて使うときの注意点をまとめ、
社内で講習会を開きました。



以下に公開しました。
実際に案件で利用しようと思った時にパフォーマンスなどで迷うこともありますので、
そんな時の一つの参考になればと思います。

www.slideshare.net


社内向けの資料なので、分かりづらいところは、ご容赦ください(^^;


参考:Elasticsearchインデクシングパフォーマンスのための考慮事項 - Qiita



なお当社では、Elasticsearchの構築サービスと、Elasticsearchの無料入門セミナーを行っています。

詳細は、こちらのURLまで!
http://www.acroquest.co.jp/elastic/


それでは。

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 データ分析で国内に新規市場を生み出す新サービス開発者WANTED! - Acroquest Technology株式会社の新卒・インターンシップ - Wantedly

Springfox+Swagger+Bootprintによる即席REST API仕様書作成 ~制約編~

こんにちは、阪本です。

以前、「Springfox+Swagger+Bootprintによる即席REST API仕様書作成」というエントリーを書きましたが、今回はパラメータの制約をドキュメントに反映する方法について確認してみます。
なお、今回はSpringfoxのバージョンを2.3.1にしています。

@ApiModelPropertyによる制約の指定

まずは、Swagger Annotationを使ってパラメータの制約や説明の追加を行ってみます。

前回使用したEmployeeクラスに、@ApiModelPropertyアノテーションを追加します。

package swagger.entity;

import java.util.Date;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;

import io.swagger.annotations.ApiModelProperty;

public class Employee {
    private Integer id;
    private String  name;
    private Date    birthday;

    @ApiModelProperty(value = "Employee ID.", allowableValues = "range[1, 100]")
    @Range(min = 1, max = 100)
    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @ApiModelProperty(value = "Employee's name.", required = true, allowableValues = "range[0, 32]")
    @NotEmpty
    @Size(max = 32)
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @ApiModelProperty(value = "Employee's birthday with ISO 8601 format.", required = true)
    @NotNull
    public Date getBirthday() {
        return this.birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}

value属性にプロパティの説明、requiredに必須かどうか、allowableValuesに値の範囲を指定します。他にも、例を指定したりプロパティ名を変更したりもできますが、ここでは割愛します。

上のようにEmployeeクラスを記述してSpringBootアプリケーションを起動すると、次のようにSwagger UIに反映されます。

http://localhost:8080/swagger-ui.html
f:id:acro-engineer:20160113022055p:plain

画面右側のModel部分に情報が反映されていることがわかります。idプロパティのinteger部分にカーソルを合わせると、allowableValuesに指定した範囲も見えます。
ちなみに、日付フォーマット等の、必須・範囲以外の制約については、value属性に説明として記述する必要があります(専用の属性がありません)。

バリデーション用のアノテーションから制約条件を取得する

上で説明した方法では、JSR-303のアノテーション(@NotNullや@Size等)の情報を取ってきているのではなく、あくまで@ApiModelPropertyの値を取ってきてドキュメントを生成しているに過ぎません。
プログラムで動作するバリデーション用アノテーションとドキュメント用アノテーションを両方記述するのは、手間なのとズレが発生するのとで、避けたいところです。
ということで、バリデーション用のアノテーション(JSR-303等)から制約条件を取得できるように修正を加えます。

残念ながら、現時点(2016年1月)の最新バージョンであるSpringfox 2.3やSwagger 1.5では実現できないため、自前でコードを書く必要があります。
具体的には、ModelPropertyBuilderPluginインタフェースを実装したクラスを作成します。@Componentを付けて、SpringBootのComponentScan対象にしておきます。

package swagger;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.google.common.base.Optional;

import springfox.documentation.builders.ModelPropertyBuilder;
import springfox.documentation.service.AllowableRangeValues;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;

@Component
public class Jsr303ModelPropertyBuilderPlugin implements ModelPropertyBuilderPlugin {

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    @Override
    public void apply(ModelPropertyContext context) {
        ModelPropertyBuilder builder = context.getBuilder();

        // プロパティのgetterを取得する
        Optional<BeanPropertyDefinition> beanPropDef = context.getBeanPropertyDefinition();
        BeanPropertyDefinition beanDef = beanPropDef.get();
        AnnotatedMethod method = beanDef.getGetter();
        if (method == null) {
            return;
        }

        // 必須・非必須を取得する
        NotNull notNull = method.getAnnotation(NotNull.class);
        NotEmpty notEmpty = method.getAnnotation(NotEmpty.class);
        if (notNull != null || notEmpty != null) {
            builder.required(true);
        }

        // 範囲制約を取得する
        Range range = method.getAnnotation(Range.class);
        if (range != null) {
            builder.allowableValues(new AllowableRangeValues(
                    Long.toString(range.min()), Long.toString(range.max())));
        }
        Size size = method.getAnnotation(Size.class);
        if (size != null) {
            builder.allowableValues(new AllowableRangeValues(
                    Long.toString(size.min()), Long.toString(size.max())));
        }
    }
}

Jsr303ModelPropertyBuilderPluginクラスのapplyメソッドで、各プロパティのgetterについているアノテーションを取得し、その内容に応じてModelPropertyBuilderに制約条件を設定しています。
#「Jsr303」というクラス名にしていますが、Hibernate Validatorのアノテーションも処理対象に加えています^^;

上記クラスを作成しておけば、次のように、Employeeクラスの@ApiModelPropertyから制約に関連する属性(requiredとallowableValues)を削除し、冗長な記述を排除できます。
(getterのみ抜粋して記載)

@ApiModelProperty(value = "Employee ID.")
@Range(min = 1, max = 100)
public Integer getId() {
    return this.id;
}

@ApiModelProperty(value = "Employee's name.")
@NotEmpty
@Size(max = 32)
public String getName() {
    return this.name;
}

@ApiModelProperty(value = "Employee's birthday with ISO 8601 format.")
@NotNull
public Date getBirthday() {
    return this.birthday;
}

これらを実施した上で再度SpringBootアプリケーションを起動すると、先ほどと同じSwagger UIページが生成されます。
必要に応じて対応アノテーションを増やす必要はありますが、冗長な記述は排除できました。

bootprint-swaggerで静的ドキュメント生成!

ということで、前回同様、bootprint-swaggerでHTMLのAPI仕様書を生成してみます。

f:id:acro-engineer:20160113024921p:plain

なかなか難しい表現が出てきました・・・(汗)
{ x ∈ Z | 1 ≤ x ≤ 100 } のZは、代数学で言うところの「整数の集合」を表しています。
あと、nameのところは (up to chars) と出力され、具体的な文字列長が出てきませんでした。
ちょっとイマイチ感がありますね。。

Bootprint側のカスタマイズは調べてみようと思います。

それでは。

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 競合は海外・外資!国内に新規市場を生み出す新サービス開発者WANTED! - Acroquest Technology株式会社の求人 - Wantedly

Elasticsearch勉強会 第14回フィードバック

こんにちは、fujiiです。

第14回elasticsearch勉強会に参加してきました。

f:id:acro-engineer:20160108014007p:plain


途中、電車遅延の影響で、間に合わないかと焦りましたが、
何とかギリギリで到着でした。

冬なのに、汗かきましたよ。。。


さて、本題に戻り、勉強会の内容を紹介しますね。

ここがツラいよElasticsearch

リクルート高林さんによる「ここがツラいよElasticsearch」

リクルートさんが行っているElasticsearchの活用事例と、
実際に困った問題を教えてくれました。

前半は、リクルートさんのElasticsearchの活用事例の紹介でした。

こちらについては、以前、ブログで紹介しているので、
以下を参照ください。

Elastic{ON} Tokyo 2015レポート 〜事例紹介 その1 リクルートテクノロジーズ、Naver #elasticon - Taste of Tech Topics


で、本題のElasticsearchの辛いところですが、

1.バージョンアップが辛い。
2.Riverが非推奨になったのが辛い。
3.フィールドに「.」が使えないのが辛い。
4.Replication-Async非推奨が辛い。
5.ズレが辛い。

ということでした。


すごい勢いでバグFixや機能追加を行っているので、
頻繁に更新される一方、
APIも大幅に変更されて困った、
というバージョンアップの辛さはよく耳にしますね。


「ズレが辛い。」というのは気になるところですが、
クラスタを組んでいる場合に、リロケート、リバランス、レプリカ生成中に、
Primaryに更新が行われた場合に、Documentにズレが発生するというもののようです。

後で詳しい話を@ さんに聞きましたが、
consistencyの設定の関係があるそうです。

クラスタを組んでいる場合、
Elasticsearchのデフォルトでは、ノード数 / 2 + 1 のノードのデータが更新されれば、
更新に成功したとみなす動きをします。

例えば、3つのレプリカを作る場合、2ノードで更新処理が成功した場合に、
更新が成功したという動きです。

で、ネットワークの切断などで、
残りの1ノードの更新が失敗していたらどうなるかというと、
更新が失敗した状態で、動いてしまい、そのままでは復旧しないそうです。

これを防ぐには、consistencyをall にする(Index API)と解消されます。

ただし、すべてのレプリカの更新を待つことになるので、インデックスの更新が遅くなり、検索にも影響が出てきます。
システムの特性に合わせて使う必要がありますね。


リクルートさんでは、レプリカのDocumentのズレを検出したら、
もう一度、過去ログの投入からやり直したそうです。


リクルートさんの苦労が感じられました。

機械学習を利用した、ちょっとリッチな検索

PFN久保田さんによる「機械学習を利用した、ちょっとリッチな検索」


機械学習を使うと、どのようなメリットがあるのか、
機械学習はどのように始めればよいのか、
という内容でした。

今回は、記事の分類などで使われる「文書分類」を基に、
機械学習を使う方法を教えてくれました。

簡単に紹介すると、以下のサイクルを覚えるとよいとのこと。

1.ラベル定義
2.データ収集
3.教師データの作成
4.学習
5.適用

また、機械学習の結果をElasticsearchに投入する方法としては、
以下を利用しているとのことでした。

1.オフラインで適用してからElasticsearchに投入する。
2.Jubatusのプラグインを使う。


初心者向けにかみ砕いて説明してくれたので、
わかりやすかったです。

Lucene Query 再考 - Domain Specific Query 実装 -

最後は、Supershipの大川さんによる「Lucene Query 再考 - Domain Specific Query 実装 -」です。

QueryParserに関する基礎知識と
Supershipさんが取り組んでいる内容を説明してくれました。

Elasticsearch では QueryDSLでは、種々のパーサーを使えるが、
1.近傍検索を行うSpanQueryに対応していない。
2.Term結合やSynonymなどの揺らぎに対応できない。

という課題があり、Query Parserのプラグインを開発したとのことでした。


Qiitaにも関連する内容をあげているそうなので、合わせて参照ください。

Domain Specific Query Parser (Hetero Grammatical Query Parser) 実装 - Qiita



実際にPJで適用したときの苦労話も色々きけて、
勉強になりました。

引き続き、参加したいと思います♪

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 データ分析で国内に新規市場を生み出す新サービス開発者WANTED! - Acroquest Technology株式会社の新卒・インターンシップ - Wantedly

Beatsのつくりかた - ヒープとGCを取得するJstatbeatsを実装してみた

Hello, world! @ です!
このエントリーはElasitcsearch Advent Calendarの20日目になります。


先日、Elastic{ON} Tour 2015 Tokyoが行われました。

世界各地で行われたイベントの中で最後となる東京では、無料イベントにも関わらず、
セッションは充実しているし、資料が印刷されて、しかも翻訳までされているし、
ランチに人形町今半のお弁当が配られるし
帰るときにもらったノベルティグッズが、なんとElasticロゴ入りモバイルバッテリーだったり、
とんでもないホスピタリティあふれるイベントでした。

Acroquestもelastic社のパートナーとして提携して、今回もデモスポンサーとして参加しましたが
それ以外にも何かイベントの盛り上げをお手伝いができないかなと思い、リアルタイムブログに挑戦してみました。

当日のブログ一覧 : http://acro-engineer.hatenablog.com/search?q=%23elasticon

同僚のサポートもあり、いずれもセッション終了から数分以内にはレポートをあげることができました!


ということでこのAdvent Calendar、元々はElastic{ON}のことを書こうかと思っていたのですが、
既に全部レポートしちゃったので、代わりに、Beatsの作り方について紹介したいと思います。

Beatsのつくりかた

Beatsとは何ぞやというところは、以前にPINOKIOが書いたエントリーを見てください。
acro-engineer.hatenablog.com

Beatsの作り方は、公式ドキュメントのDeveloper Guideにまとまっています。
https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html

この手順に沿いつつ、JavaのヒープサイズやGCの回数をjstatコマンドで取得する、
「Jstatbeat」を作ってみたいと思います。


なお、今回作成したコードは、Githubで公開しています。
https://github.com/cero-t/jstatbeat

こんな感じのダッシュボードを作ることができます。
f:id:acro-engineer:20151220045735p:plain
サクッと作れる割に、良い感じです。


ちなみにBeatsシリーズはGo言語で書かれているため、Goの開発環境を事前に作っておく必要があります。
僕はIntelliJ + golang-pluginを使っています。

Goは文法的にも取っつきやすく、チュートリアルも充実しており、習得しやすいと感じています。
クロスプラットフォーム向けコンパイルや、メモリフットプリントの小ささなどメリットも大きいので、
まだGoを学んでない方は、この機会にぜひ習得してみてください。

1. 概要を把握する

まずは簡単にBeatsの実装を知るために、Topbeatのソースコードを確認します。
https://github.com/elastic/beats/tree/master/topbeat

Topbeatの本体ソースコードは、4つしかありません。
各ソースの代表的な部分とともに紹介します。

1. /main.go (https://github.com/elastic/beats/blob/master/topbeat/main.go)
メイン関数です。実質的にはbeatを起動する処理を1行書くだけです。

func main() {
	beat.Run(Name, Version, topbeat.New())
}

2. /beat/config.go (https://github.com/elastic/beats/blob/master/topbeat/beat/config.go)
設定ファイルと1:1のパラメータを持つ構造体です。

type TopConfig struct {
	Period *int64
	Procs  *[]string
	Stats  struct {
		System     *bool `yaml:"system"`
		Proc       *bool `yaml:"process"`
		Filesystem *bool `yaml:"filesystem"`
		CpuPerCore *bool `yaml:"cpu_per_core"`
	}
}

3. /beat/sigar.go (https://github.com/elastic/beats/blob/master/topbeat/beat/sigar.go)
Topの情報を作成するための、gosigarのラッパー関数群です。詳細は割愛します。

4. /beat/topbeat.go (https://github.com/elastic/beats/blob/master/topbeat/beat/topbeat.go)
処理の本体です。一番重要な関数群です。

func (tb *Topbeat) Config(b *beat.Beat) error {
	// 略
}

func (tb *Topbeat) Setup(b *beat.Beat) error {
	// 略
}

func (t *Topbeat) Run(b *beat.Beat) error {
	// 略
}

func (tb *Topbeat) Cleanup(b *beat.Beat) error {
	return nil
}

func (t *Topbeat) Stop() {
	close(t.done)
}

この5つのメソッドを実装することで、beat本体となるBeaterを実装することができます。

参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#_developing_the_beat_specific_code


自分でbeatを作る際には、libbeatという共通ライブラリを利用することで実装量を抑えることができるため、
1、2、4に相当するコードだけを書けば良いわけです。

参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#_overview


要するに周辺作業はlibbeatがすべてやってくれるので、
個別のbeatはデータを取るところに注力すれば良いわけです。

2. libbeatのソースをgo get

よし作ってみよう、という気になったら、まずはlibbeatの取得をします。
参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#_getting_ready

公式ドキュメントの通りにtopbeatをgo getで取ってくれば、libbeatも一緒に取得できます。

go get github.com/elastic/topbeat

これで開発の準備ができました。

3. 設定ファイルと、設定の構造体を作る

まずは設定ファイルの作成と、その設定を読み込む処理を作成します。
参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#config-method


Jstatbeatでは「GC情報を取得する間隔(ミリ秒)」と、jpsを実行した時に取れる「プロセス名」の
2つを設定できるようにします。

設定ファイルはtopbeatのものをコピーして、設定部分だけ変えるのが良いでしょう。
https://github.com/elastic/beats/blob/master/topbeat/etc/topbeat.yml

/etc/jstatbeat/jstatbeat.yml

input:
  interval: 5000
  name: "Elasticsearch"

  # 略


そして、この設定ファイルに対応する構造体を作ります。

/beat/config.go

package beat

type JstatConfig struct {
	Interval *string `yaml:"interval"`
	Name     *string `yaml:"name"`
}

type ConfigSettings struct {
	Input JstatConfig
}


続いて、本体となるJstatbeatクラスのうち、設定ファイルを読み込むConfig関数を作成します。

/beat/jstatbeat.go

package beat

// import文は省略

type Jstatbeat struct {
	// from configuration
	interval string
	name     string // (1)

	// state
	pid     string
	isAlive bool
}

func (jsb *Jstatbeat) Config(b *beat.Beat) error { // (2)
	var config ConfigSettings
	err := cfgfile.Read(&config, "") // (3)
	if err != nil {
		logp.Err("Error reading configuration file: %v", err)
		return err
	}

	jsb.name = *config.Input.Name // (4)

	if config.Input.Interval != nil {
		jsb.interval = *config.Input.Interval
	} else {
		jsb.interval = "5000"
	}

	return nil
}


(1) Jstatbeat自身のフィールドとして interval と name を定義しておき
(2) Config関数の中で
(3) 設定ファイルを読み込んで
(4) 読み込んだ値を、フィールドに代入します。

これでConfig関数は完成です。
ね、簡単でしょう?

4. 起動時の処理を作る

続いて、起動時の処理を行うSetup関数を作成します。
起動時の処理では、jpsコマンドを実行して、監視対象となるJavaプロセスのプロセスIDを取得します。
参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#setup-method


/beat/jstatbeat.go

type Jstatbeat struct {
	// from configuration
	interval string
	name     string

	// state
	pid     string // (1)
	isAlive bool
}

func (jsb *Jstatbeat) Setup(b *beat.Beat) error { // (2)
	cmd := exec.Command("jps") // (3)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		logp.Err("Error get stdout pipe: %v", err)
		return err
	}

	cmd.Start()

	// TODO: handle error when 'jps' command cannot be executed.

	scanner := bufio.NewScanner(stdout)
	for scanner.Scan() {
		line := scanner.Text()
		items := strings.Split(line, " ")

		if len(items) == 2 && items[1] == jsb.name { // (4)
			jsb.pid = items[0]
			break
		}
	}
	cmd.Wait()

	if len(jsb.pid) == 0 {
		logp.Err("No target process: %v", jsb.name)
		return errors.New("No target process: " + jsb.name)
	}

	return nil
}

(1) Jstatbeatのフィールドとして pid を定義しておき
(2) Setup関数の中で
(3) jpsコマンドを実行して
(4) 設定で指定した name に一致するプロセスの pid を取得します

5. メインの処理を作る。

いよいよ、一番のメイン処理となるRun関数の実装です。
https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#run-method


処理が少し長いので、いくつかのブロックに分けて説明します。

/beat/jstatbeat.go

func (jsb *Jstatbeat) Run(b *beat.Beat) error {
	jsb.isAlive = true

	cmd := exec.Command("jstat", "-gc", "-t", jsb.pid, jsb.interval) // (1)
	stdout, err := cmd.StdoutPipe()

	if err != nil {
		logp.Err("Error get stdout pipe: %v", err)
		return err
	}

	cmd.Start()
	defer killProcess(cmd) // (2)

	// 略

	return nil
}

func killProcess(cmd *exec.Cmd) {
	if cmd.Process != nil {
		err := cmd.Process.Kill()
		if err != nil {
			logp.Err("Error killing jstat process: %v", err)
		}
	}
}

(1) jstatコマンドに-gcオプションと-tオプションをつけて実行します。
また、pidやintervalは、それぞれSetupとConfigで取得したものを渡します。

(2) コマンドを実行した後は、エラーなどで処理が止まってしまって良いように
deferでプロセス停止の関数を呼ぶようにしておきます。


/beat/jstatbeat.go

var blanks = regexp.MustCompile(`\s+`)

func (jsb *Jstatbeat) Run(b *beat.Beat) error {
	jsb.isAlive = true

	// 略

	scanner := bufio.NewScanner(stdout)
	var keys []string
	var version string
	for jsb.isAlive && scanner.Scan() { // (3)
		line := scanner.Text()

		values := blanks.Split(line, -1) // (4)

		if len(values) > 2 && values[0] == "Timestamp" {
			keys = values // (5)

			if strings.Contains(line, "CCSC") { // (6)
				version = "java8"
			} else {
				version = "java5"
			}

			continue
		}

		// 略
	}

	return nil
}

(3) jstatの結果を一行ずつ読み出しながら
(4) 正規表現を使って、空白で値を分割しています。
(5) 1行目(見出し行)の場合は、分割した値をkeysとして保持しておきます。
(6) また念のため、jstatで取れた情報から、JVMJava5〜7なのか、Java8だったのかを判断しています。
この判断結果は、elasticsearchのtypeとして利用します。


/beat/jstatbeat.go

func (jsb *Jstatbeat) Run(b *beat.Beat) error {
	// 略

	for jsb.isAlive && scanner.Scan() {
		line := scanner.Text()

		values := blanks.Split(line, -1)

		// 略

		event := common.MapStr{ // (7)
			"@timestamp": common.Time(time.Now()),
			"type":       version,
		}

		for i, key := range keys { // (8)
			if len(key) == 0 {
				continue
			}
			event[key] = toFloat(values[i+1]) // (9)
		}
		b.Events.PublishEvent(event) // (10)
	}

	return nil
}

func toFloat(str string) float64 {
	value, err := strconv.ParseFloat(str, 64)

	if err != nil {
		logp.Err("Cannot parser to float. Ignore this value: %v", err)
		return 0
	}

	return value
}

(7) jstat実行結果の2行目以降に対して、行に対応する内容をMapとして作成し、
(8) すべての項目に対して
(9) 項目 = 値となるように、Mapに格納しています。また値はstringではなくfloat64に変換してから渡しています。
(10) そして、それをlibbeatのPublishEvent関数を使って送信します。


要はRun関数では、Mapを作ってPublishEventする、ということです。

Java5とJava8の両方に対応させるため、若干、処理がややこしくなっていますが、
簡単なbeatを作るだけなら、ホントに十数行で済むんじゃないかと思います。

6. 終了時の処理を作る

残るCleanup関数とStop関数は、まとめて紹介します。
https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#cleanup-method
https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#stop-method


Cleanup関数は処理が終わるとき(正常時、異常時とも)の処理を書きますが、
ここでは特にやることがないので、何の処理も書いていません。

Stop関数は、SIGTERMを受信した時の処理を書きます。
ここではフラグを落として、ループが終わるようにしています。

/beat/jstatbeat.go

type Jstatbeat struct {
	// from configuration
	interval string
	name     string

	// state
	pid     string
	isAlive bool // (1)
}

func (jsb *Jstatbeat) Run(b *beat.Beat) error {
	jsb.isAlive = true // (2)

	// 略

	for jsb.isAlive && scanner.Scan() { // (3)

	// 略
}

func (jsb *Jstatbeat) Cleanup(b *beat.Beat) error {
	return nil
}

func (jsb *Jstatbeat) Stop() {
	jsb.isAlive = false // (4)
}

(1) フィールドとして定義した isAlive を
(2) Run関数で処理の前に true にしておき
(3) ループ中は必ず確認するようにして
(4) Stop関数が呼ばれた際に false にします。
こうすることで、SIGTERM受信時には処理が中断できるようになります。

7. main関数の作成

最後にメイン関数の作成です。
Jstatbeatのインスタンスを作成して、それをlibbeatに渡すだけです。
参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#_the_main_function


/beat/jstatbeat.go

func New() *Jstatbeat { // (1)
	return &Jstatbeat{}
}

/main.go

package main

import (
	jsatbeat "./beat" // (2)
	"github.com/elastic/libbeat/beat"
)

func main() {
	beat.Run("jstatbeat", "0.1", &jsatbeat.Jstatbeat{}) // (3)
}

(1) go言語お作法に従って、Jstatbeatのインスタンスを作成するNew関数を用意しておきます。
(2) また、beatという名前空間がlibbeat被るため、jstatbeatというエイリアスをつけておき
(3) main関数では、実質1行で処理を書いてしまいます。
このRunの第一引数で渡した"jstatbeat"は、設定ファイル名や、Elasticsearchのindex名などでも
利用するため重要です。index名で使う都合上、名前は英語の小文字だけにしてください。


これで本体ソースコードは完成です。
ほら、簡単でしょう?

8. いざ実行!

main.go、beat/config.go、beat/jstatbeat.goの3つのソースコードと、
jstatbeat.ymlを /etc/jstatbeat/jstatbeat.yml におけば、いよいよ実行です。

go run main.go

これでエラーなく動作するようであれば、Elasticsearchで確認しましょう。

http://(Elasticsearchのアドレス):9200/_cat/indices にアクセスして
jstatbeat-yyyy.MM.dd なindexができていれば成功です。

動作しない場合には、elasticsearchのログか、
Macの場合は /var/log/system.log 辺りを見ると、エラーが出ている可能性があります。


これだけでも動作自体には問題ないのですが、index定義をjsonとして作っておいたほうがより親切です。
topbeatのtemplateを参考にすると良いです。
https://github.com/elastic/beats/blob/master/topbeat/etc/topbeat.template.json

9. ダッシュボード

最後に、kibanaでダッシュボードを作りましょう。
今回は、こんな感じのダッシュボードを作ってみました。

f:id:acro-engineer:20151220045735p:plain

なお手動でダッシュボードを作ったあと、dashboardやvisualizationを
kibana上でexportしておくと、別の環境でもimportすることができます。

なのでexportしたものを配布物と一緒に提供するのが良いかと思います。

公開しました。

ということで、意外とさっくりとbeatを作れることが分かってもらえたかと思います。

今回は簡単にjstatを実行しただけなので、言ってしまえば別に、
jstatをファイルに出力してfilebeatやlogstashで送ってしまえばいいって話になります。

もう少し役立つものにするためには、複数プロセスや複数サーバに対応するとか
あるいはJMX経由でもう少し情報を取ってくるとか、そういうことが必要かと思います。
ただ逆に、そういうものを集める方法を統一するために、Beatsがピタっとハマりそうです。


冒頭にも書きましたが、今回作成したコードは、Githubで公開しています。
https://github.com/cero-t/jstatbeat

また、Windows/Mac/Linuxのそれぞれ向けのバイナリや
ダッシュボードの設定ファイルも含んだファイルをリリースしています。
https://github.com/cero-t/jstatbeat/releases

よかったら参考にしてください。


それでは、
Stay beats, See you!


Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

Elastic{ON} Tokyo 2015レポート 〜Elasticスタックを使用したセキュリティ関連のユースケース #elasticon

本日最後のセッション、
Elasticsearchをセキュリティ用途で使えないかと注目している皆さんにとっては
インパクトある内容だったと思うのが、このセッション。

elasticのNicholas Limさんによる
「Security Use Cases with Elastic Stack」です。

f:id:acro-engineer:20151216170512j:plain

セッション中には、デモを交えた事例が紹介されました。

一つめが、AntiVirusのイベントログを分析することで
ウイルス感染した件数、感染したホスト数、影響を受けたユーザ数などを可視化するものです。
(食らった後の話かー!)

続いて、IDS/IPSのログから、イベントの総数や、攻撃者数、標的数などを可視化するダッシュボード。
また、Firewallのイベントログから、ソースIP、攻撃先IPとポート数などを可視化するダッシュボード。
(いずれも、私たちが作ろうとしているものに近いです、参考になる!)


そしてWatcherを通じて、メールやHipChatに通知を送ることができます。
HipChatとつなぐというアイデアは、星野リゾート様の事例にも出てきましたが
最近はチャットツールAPI経由でのアクセスが当たり前になってきているので
通知先としてもかなり有効です。


またセキュリティログを分析する際、情報ソースとなるFirewallやミドルウェア
プロダクトごとにログ形式は異なっており、自前で解析する必要があります。

そこで推奨されていたのが、標準に則ることです。
STIX/CEF/CIM/LEEFなどのセキュリティロギングの標準に従ってログ出力し
その解析をElasticsearchで行うというものです。


私はまだセキュリティ関係は勉強中なので、改めて復習します!(><)


さー、LTやでー!