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

その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

雑に特定のホストの特定のポートと疎通できてるかどうか確かめる

新しくサーバ立てた時やサーバ追加した時に,そのサーバが他のホストの特定のポートと疎通できるかどうかチェックする必要が出てくる時がある.ACLとかの兼ね合い.
そういうのは本番の環境だと監視のシステム等に組み込まれていたり,あるいはserverspecとかで確認されていることが多いと思うのだけれど,その場で雑に確認したくなることがあると思う.そういう時はtelnetで繋いで,quitして,というのを繰り返していく感じになりがちなのだけれど,対象となるホストの数が多くなってくるとそういうことを手でやるのも大変になってくる……というわけでこれです.

$ (sleep 0.1; echo quit) | telnet $HOST $PORT)

こういう風にしておくとtelnetで繋いだ後にquitを発行するということを自動でやってくれる.
とはいうものの,こうすると正しく繋げているかどうかを目視で確認する必要が出てくる (つまり以下の様な文字列が正しく出力されているかどうかを目視で確かめる必要がある).

Trying ::1...
Connected to localhost.
Escape character is '^]'.

更にこの方法だと Connection closed by foreign host となるために,exit codeが1となってしまう.その為,スクリプト中でexit codeを使って良い感じにするのが難しい.


というような話をしていたら,id:karupaneruraさんから「それncで出来るよ」と教えてもらって以下の様な感じになった.

if [ -z $((sleep 0.1; echo "\nquit") | nc $HOST $PORT) ]; then
    echo "Fail $HOST:$PORT"
fi

見た目としてはtelnetの代わりにncを使って,送り込む文字列を quit から \ncode にしたという様子.
前者はまあその通りという感じ.後者について少し説明すると,ncで正しく繋げている場合に先に改行文字を送り込むと ERROR というレスポンスがncから返ってきた後にquitする.一方で正しく繋げていない場合はncから何もレスポンスが返ってこないという挙動になるので,それを利用して文字列が空か空ではないかで正しく繋げているか否かを判断している (つまり文字列が空でなければ正しく繋げていると判断している).

なお補足すると,ncだとこういう風に動くんだけど,CentOS6のncatコマンドだとquit時に+OKという文字列が出てくるので最初に改行文字を送り込む必要は無かったりする.みたいな感じで微妙にnc (ncat) コマンドの挙動に差があったりするので,そこら辺はまあ臨機応変に対応しましょうという感じ.


これを適当にforの中に組み込んでおくと雑に疎通のチェックが出来て便利,という話でした.ncを使うという発想がなかったので勉強になった.


[追記]

コメントでid:bearminiさんが /dev/tcp を使う方法を教えてくれたんですが,これめちゃめちゃ便利ですね.知らなかった……

tinyorm 1.11.0 is out

java

tinyormのversion 1.11.0が出ました.

Maven Repository: me.geso » tinyorm » 1.11.0

新しい機能としては

に書いたように

  • Connection を2つ持てるようになった
  • Lazily な connection borrowing に対応した

という2つが挙げられます.
rc1の時点では中々にバギーだったんですが,改善を重ねていった結果,プロダクションでも安定して動作するようになったのでこの度rcを外して正式なバージョンとしてリリースしました *1

今回入った機能についてはConnectionというセクションのドキュメントでも説明しているので併せて参照ください.

*1:その結果rc7までバージョンが育ったという逸話があります

ltsv_exporter書いた

prometheus

ltsv_exporterというPrometheusのexporterを書いた.

その名の通り,LTSV形式のテキストを読み込んでPrometheusが解釈できる形式に吐き出すexporter.

利用方法としては2つのパターンを想定している.

1つはリモートで配信されているLTSVを読むというスタイル.

$ ltsv_exporter --url https://example.com/sample.ltsv

このようにすると https://example.com/sample.ltsv で配信されているLTSVを読み込んでそれをPrometheus形式で出力するようになる.

もう1つはローカルに設置されているLTSVを読むというスタイル.

$ ltsv_exporter --file /path/to/sample.ltsv

このようにすると /path/to/sample.ltsv に設置されているLTSVを読み込んでPrometheusの形式で出力するようになる.

Exporterの出力としてはこのような感じ.

# HELP ltsv_value LTSV value
# TYPE ltsv_value gauge
ltsv_value{key="duration"} 4.097
ltsv_value{key="size"} 123

