自宅のElasticsearchとKibanaをAmazon Elasticsearch Serviceに引越し

例のごとく簡単なことしかやっていないのであまり参考にならないかもしれないですが、アクセスポリシーや自宅からどうやってAmazon Elasticsearch Serviceにデータを流すか、kibanaで閲覧するかは参考になる人がいるかもしれません。前置き終わり。

なぜMac MiniからAmazon Elasticsearch Serviceへ?

約二年前から自分に関連する可能な限りの情報(お金や位置情報、作業時間などなど…)をElasticSearchに放り込み、kibanaで可視化や検索がさっとできるような環境を自宅のMac Miniで運用していました。

そんなわけで二年ほどMac Miniで運用していましたが、以下のような理由で移行を考えてました。

  • Mac Miniも大分古い(5-6年前に買ったやつ)ので、そろそろ買い替えたい
    • SSDではなくHDDなので今となってはつらい。地獄かよ
    • 新卒入社直後だったので、金なしだった記憶がある…
  • ElasticsearchおよびKibanaのバージョンを上げたい
  • 仕事でAWS連携をやっているにも関わらず、個人ではAWSをほとんど使ったことがなく割と(かなり?)まずい
    • 使い勝手とかどういうメトリックが欲しくなりそう、というのは自分もドックフードしてみないと分からない

Mac Miniを新しく買い替えるのが一番無難でコスパも最終的にはよい気がしたのですが、ずっと個人でAWS使ったことないっていうのはどうなのか…というのがひっかかったのでAWS上で運用することにしました。EC2を借りて、そこでElasticsearchやKibanaをインストールして、というのでもよかったのですが、自分の性格的に色々できる環境だと色々やりすぎて本末転倒なことをやりそうな気しかしなかったので、Amazon Elasticsearch Serviceに移行することにしました。

AWSの設定

  • AWSのアカウント作成、クレジットカードの登録
    • MFAを有効化
  • グループの作成
    • Elasticsearchの読み書きができればいいので、AmazonESFullAccessのポリシーのみを持つグループを作りました
    • kibanaもダッシュボート等の情報を保存する必要があるので、readだけなくwriteの権限も必要

Amazon Elasticsearch Serviceの設定

  • Create a new domainで新しいドメインを作る
    • elasticseachのバージョンを選択。新しいのを使いたかったので、5.1を選んだ
  • Configure clusterでインスタンスのタイプを選択
    • elasticseachの5.1を選ぶにはmicroではなく最低でもsmallを選ぶ必要があるということにここで気づく…
  • Set up access policyでアクセスポリシーを選択
    • IPアドレスベースかロールベースかで選べる
    • 自分の場合、自宅や会社からkibanaが見れるといいなと思ったので、ロールで制限した

こんな感じにしておきました。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::XXX:user/es-manager"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:XXX:domain/YOUR_DOMAIN/*"
    }
  ]
}

これでElasticsearchやKibanaが立ち上がります。といっても立ち上がってくるまで20-30分くらいかかった気がする。アクセスポリシーを変更してそれが反映されるのも同じくらいの時間がかかるようです。

データを流す先をMac MiniからAmazon Elasticsearch Serviceに変える

自宅のMac Miniでは大体のタスクは

  • jenkinsでjobがキックされる
  • データ取得元からクロールする
  • 取ってきたデータをcurlでElasticsearchに流し込む

といった流れで動いていました(nasne残量の例自宅の回線速度を取る例)。例えばruby speedtest_logger.rb | curl -XPOST localhost:9200/_bulk --data-binary @-のエンドポイントをAWSに向ければ終わり、という予定でいましたが、アクセスポリシーの関連でそのままcurlでやるのは難しそうでした。aws-sdkを使うと対応できそうな雰囲気がありますが、Elasticsearchに流し込むプログラムはbash、ruby、perl、pythonと多岐に渡り(どうしてこうなった…)、流し込むjenkinsのjobも50程度まで増えていたので、困りました…。自宅も固定IP環境ではないので、IPのアクセスポリシーでやると運用が難しい。ここが一番時間がかかった。

先人の知恵を調べたところ、proxyを立てて、そこを通すという手が簡単そうでした。goなので簡単にビルドできる。go1.5じゃないといけないのが面倒といえば面倒ですが、割とすぐできる。

