読者です 読者をやめる 読者になる 読者になる

$shibayu36->blog;

プログラミングの話や自分の考えを色々と書いています。特にperl、emacsや読んだ本の話が多いです。

プロジェクト単位でElasticsearchのローカル開発環境を作成する

tech elasticsearch

 最近Elasticsearchを触っていて、まずはローカル開発環境を作ることになった。ただ、Elasticsearchはバージョンがかなり変わり、別プロジェクトと利用したいバージョンが異なっていたので、プロジェクト単位で使い分けるにはどうすればよいかを考えた。

やりたいこと

  • プロジェクトごとにElasticsearchのバージョンを切り替えられるようにしたい
    • MySQLだったら同じようなものにmysqlenvなどがある
  • また、他の人が一瞬で環境を整えられるようにしておきたい
  • バージョンを上げる時もローカル環境なら簡単にあげたい

作戦

  • Elasticsearchはここ からダウンロードして、特定のディレクトリに展開するだけでも利用できる
  • そこでプロジェクトルートのelasticsearch/ディレクトリ以下に展開し、ここに展開したバイナリを利用するようにする
  • 一瞬でセットアップできるように、Elasticsearchのセットアップスクリプトを用意し、そこでいい感じにセットアップできるようにする
  • セットアップスクリプトのバージョン情報を少しいじるだけで、アップグレードも行えるようにする

セットアップスクリプトを作る

 セットアップスクリプトに求める機能要件は以下のとおり。

  • 指定したバージョンのElasticsearchが自動でダウンロードされ、elasticsearch/ディレクトリ以下に展開される
  • 必要なプラグインが自動でインストールされる
  • バージョン情報を変えると、自動でそのバージョンに変更してくれる

 また、このセットアップスクリプトの非機能要件としては以下のとおり。

  • 何度実行しても同じ状態になる(冪等である)
  • 必要ないのにデータのダウンロードをしない(高速である)

 冪等であれば、何かあったらもう一回セットアップスクリプトを実行してくれたら直るとか、プラグインを追加した時も何も考えずセットアップスクリプトを実行してくださいということができ、利用が簡単である。また高速であれば、例えば他の開発者に簡単に利用できるようにローカルサーバ起動時に自動でセットアップスクリプトを呼ぶなどをしても、特にストレス無く開発できる。

 このような要件を満たすセットアップスクリプトPerlで作成した。特にPerlである必要はなく、適当に好きな言語を使ってもらっても作れるはず。

setup-local-elasticsearch.pl

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;

use feature qw(say);

# バージョン情報
my $ELASTICSEARCH_VERSION = '2.3.4';

# tarをdownloadして、elasticsearch/以下に配置するコマンド
# 指定したバージョンのダウンロードリンクからダウンロードし、elasticsearch/ディレクトリ以下に展開する
my $get_elasticsearch_command = <<"COMMAND";
curl -O https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/$ELASTICSEARCH_VERSION/elasticsearch-$ELASTICSEARCH_VERSION.tar.gz 2>/dev/null;
tar zxf elasticsearch-$ELASTICSEARCH_VERSION.tar.gz;
mv elasticsearch-$ELASTICSEARCH_VERSION elasticsearch;
rm -f elasticsearch-$ELASTICSEARCH_VERSION.tar.gz
COMMAND

# ---- ElasticSearchをインストールする ----
if (-e "elasticsearch/") {
    # elasticsearch/ディレクトリがあれば、バージョンの変化がないかチェックし、
    # 変化があれば更新する
    my ($version) = `elasticsearch/bin/elasticsearch --version` =~ /\AVersion: ([0-9.]+)/;
    unless ($version eq $ELASTICSEARCH_VERSION) {
        say "update elasticsearch...";
        system "rm -rf elasticsearch";
        system $get_elasticsearch_command;
        say "elasticsearch update was done";
    }
}
else {
    # そもそもelasticsearch/ディレクトリがなければ新規でセットアップ
    say "Download and install elasticsearch...";
    system $get_elasticsearch_command;
    say "elasticsearch setup was done";
}

# ---- Javaのバージョンの確認を行う ----
# https://www.elastic.co/guide/en/elasticsearch/hadoop/current/requirements.html#requirements-jdk
# によると、JDK 8以上が強く推奨されているのでチェックを行っておく
my ($java_version) = `java -version 2>&1` =~ /java version "(\d+\.\d+)/;
unless ($java_version >= 1.8) {
    say '';
    say "!!!!!!!! NOTICE !!!!!!!!";
    say "Elasticsearch requires recent java.";
    say "Please download and install recent java from the following URL.";
    say "http://www.oracle.com/technetwork/java/javase/downloads/index.html";
    exit 1;
}