key というラベルにLTSVのkeyが入り,値の部分にはそのままLTSVのvalueが入る.なお,LTSVのvalueが数値ではなかった場合は,exporterはその値を出力に含めずに読み飛ばす.
あとはこのexporterのhttp越しの出力をPrometheusで収集すれば良い.


というわけでメトリクスを取りたいものがあったり監視したいものがあった時は,雑にサーバでLTSVとして配信したり,もっと雑にLTSVとしてローカルファイルに吐き出したりするだけでltsv_exporterで読み込んでPrometheus形式で出力できるようになった.
LTSVはシリアライズ(?)する時に特別なシリアライザが不要で,シンプルに文字列組み立てだけで作れるので楽で良いですね.


今後としては,最後の1行だけ読み込んでパーズするモードなんかがあると便利なのかな〜と思っている.

CasperJSを使ってGrafanaのグラフのスクリーンキャプチャを撮る

javascript grafana prometheus

最近はサーバのメトリクス収集にPrometheus,その可視化の為のフロントエンドとしてGrafanaをハードに使っている.Grafanaは予想よりもはるかに格好良いグラフが生成されるのでやる気が出て良い.デモを触るとなんとなく温度感がわかると思う.
そうしているうちに,これは便利なのでサーバのメトリクス以外の情報も入れてみましょうという発想があり,サービスのKPIに関するような情報も入れ始めている.
とまあここまでは良いのだけれど,そうやって収集したデータも,格好良く可視化したグラフも,人に見られなければ一切の意味が無いのでそうした無意味を避けるためにデイリーでグラフを社内チャットに放流することにした.


という事になるとGrafanaで描画したグラフを画像としてエクスポートする必要が出てくる.
Grafanaにはグラフを画像としてシェアする機能があるのだけれど,これはブラウザをポチポチする必要があるみたいで,APIもぱっと見つからなかった (もしあったら教えて下さい) ので利用を見送った.あとどうやらこの画像データはSQLiteに入るみたいでホイホイ使って良いのかどうかあまり判断が付かなかった (間違ってたら教えて下さい).
というわけで今回はCasperJSを使ってGrafanaのグラフをキャプチャすることにした (結局CasperJS使うんだったらそれでGrafanaの画像シェア機能をポチポチすれば良かったんではないか,という意見もあることだろう.確かに!).
CasperJSはご存知の通りPhantomJS (かSlimerJS) を使ってスクレイピングやテストの実行をするのを助ける便利ライブラリ.CSSセレクタで指定すると,その範囲だけスクリーンキャプチャを撮ってくれるという機能があって大変便利なので使うことにした.

以下の様な感じでキャプチャが撮れる.

var casper = require('casper').create();

// login処理
casper.start('https://your-grafana.example.com/dashboard/db/your-dashboard?from=now-24h', function () {
    this.fill('div.login-inner-box > form', {
        username: 'Grafana user name',
        password: 'Grafana password',
    }, true);
});
casper.then(function () {
    this.click('div.gf-form-button-row > button');
});

// キャプチャ撮る
var waitMillis = 2000;
casper.viewport(1536, 1536).wait(waitMillis, function () {
    this.captureSelector('capture.png', 'div.grafana-row');
});

casper.run();

ログイン処理の部分は最初にfillしてからログインボタンをclickするように書いてある.シンプルなフォームだとfillの部分だけで認証が通るのだけれど,Grafanaはリッチな感じ (buttonのng-clickで処理が走るっぽい) なのでマニュアルでclickする処理を入れている.
認証が通ったらキャプチャを撮る処理を走らせる.ここで即座にキャプチャを撮ってしまうとグラフが未描画の場合があるので,気持ち待ってからキャプチャを撮る処理を開始している (例だと2秒).


で,これらをやる上で考えるべき点としては

  • グラフの時間スケール
  • viewportのサイズ
  • 対象となるグラフのCSSセレクタ

というのが挙げられる.


グラフの時間スケールと言うのは,どの時間スケール (直近5分とか直近24時間とか) のグラフを取得するかという点.Grafanaは時間スケールの情報はダッシュボード自身と紐付いていて,自分あるいは自分以外のユーザが時間スケールを変更すると全てのユーザに影響する.
そうした状態で時間スケールを特に指定せずにグラフを取得してキャプチャを撮ってしまうとめちゃくちゃになってしまうのでちゃんと指定してやる必要がある,という感じ.
例で言う所の https://your-grafana.example.com/dashboard/db/your-dashboard?from=now-24h?from=now-24h の部分がそれに当たる.