これでlocalhostでAWS上のElasticSearchが見えるようになりました。ruby speedtest_logger.rb | curl -XPOST localhost:9200/_bulk --data-binary @-とやっていたプログラムもポート番号を変えればよいだけなので、簡単に対応できます。これでデータはAmazon ElasticSearch Serviceに流れるようになりました。

kibanaも見れるようになりましたが、しかしちゃんとは動いていないようでした(jsが4xxのエラーを吐いていて管理画面が真っ白)。これについてもnodeでproxyを立てて解決する、というのをdockerに作っている人がいたので、それを使うと動かせるようになりました。

環境変数でawsのkeyを渡します。

docker run -e AWS_ACCESS_KEY_ID=XXX -e AWS_SECRET_ACCESS_KEY=XXX -p 9200:9200 rlister/aws-es-kibana search-XXX.ap-northeast-1.es.amazonaws.com

kibana5だ、わいわい。

f:id:syou6162:20170319151121p:plain

各種情報をMackerelで見れるように

Amazon ElasticSearch Serviceの管理画面からは検索可能なドキュメント数やディスク残量などが分かるようになっています。

f:id:syou6162:20170319151425p:plain

しかし、普段はMackerelのダッシュボードを見るようにしているので、見る画面が増えるのは面倒です。mackerel-agent-pluginsを使って、同じ情報をMackerelにも登録しましょう。

mackerel-agent-pluginsはMacには公式対応していませんが、それぞれのプラグインはMacでも簡単にビルドして使うことができます(正確に言うと使えるものもあります。elasticseachは大丈夫)。GOPATHに移動してbuildするとよいです。

% cd go/src/github.com/mackerelio
% git clone https://github.com/mackerelio/mackerel-agent-plugins.git
% cd mackerel-agent-plugins/mackerel-plugin-elasticsearch
% go build

./mackerel-plugin-elasticsearchで動かしてみるとよいでしょう。これで準備は終わったので、設定ファイル(/usr/local/etc/mackerel-agent.conf)に書きましょう。ひとまずこんな感じで書きました。

[plugin.metrics.elasticsearch]
command = "/Users/yasuhisa/go/src/github.com/mackerelio/mackerel-agent-plugins/mackerel-plugin-elasticsearch/mackerel-plugin-elasticsearch -port=9876"

agentを再起動するとちゃんとメトリックが取れるようになっています。これでディスク残量等も監視できるようになって安心ですね。

f:id:syou6162:20170319151543p:plain

AWS童貞を卒業したので、暇ができたらlambdaでbotを作って遊んだりしてみようと思います。

データベースリファクタリングやデータ移行のタスクの進め方

よくある当たり前っぽい内容ですが、はてなに入る前はあまりやったことがなかったので勉強しながらやっていました(解析器の結果をapiで見せるみたいなことが多かったので、DBそもそもほとんど使っていなかった…)。最近はデータ移行職人業務をやっている。

前提

前提があったほうが説明が書きやすい。is_hogeのようなbooleanなフィールドがhogefugapiyoのようにenumな値を取るように変更が必要という前提で話を進めます。

作戦: 下の層から丁寧にやっていく

一気にやると大変なことになるので、下の層からちびちび進めていきましょう。Pull Requestを送るときに↓のようなやることリストを付けておくと、全体のどの辺をやっているか分かりやすくなるのでレビュアーにやさしい感じになりそうですね。

  • [model層]hogefugapiyoを表わすようなフィールドを追加
  • [DB層]hogefugapiyoを表わすようなフィールドを追加
  • [model層]is_hogeに破壊的な操作(作成/更新/削除)をするときに、hogefugapiyoを表わすカラムにも変更を反映
  • [DB層]is_hogeカラムの状態からhogefugapiyoを表わすカラムの残りを埋める
    • 例: is_hogeがtrueだったらhoge、falseだったらfugaみたいなSQLを発行したり
  • [model層]is_hogeではなくhogefugapiyoカラムのみで値を返すようにする
    • is_hogeカラムには書き込まれなくなる
  • [DB層]is_hogeカラムをdropする
  • [controller層]必要だったらhogefugapiyoの状態を返す、設定するエンドポイントを生やす
  • [controller層]is_hogeを返すようなAPIがあるようだったらhogefugapiyoフィールドも追加して返すようにする
  • [frontend層]APIでis_hogeを見ている箇所を追加されたhogefugapiyoフィールドを見て処理するように変更
  • [controller層]不要だったらis_hogeを返すようなAPIは消す