# ---- 必要なpluginのインストールを行う ----

# すでにインストールされているプラグインをリストアップ
my $is_installed = do {
    my $output = `elasticsearch/bin/plugin list`;
    my @list = $output =~ /^    - (.+)$/mg;
    +{ map { $_ => 1 } @list };
};

# plugin listで表示される名前と、インストールに利用する名前は違うため
# [ (plugin名), (installに渡す名前) ] の組を書いておく
my $plugins = [
    [ 'head'                     , 'mobz/elasticsearch-head' ],
    [ 'elasticsearch-inquisitor' , 'polyfractal/elasticsearch-inquisitor' ],
    [ 'analysis-kuromoji'        , 'analysis-kuromoji' ],
    [ 'analysis-icu'             , 'analysis-icu' ],
];
for my $plugin (@$plugins) {
    my ($name, $install_name) = @$plugin;
    # すでにインストールされているなら省略
    next if $is_installed->{$name};

    say "Install $name plugin...";
    system "./elasticsearch/bin/plugin install --batch $install_name >/dev/null 2>&1";
}

 このスクリプトを実行しさえすればElasticsearchのセットアップやプラグインのインストールを自動で行うことができる。またすでにセットアップが終わっていればElasticsearchのダウンロードやプラグインのインストールをしないので、二回目以降は高速に実行が出来る。もしバージョンを変えたくなった場合も、スクリプトの最初の方にある$ELASTICSEARCH_VERSION変数を変更して、セットアップスクリプトを実行し直すだけですぐに変更できる。

 あとはこれをプロジェクト全体のセットアップスクリプトに組み込んだり、初期セットアップフローにこれを実行するようにしてもらえば、チームメンバーも簡単にセットアップできるようになる。自分のプロジェクトではプロジェクト全体のセットアップスクリプトがあり、それがローカルサーバ起動時に呼ばれるようになっているので、そちらに組み込んでいる。


 セットアップしたElasticsearchは以下のように起動すれば良い。クラスタ名も明示的に付け、他のプロジェクトと誤ってクラスタリングされないようにしておく。

$ elasticsearch/bin/elasticsearch --cluster.name=myproject

まとめ

 今回はElasticsearchをプロジェクトごとにセットアップする方法についてまとめてみた。個人的にはミドルウェアを入れる場合は、他のメンバーに如何に何も考えずに利用してもらえるようにするかというのを考えていて、このようなセットアップスクリプトを用意するようにしている。参考になればと思う。

horensoを使ってcronの実行時間を自動でmackerelに記録する

tech

 最近cronの実行にはhorenso というツールを使って、実行エラーがあればSlackに通知するなどといったことをしている。今回はcronの実行時間をmackerelのサービスメトリックに自動で記録するということをやったのでメモ。

やりたいこと

  • crontabに以下の形式でスクリプトを登録すれば、自動でサービスメトリックに記録される
    • horenso --tag (tag名) --reporter=elapsed-time-to-mackerel-reporter.pl -- (実行したいコマンド)
  • 記録は秒数で、小数3桁まで記録する

horensoの動き

 horensoではreporterとして適当なスクリプトを指定することが出来て、そのスクリプトには標準入力に実行のログ等が渡ってくるようになってくる。例えば

$ horenso --reporter=test-reporter.pl -- perl -E 'say 1; warn 2;'

とすると、以下のようなJSONがtest-reporter.plに標準入力として渡ってくる。

{
  "command": "perl -E 'say 1; warn 2;",
  "commandArgs": [
    "perl",
    "-E",
    "say 1; warn 2;"
  ],
  "output": "1\n2 at -e line 1.\n",
  "stdout": "1\n",
  "stderr": "2 at -e line 1.\n",
  "exitCode": 0,
  "result": "command exited with code: 0",
  "pid": 95030,
  "startAt": "2015-12-28T00:37:10.494282399+09:00",
  "endAt": "2015-12-28T00:37:10.546466379+09:00",
  "hostname": "webserver.example.com",
  "systemTime": 0.034632,
  "userTime": 0.026523
}

 ここで実行開始時間を表すstartAtと実行終了時間を表すendAtの差分を利用すれば、実際に動いている時間を取ることが出来る。

 また、次のように--tagを使って名前を入力しておくことで、実行のJSONにtagというキーで指定した名前を取得できる。こちらはメトリックに投稿するためのラベルとして利用する。