viewpointのサイズというのはCasperJSの話.要は仮想的なブラウザの大きさをどうするかみたいなもので,デフォルトの状態のままだとかなり小さめに設定されている.従ってほぼ確実に調整が必要になる部分だと思う.
Grafanaのグラフの大きさはブラウザの画面サイズに応じて可変なので,良い感じの数値を探っていくという泥臭い作業が要求されるのだけれど,まあ適当に妥協しましょう.


対象となるグラフのCSSセレクタというのは若干厄介.
今回は div.grafana-row というのを指定しているのだけれど,これはGrafanaのダッシュボードのrowそのものを表しているので,そのrowに属するグラフ等のパネルを全てキャプチャできる.
div.panel-container というのを指定すると個別のパネルをキャプチャできるので,用途に応じて使い分けると良い……という感じなのだがそう簡単には済まない.
なぜかというと,例えばrowが2つ有ると div.grafana-row というCSSセレクタも2つhtmlに表れてしまう為,先に出た方が優先されてしまう.つまりどちらか片方しかキャプチャができない.id要素などが付いているわけではない,本当に同一のCSSセレクタが出てきてしまうので面倒くさい……という感じ.もちろんパネルが複数個あればその個数分だけ div.panel-container が出てくるので問題は変わらない.
まあここら辺をなんとかする方法としては

  • 気合
  • キャプチャ用のダッシュボードを新たに作る

などといった解決方法があるので頑張れば問題がない.頑張りましょう.



こんな感じでGrafanaのグラフをキャプチャできるようになった.それを社内Gyazoに投げつけた上で社内チャットに毎日放流するという事が自動行えるようになって便利!
なお自動化はcronでやってます.

そして物語は何度目かのアプリ内通知再実装を迎える

というタイトルでKyoto.なんか #2で発表してきました.


そして物語は何度目かのアプリ内通知再実装を迎える / Reimplement in app notification // Speaker Deck

スライドの内容としては,アプリ内通知 (Twitter appで言うところの「通知」タブにあたる部分) のサーバサイドを実装する際にどういう問題があって,それをどういう風に実装したかという葛藤の記録となっています.

Webアプリケーションやスマートフォンアプリケーションを書いていると,そこそこの確率でアプリ内通知を書くことになると思うんですが,ところがどっこい「実際にどういう風に実装しているか」みたいな知見が共有されている感じがあまりありません.みんな実装しているはずなのに,ググってもあまり情報が出てこなくて寂しい.地味な機能だから?
という思いがあり,そこら辺アプリ内通知周辺の技術交流・意見交換などしたいなあというモチベーションでこの度発表してみた次第です.賽は投げられた.


ところで,この発表の最後で言おうと思っていたのに言っていない事があったのでこの場に書きますが,実際は「アプリ内通知」というか「通知タブ」のようなものはアプリケーションが個別に自作すべきものではないのでは? と思っています.
iOSAndroidには受け取ったpush通知を貯めて表示する通知センター的な領域が用意されているので,そうしたOSが提供してくれている機能に乗るようにアプリケーションを作成すればこんなに悩まなくても済みます (例えば今回の場合だと,push通知を送るだけ送るようにしておいて,クライアントが通知を貯めておくようにしておけばサーバサイドで通知情報を保持する必要がなくなる).


通知に限らず,他にもそうしたコンポーネントと言うのは様々あると思っていて,OSが提供してくれている機能で要件を満足できるのであればそれを活用するのが良いんじゃないかな,と思っています.
とはいえアプリケーション・サービスの特性によってはそうも言っていられないので,今回のように自作することになります.ケースバイケースと言えるでしょう.OSの通知センター使いたかった……




Kyoto.なんかは色々な技術的な話を聞けてとても有益でした.
特にid:r7kamuraさんのamakanのシリーズ判定の話がすこぶる面白かった.

r7kamura.hatenablog.com

主催のid:hakobe932さん,id:hitode909さんありがとうございました.また参加します.

Guice で独自の scope 切って inject したいって時

java

これを見ると良い.要は CDI のようなものをやるという話である.
CustomScopes · google/guice Wiki · GitHub


ここに書かれているように,

1. 任意の Scope を表す annotation を作る
2. Scope を実装する
3. Scope の実装と annotation を紐付ける (これは Module 内で行なう)
4. Scope#enter()Scope#exit() で Scope のライフサイクルを管理する

という流れに従う.