基本的には

  • 最初は両方に書く
  • ロジックを新しいほうを使うようにする
  • 使わなくなった箇所を消す

というのを隣接する層毎にやっていくとスムーズでした。

知ってる && 試すといいやつ

CASE式

is_hogeカラムに値によってhoge_fuga_typeカラムの値を埋める、みたいなSQLを発行したいとき。boolなので、UPDATEを二回発行してもいいけど、CASE式を使うと一回で書けるのでよい。

UPDATE target_table SET
hoge_fuga_type =
  CASE is_hoge
    WHEN true THEN 0 -- hoge
    WHEN false THEN 1 -- fuga
  END
;

ALTER TABLEにかかる時間を計測

ALTER TABLEに予想しないくらい時間がかかって、サービスに影響が出るというのは避けたいので、backupサーバー等で時間をはかっておくと安心です。トランザクションを貼って、ROLLBACKすれば大丈夫。

hoge_fuga_service=> BEGIN;
BEGIN
hoge_fuga_service=> \timing
Timing is on.
hoge_fuga_service=> ALTER TABLE "target_table" ADD COLUMN "hoge_fuga_type" INT NOT NULL DEFAULT 1;
ALTER TABLE
Time: 82.552 ms
hoge_fuga_service=> ALTER TABLE "target_table" DROP COLUMN "hoge_fuga__type";
ALTER TABLE
Time: 7.523 ms
hoge_fuga_service=> ROLLBACK;

達人に学ぶDB設計 徹底指南書

達人に学ぶDB設計 徹底指南書

続Go言語に入門する

前回はこちら。書いてて楽しいので、地味に活動が続いている。

OSS活動

mackerel-client-go && mkr

mackerel関連のgoのライブラリとツールです。グラフアノテーション機能がリリースされたので、それをmkrで操作できるようにしました。mackerelなので仕事に関連しているのですが、週末に趣味時間でやっていました(Goの練習をしたかったのじゃ!)。

基本的にやることはWebAPIのラッパーなので難しいことはないんですが

  • goでのエラーハンドリング
  • コマンドライン引数周りの雰囲気
  • JSONの取り扱い
    • structを定義してjson decoderに投げればよい

が分かってよかったです。

自然言語処理

もう少し複雑だったりメモリやCPUを使う処理もgoで書いて感覚を掴みたいなと思ったので、次のステップとして係り受け解析器をgoで書いてみることにしました。絶賛WIPです。

Webアプリ

goでのwebアプリも書きたいという気持ちはあるけど、こちらはまだ未着手。既存のものを見たり、どのライブラリがよさそうか見たりしている。

みんなのGo言語【現場で使える実践テクニック】

みんなのGo言語【現場で使える実践テクニック】

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

データベース関連の最近の話題

最近といっても自分が新しく知ったとか使ってみたという感じのやつなので、全く目新しいやつではない。

PSequel

postgresqlのGUIクライアント。

ないと死ぬわけでもないし普段オペレーションするときはpsqlを使っているけど、テーブルの様子とかを見るのに便利。

  • カラムの定義(Not Nullとかディフォルト値とか型とか)、インデックス、外部キーの様子が一目で分かる
  • テーブルの一覧を絞り込み検索とかできる
  • GUI上でSQLも書ける
  • 好きなクエリをブックマークできる(これは特に使ってない)
  • CUIでもいいけど、何か考えながらやるときはGUIでやっているとやりやすいような気がする
  • めっちゃ複雑なことができるわけではない

トランザクションを張る

これもよくあるやつ(けど、あんまり自分やったことなかった)。更新処理とかをしたいんだけど、UPDATEミスってたら怖いしちゃんと反映されているかSELECTで確認してから正式に反映したい、というときにやる。些細な更新でもペアオペで一緒に見てもらいながらやると安心。

最初にトランザクション開始。

BEGIN;

更新処理をする。SQLをコピペすることが多いと思うけど、末尾のセミコロンを抜いてそこだけ手入力にすると最後に目視で確認するので安全度がちょい増す。

UPDATE hoge SET a = 0

結果を確認。

SELECT ...

よさそうだったら反映。

COMMIT;

ミスってた!!!という場合には反映しない。

ROLLBACK;

