Taste of Tech Topics

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

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やでー!

Elastic{ON} Tokyo 2015レポート ~ElasticスタックとTipsの紹介 #elasticon

ElasticスタックとTipsの紹介

ここからは、テクニカルセッションです。
まずはelastic大谷さんによる、「ElasticスタックとTipsの紹介」です。

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

冒頭に「Elasticsearch使ってるひとー」っていうアンケートというか客いじりがあったんですが
8割ぐらいが手を挙げるぐらい、今日はElasticsearchユーザの多いイベントでした。

Elastic{ON}とは言え、すごいですね。

Logstash

まずは、Logstashの紹介です。

セッションでも度々出てきたLogstashですが、
ここではApacheアクセスログの設定を例にして、具体的な設定が紹介されました。

grokでパースして、
dateで日付フォーマットを指定して、
useragentでユーザエージェントを加工して、
geoipで、IPアドレスから緯度と経度を取りました。

Tipsというよりはむしろこれこそが標準的な設定なので、
まずはここから始めましょう!
(個人的には、この機能をElasticsearch側にもつけて欲しいです!)

Elasticsearch

次はElasticsearchのノウハウ。
今回、説明されたのは、以下の4つでした。

1. Index template
2. multi_field
3. dynamic template
4. numeric_detection


Index templateは、要するにRDBMSで言うところのDDLみたいなものです。
スキーマレスなElasticsearchですが、ここは数値にしたいとか、ここはgeoipにしたいとか
また全文検索のような構文解析分かち書き)しなくて良い、という定義をしたくなります。
そのような定義ができるのが、index templateです。
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html

multi_fieldは、一つのフィールドに対して、複数のフィールド定義を持たせるものです。
たとえば「国名」というフィールドに対して「分かち書きした国名」と「分かち書きしてない国名」の
両方を定義する、というようなことができます。
https://www.elastic.co/guide/en/elasticsearch/reference/current/_multi_fields.html

ただ、このような定義を、いちいち全部のindexに定義するのは面倒なものです。
そこで使えるのがdynamic templateです。
たとえば「stringはすべて分かち書きしない」とか
「こういう名前に一致するものは全部(日付ではなく)文字列とする」というような定義ができます。
https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-templates.html

最後のnumeric_detectionは、文字列ではなく数値として定義するような機能です。
(ドキュメントが、見つからなかった・・・)

Kibana

次はKibanaアプリのSense。

すごく平たく言えば、
Elasticsearch用にカスタマイズされたREST API用クライアントです。

コマンドやindex名、クエリなどの補完が効くので、
ようやくクエリを学び始めた私みたいなエンジニアには便利すぎる機能です。
curlコマンド用の文字列としてコピーする機能もあるので、重宝します。


最後は、visualize-spy。
これ凄く気づきにくい機能なんですが、KibanaのVisualizationって
一番下に ▲ というボタンがあり、これをクリックすると、
Elasticsearchへのリクエスト、レスポンスなどを直接確認することができます。

Kibanaの出力が期待通りにならないから
ちょっと中を見てみたい、という時に使える機能です。
また、Kibanaが発行しているクエリを見て勉強するためにも使えます。

それにしても、気づきにくい機能ですよね。
spyなどと言わず、もっと目立って!


というTipsが多いセッションですが、いかがでしたか?
知らなかった、これ試したい、というものが多ければ私も嬉しいです!

Elastic{ON} Tokyo 2015レポート 〜事例紹介 その3 星野リゾート、ゴールドマンサックス #elasticon

事例紹介その3は、星野リゾートと、ゴールドマンサックスです。
これで事例紹介は最後となります。

星野リゾートにおけるELKを活用した可視化と共有の取り組み

続いては、星野リゾートの久本英司さんによる
星野リゾートにおけるELKを活用した可視化と共有の取り組み」です。

私たちも一緒に取り組みをさせていただいています!(><)


星野リゾートは創業101年のホテル・旅館の運営会社で、
メディアなどに取り上げられる機会も増えて、認知度が非常に高まっています。