ここで少し難しく感じるのは2の「Scope を実装する」だと思う.
Wiki に書いてあるように基本的に Wiki に書いてある実装をコピペして使えという感じなのだが,まあ中身は読めばそこまで難しいことはしていない.
enter() する時に Scope が保持する objectを管理する Map を作成し,exit() する時にその Map を破棄するという感じ.あとは seed() で scope が持つ Map に直で object を登録したり,scope() で scope が保持している object Map から対応する object を取ってくるという処理を書いたら良い.
基本的に自分で Scope の実装を書く時はデフォルトの実装をそのまま使い,どこかで独自の処理をしたくなったらその部分を書き換えるという方法で良いと思う.元は thread safe な実装になっているし「大抵は」満足できる.


んでもって,あとは4のライフサイクル管理が若干面倒,一方で直感的ではあるかなあとは思う.
しかし,Module 内で injectee が bind されている時の挙動が個人的にわかりにくかったので書いておく.

例えば Module が以下のようになっていて,

final SimpleScope batchScope = new SimpleScope();
bindScope(BatchScoped.class, batchScope);
bind(SimpleScope.class)
    .annotatedWith(Names.named("batchScope"))
    .toInstance(batchScope);

bind(SomeObject.class)
    .toProvider(SomeObjectProvider.class)
    .in(BatchScoped.class);

Scope を管理する部分が以下のようになっている時,

final SimpleScope scope = injector.getInstance(Key.get(SimpleScope.class, Names.named("batchScope")));
try {
    scope.enter();

    injector.injectMembers(instance);

    // do something
} finally {
    scope.exit();
}

scope 内で injectMembers() しないと SomeObject は inject されない.
scope 内で injectMembers() された時は Module で指定されたように,SomeObject のライフサイクルは scope 内に固定される.ここは直感的.
なお Wiki の注意書きにも書かれているが,finally でちゃんと Scope#exit() しないと scope が閉じられず,ずっと生きながらえてしまうという問題があるので注意が必要.try-catch-resources が使えれば良いような気はしているが……


と,ここまで散々書いたのだけれど,Wiki の冒頭にあるように Custom Scope は自前で積極的に書くべきものではない.
基本的に,Singleton ServletModule が提供する RequestScopedSessionScoped で事足りるはずで,それで足りないケースというのはあまり存在しない.足りないケースの時だけ書くべきであるという事を留意されたし (今回はやんごとなき事情で自前で書きました).

tinyorm 1.11.0.rc1 is out

java

tinyorm 1.11.0.rc1 が出ました.以下の様な変更が入っています.

Connection を2つ持てるようになった

「Read/Write できる connection」と「Read だけができる connection」の2つの connection を持てるようになりました.tinyorm は内部でクエリのタイプ (read or write) に応じてこれらの connection を使い分けるようになっています.
これにより,write 系のクエリは master へ向いた「Read/Write できる connection」を使いつつ,read 系のクエリは slave へ向いた「Read だけができる connection」を利用するというような事が可能になりました.

今までは「Write 用の tinyorm」と「Read 用の tinyorm」をそれぞれ用意して利用するという感じで運用していたのですが,「Read 用の tinyorm」でひいてきた Row object に対して update をうっかり発行すると例外が上がってしまうという使い勝手の悪さがあり,今回このような改善を入れたという次第になります.

もちろん従来通り,単一の connection を利用することも出来ます.この場合はその1つの connection を全てのクエリで利用します.

Lazily な connection borrowing に対応した

Lazily な connection borrowing,つまり実際に connection が必要になったタイミングで connection を取ってくる (都度 establish するなり,connection pool から取ってくるなり) よう指定することが可能となりました.

方法としては,tinyorm を instantiate する際に javax.inject.Provider<Connection> を渡してやることで,connection が必要になったときに Provider<>#get() を利用して connection を borrow してくるようになります.なお,一度 borrow してからはその tinyorm instance 内では borrow した connection を使いまわします.

今後

内部的な変更が大きかったので,ひとまず rc 版ということで maven central にリリースしてあります.既存のテストケースは全て通っていますが,機能のすべてを保証できるとは言い切れないというステータスです.
安定して利用出来ることが検証でき次第,正式版としてリリースする予定です (rc が外れるタイミングでドキュメントが書かれる予定です).

以上です.よろしくお願いします.

2016-08-15 19:44 追記

rc1 は全く話にならず,現在は rc4 がお目見えしています.これはちゃんと動いてるのでもし試されるのであればこちらが良いです.