ROLLBACK前提で、UPDATEやALTER TABLEがどれくらい時間かかるのか試してみる、という用途でも使われる。時間を計測したいときには\timingとか打っておくと時間を出してくれるので便利。

CASE式

この本を読んで知ったやつ。

達人に学ぶ SQL徹底指南書 (CodeZine BOOKS)

達人に学ぶ SQL徹底指南書 (CodeZine BOOKS)

whereで分けて複数回クエリを発行しなくて済む。まだ読み込み足りていないけど、この本はSQL的世界観でどういうコードを書けばいいかを教えてくれるので結構参考になる。関数型で書くときには手続き型とは違った思考の方法をするけど、SQLを書くときには手続き型ではなく集合的な考えを使うあたり。

不定期ML&NLP報#4

最近の機械学習&自然言語処理に関する情報をまとめるコーナーです。前回はこちら。このエントリ忘れてるよというのがありましたら、たれこみフォームから教えてもらえるとうれしいです。

論文

  • [1701.07875] Wasserstein GAN
    • GANを含む生成系のタスクは難しいことが知られているが、学習時に使う距離をWasserstein距離というものを使うと学習が安定したという話

ブログ/勉強会資料

speakerdeck.com

機械学習モデルのバージョン管理をするstarchartというツールは知らなかった。解説記事もあった。

speakerdeck.com

機械学習というよりはログ基盤的な話。

www.slideshare.net

ビジネス

学会/勉強会

NIPS読み会

Kaggle Tokyo Meetup #2

全脳アーキテクチャ若手の会

AAAI2017

その他

機械学習のための連続最適化 (機械学習プロフェッショナルシリーズ)

機械学習のための連続最適化 (機械学習プロフェッショナルシリーズ)

  • 作者: 金森敬文,鈴木大慈,竹内一郎,佐藤一誠
  • 出版社/メーカー: 講談社
  • 発売日: 2016/12/07
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る
関係データ学習 (機械学習プロフェッショナルシリーズ)

関係データ学習 (機械学習プロフェッショナルシリーズ)

研究開発の実験に使えるホスト名の一覧をMackerelを使って素早く取得する

最近は仕事でサーバーの監視を行なうサービス開発(Mackerelチームに所属しています)の開発をしていますが、1年ほど前までは大学の研究室や企業の研究所で自然言語処理や機械学習の研究を行なっていましたid:syou6162です。研究開発における実験をするときにMackerelをこういう使い方をすると便利かも?というのがあったので、紹介します。

研究開発の実験あるある

研究では計算機をがしがし使って実験を行なうのですが、それらの計算機は当然一人で使うわけではなく、みんなで使います。他人がガンガン実験しているホストに途中からジョブを投下するのは迷惑ですし、実験によってはメモリが大きいマシンでないと実行できないなど、実験の前にあれこれ確認すべきことがあります。前職では例えばGangliaを見ながら

  • memoryが64GB以上積んでる
  • load averageが1以下

などの条件に合うホストを探して、GXPでホストを指定し、パラメータ変えて実験をぶん回すというのをよくやっていました。

しかし、実験をやる度にGangliaで計算機の混み具合を確認して…というのは率直に言って面倒でした。条件を指定したら使えるホスト名のリストを返して欲しいです。これを行なうシェルスクリプトを書いてはいましたが、サーバーの購入や撤退などによりホスト名のリストを定期的に更新する必要があるのもよくない点でした。

Mackerelを使って楽をする

Mackerelはサービスやロールといった概念でホストをまとめて監視することができます。例えば自分が所属しているグループでホストをグルーピングしたり、深層学習用のGPUのホストをまとめてみたり、といった形です。ホストにメタデータを登録、取得することができるので、それを使うのもいいかもしれません。その上でload averageが閾値以下のホストで条件付けして一覧を取得する、といったことがMackerelだと簡単にできます。サーバーのセットアップのついでにmackerel-agentも仕込んでおくようにする(ワンライナーでインストールできます)と、ロールに所属するホストの一覧などはMackerelのapiが返してくれるので、手元に一覧を持っておく必要はなくなります。これにより、実験に使えるホストをさくっと探すといったことができるようになります。

実例(Go言語による簡単なスクリプト)