$ horenso --tag say --reporter=test-reporter.pl -- perl -E 'say 1; warn 2;'

perlで実行時間をmackerelに投稿する

 先ほど紹介したとおり、reporterには標準入力に実行ログが入ってくるので、どんなスクリプトを指定してもよい。今回は自分が慣れているperlを利用してみる。

elapsed-time-to-mackerel-reporter.pl

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;

use JSON::XS qw(encode_json decode_json);
use LWP::UserAgent;
use JSON::Types ();

use DateTime::Format::W3CDTF;

# 標準入力を読み込み、JSON decodeする
my $json = do { local $/; <> };
my $data = decode_json($json);

my $start_at = DateTime::Format::W3CDTF->parse_datetime($data->{startAt});
my $end_at   = DateTime::Format::W3CDTF->parse_datetime($data->{endAt});

# 小数点3桁までの実行秒数を計算する
my $elapsed_time = sprintf('%.3f', $end_at->hires_epoch - $start_at->hires_epoch);

# 自分が使っているservice名を利用
my $service_name = 'cron';
# グラフがcron_elapsed_time.*でまとまるように
# cron_elapsed_time.{tag名}という名前で投稿する
my $metric_name  = 'cron_elapsed_time.' . $data->{tag};
# api_key
my $api_key = '...';

my $ua = LWP::UserAgent->new;
$ua->post(
    "https://mackerel.io/api/v0/services/$service_name/tsdb",
    'X-Api-Key' => $api_key,
    'Content-Type' => 'application/json',
    Content => encode_json([{
        name  => JSON::Types::string($metric_name),
        time  => JSON::Types::number($start_at->epoch),
        value => JSON::Types::number($elapsed_time),
    }]),
);

reporterを利用してmackerelに実行時間を記録する

 あとはこのreporterと、適当なタグを指定して、horensoを使ってスクリプトを実行すれば良い。例えば以下の通り。

$ horenso --tag echo --reporter=elapsed-time-to-mackerel-reporter.pl -- sh -c 'echo 123;sleep 1'
$ horenso --tag sleep --reporter=elapsed-time-to-mackerel-reporter.pl -- perl -E 'sleep int rand(10);'

 1つ目は適当にechoして1秒sleepするようなコマンドで、これにはechoというタグを付けた。2つ目はランダムで1~9秒間sleepするようなもので、これにはsleepというタグを付けた。

 これを適当に何回か実行することで以下のようにmackerelに実行時間がプロットされる。

f:id:shiba_yu36:20160725060537p:plain

 あとはcrontabでは

horenso --tag (tag名) --reporter=elapsed-time-to-mackerel-reporter.pl -- ...

という形式でジョブを登録しておけば、全てのcronジョブの実行時間を記録できるようになる。

投稿したメトリックを監視する

 さらにサービスメトリックに投稿だけしておけば実行時間監視も監視できる。例えば以下のように設定しておけば、sleepタグの付いたジョブの実行時間が5秒を超えるとWarning通知し、10秒を超えるとCritical通知するということが出来るだろう。

f:id:shiba_yu36:20160725061012p:plain

まとめ

 今回はcronのジョブの実行時間を自動でmackerelに投稿することをやってみた。horensoはreporterとしていくつも登録できるので、例えばnagios監視と連携したり、エラーが起こった時にSlack通知したりといろんなことが出来る。非常に便利なので是非使ってみてください。

「HTML5/CSS3モダンコーディング」読んだ

book

 最近自分のプロジェクトのデザイナが、JavaScriptで実装しないといけないと思っていたことをどんどんCSSで実装していくのを見かけた。この出来事から、JSで実装したほうが良いか、デザイナにやってもらったほうが良いか、どちらが良いかを自分で判断できてないと感じたので、最近のCSS事情を知りたいと思って読んだ。サラッと流し読みするだけで、CSS3で利用できるようになったよく使うプロパティの概要を把握できたので、今の自分の知りたいことが分かって非常に良かった。

 例えば以下のことを学ぶことが出来た。

  • reset.css, normalize.cssとはなにか
  • beforeやafter擬似要素を使ったいろんな技
  • アニメーションを設定するtransition
  • 要素の移動、回転、変形を行うtransform

 デザイナはもちろんのこと、フロントエンドのことをやっているエンジニアも軽く読んでおくと良さそうに思った。