一般的に、ホテルや旅館は縦割り組織が多く、
フロントはフロント、清掃は清掃、レストランはレストランという形ですが、
星野リゾートでは、いわゆる「多能工」で、スタッフはマルチタスクで動いています。

そのほかにも、全員で経営判断をできるようにするとか、フラットな組織文化にするなどして、
顧客満足と生産性の両立を目指しています。


特に経営判断に必要なのが、情報共有です。

星野リゾートでは積極的な情報共有を行っていますが、
既存のシステムが古くて使い物にならないものがあったり、
KPIを収集する仕組みが世の中の変化についていけないとか、
あるいはセキュリティ面での課題があり、改善が必要となりました。

改善のアプローチとして、あらゆる経営情報を蓄積しておける場所が必要となり
ここにAcroquestからの提案で、Elasticsearchを利用することにしました。

手書きの絵やマインドマップを使ったビジョン共有を行い、
必要最小限のダッシュボードを作って見せてフィードバックを受け取り、
また改善したものを見せてはディスカッションしてフィードバックを受け取り、
ということを数ヶ月繰り返しました。

そのようなものを利用したところ、

1泊宿泊制限が解除されたタイミングで急に予約数が増えることが分かった
 → 制限が解除されるまで、多少、予約が少なくても大丈夫

とか、
他にも予約が入るタイミングが年々早くなっていること、
会員数が順調に伸びていることなどが、はっきり可視化されました。


将来構想としては、あらゆるデータを投入し、
Timelionや、Sparkとの連動、HipChatなどとの連携を進めたり、
たとえば大量キャンセルなどのタイミングでアラートを上げるなどの
業務に直結する部分のシステム化を進めようとしています。

またIoTの導入による生産性や満足度の向上、
需要予測・稼働予測、稼働調整なども視野に入れています。


実際に動いているものを見たい方は、ぜひAcroquestのブースまで!(>▽<)ノ

Goldman Sachs Engineering

続いては、事例紹介としては最後となります、
Goldman SachsのIan Macleanさんによる「Elastic in Equity Finance」です。

ワークフローやトレーディングオーダーの検索、法規文書やレジュメの全文検索
JVMやリソースなどのメトリクス、アラート、データ解析など
幅広い範囲でElasticsearchを利用しています。

GitHubからElasticsearchのソースをクローンして内部でビルドしており、
独自プラグインを用いて、セキュリティやバックアップの強化を行っているとのことです。
(マジか!)


Elasticsearchのメリットとして、サポートを挙げているところが興味深かったです。
設計レビューやパフォーマンスチューニング、パッチの適用など
サポートで得られるメリットが大きいとのことです。


事例としては、オーダーの検索が説明されました。
オーダー情報はSybaseに保存していましたが、とにかくレコードが多すぎたため
DBをかなり分割しなくてはいけなくなりました。

また検索も時間が掛かり、数時間かかるものがありました。
そのため、意味のある分析をするのが難しかったのです。
(そうですよね、検索が遅いと、分析を試すこともなかなかできない)


そこでElasticsearchを導入しました。
履歴データをElasticsearchに入れ、取引時間中のデータもインデックスしておき、
分析や可視化に利用します。Aggregationを使った高速な分析も行うようにしました。

そして、独自に「Sharp-X」という名の、管理/分析ダッシュボードを作成しました。
オーダーのデータを市場ごとに分析できるダッシュボードです。
履歴データと現在のデータを比較した分析や、取引でエラーが起きてないかどうかの確認などができます。

結果、アーキテクチャソースコードも、かなりシンプルなものになりました。


Elasticsearchは、開発が容易で、スケーラブルな、Game changerです。
今後、RDBMSから、HadoopとElasticsearchを用いたシステムにを置き換えることを計画しています。
(なんか私たちとやりたことが似てる!)


どちらも刺激的なセッションですね。
星野リゾート様のセッションは技術寄りよりも経営寄りの興味深い話でしたし、
Goldman Sachsは力の入れ方に圧倒される一方で、次にやりたいことなどが
非常によく似ているので、ぜひディスカッションさせてもらいたいなと思いました。


さて、事例紹介は以上となり、次はテクニカルセッションです!