例として"My-Machine"というロールに所属しているホストの中でload averageが1以下のホスト名のみを取得するというのをやってみましょう。Mackerelではコマンドラインツールや各種言語のライブラリ(下のはほんの一部)がOSSで用意されているので、それを使うと簡単です。

今回は私が最近Goに入門中なので、Goのライブラリを使ってみます。ちなみにGoのライブラリは最近のリリースで出たグラフアノテーションに対応しています:)

さて、さっそくコードです。以下のコードをgo run find_hosts_for_experiments.goといった形で動かすと条件に合うホスト名が出てくるので、あとはこれをgxpなどに投げればよいだけです。

 package main

 import (
     "fmt"
     mkr "github.com/mackerelio/mackerel-client-go"
     "os"
 )

 func main() {
     client := mkr.NewClient("YOUR_MACKEREL_API_KEY")
     // collect host names
     hostCandididates, _ := client.FindHosts(&mkr.FindHostsParam{
         Service: "My-Machine",
     })
     var hostIDs []string
     for _, h := range hostCandididates {
         hostIDs = append(hostIDs, h.ID)
     }

     // get metric values
     metricValues, err := client.FetchLatestMetricValues(hostIDs, []string{"loadavg5"})
     if err != nil {
         os.Exit(0)
     }

     // filter hosts by loadavg5
     for _, h := range hostCandididates {
         hostID := h.ID
         loadavg5, ok := metricValues[hostID]["loadavg5"]
         if !ok {
             continue
         }
         if loadavg5.Value.(float64) < 1.0 {
             fmt.Println(h.Name)
         }
     }
 }

ロールに所属するホストの一覧を取得して、そのホストのloadavg5も取得、それらが1以下なら出力するだけという簡単なものです。Rubyでも似たようなことが簡単にできると思います。

まとめ

Mackerelはサーバーの監視や状態の通知を簡単に行なうことができるので、それ自体はもちろん便利ですが、ホストの情報を使うことで研究開発を少し便利にすることができるという例を紹介しました。

Go言語に入門する

先週末、仕事で東京に行く機会があって、往復の新幹線の中でまとまった時間が取れるなと思ってGo言語に入門していました。Go言語に入門する理由は

  • 仕事で普通に使う(mackerel-agentなど)
  • 職場で評判がいい
  • 一年に一個は新しい言語を勉強しようっていうアレ

などなどです。本やtour of goを読むだけだとあまり頭に入ってこないので、複雑過ぎず簡単過ぎない例をやろうということで平均化パーセプトロンで二値分類を書きました(ファイルの読み込み、文字列操作、スライス、Mapの基本的な操作、structの使い方などがちょっと身に付く)。Perlに再入門、Scalaを勉強するときもこのネタだったので、定番になってきつつある。コードはこの辺に置いてます。

ちょっと困った箇所

失敗するかもしれない計算の戻り値の型

scalaでいうところのOption[XXX]を返したい。結論から言うと(x *XXX, err error)を返すのがよさそう(XXXはstructを想定)。計算がちゃんとできたときはreturn &x, err、失敗したときにはreturn nil, errを返すという感じ。ポインタ型だとゼロ値がnilなので、参照ではなくポインタを返すことが多いらしい。

一行が長すぎるとreadlineで失敗する

この辺。20newsをデータでやっていたら一行の入力が長すぎて失敗するときがあった。isPrefixで行末いくまで待つ、というのが解決法っぽい。

ほぼ初めての言語を新幹線のオフライン環境でやるための工夫

新幹線でなければ普通にぐぐればいいんだけど、新幹線だとぐぐれず困ることがあるだろうということで前日に準備した。

オフラインでTour of Go

Tour of Goに基本的な概念がまとまっていてよいのだが、オフラインで見れないので見れるようにする。ローカルでサーバーが立つので、それで解決。

go get golang.org/x/tour/gotour
~/go/bin/gotour

Emacs、REPL

この辺のツールがひたすら充実している。go-mode入れて、godefとかを入れておくと関数の定義元にさっと行ったり、変数の型、関数の引数/戻り値の型がさっと見れて最高だった。ensimeを動かすのに結構苦労したあの時間は何だったんだろうという簡単さ。

dashでドキュメントを見る

Tour of Goは目次はあるけど、検索ができないのでdashで引けるようにする。Emacsからサクっと引けるので最高だった。

みんなのGo言語【現場で使える実践テクニック】

みんなのGo言語【現場で使える実践テクニック】