Figを使って複数のDockerコンテナをコマンド一発で立ち上げる

土日を使ってDocker入門を読みながらDockerを触ってみました。

とりあえずRails/MySQL/Elasticsearchで構成されたWebサービスの開発環境を作ることを想定して、ひと通り手でコマンドを打ちながらMySQLとElasticsearchのコンテナを作ってRailsから接続させることはできました。

$ docker run -d -p 9200:9200 -p 9300:9300 dockerfile/elasticsearch
$ docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password mysql

これはこれで良いのですが毎回イメージごとに引数を思い出すのはだるいのでシェルスクリプトでも書こうかなと思ったら、ちょうどFigという複数のコンテナを一元管理する仕組みを提供してくれるツールを見つけたので使ってみました。

前提

fig.yml

docker runで起動するときのオプションをfig.ymlに書きます。 リファレンスはここにありますがdocker runのオプションをそのまま書くだけなので簡単です。

elasitcsearch:
  image: dockerfile/elasticsearch
  ports:
    - '9200:9200'
mysql:
  image: mysql
  ports:
    - '3306:3306'
  environment:
    MYSQL_ROOT_PASSWORD: password

実行

あとはfig upするだけです。

実行するとMySQLとElasticsearchの起動ログがずらずらと流れて起動完了します。 これで最新のMySQLとElasticsearchをホストOSの環境を汚すことなくインストールして使うことができます。

fig scale

さらにfig scaleというコマンドを使うと簡単にサービスを複数立ち上げることもできます。 以下はMySQLのコンテナを3つに増やした場合ですが、fig scale mysql=3とするだけです。 (ただし、ポートフォワードしてると衝突してしまうため3306:3306としていたものを3306に変更しました)

% fig scale mysql=3
Starting figtest_mysql_2...
Starting figtest_mysql_3...
% fig ps
        Name                       Command               State            Ports
-----------------------------------------------------------------------------------------
figtest_elasitcsearch_1   /elasticsearch/bin/elastic ...   Up      9300/tcp, 9200->9200/tcp
figtest_mysql_3           mysqld --datadir=/var/lib/ ...   Up      49155->3306/tcp
figtest_mysql_2           mysqld --datadir=/var/lib/ ...   Up      49154->3306/tcp
figtest_mysql_1           mysqld --datadir=/var/lib/ ...   Up      49153->3306/tcp

この環境でMySQLだけ増やしても意味無いですが、ResqueなどのWorkerを複数立ち上げたりロードバランサのコンテナを追加したりすると開発環境でも本番環境に近い構成が簡単に作れるようになるかもしれませんね。

まとめ

Figを使うと

  • プロジェクトが依存するミドルウェアをコマンド1つで立ち上げられる
  • 1つのマシンに複数バージョンのミドルウェア共存させられる
  • チーム開発時にDockerのことを知らないメンバーでも簡単に同じ環境を作れる

といったことが簡単に実現させられるようになります。

しかもFigの開発元は最近Docker社に買収されFigはDockerの公式ツールになるようなので今後もっと便利になっていきそうです。

http://www.publickey1.jp/blog/14/dockerfig2orchard.html

通話中の画面サイズ

iPhoneには通話中やテザリング中にステータスバーの高さが2倍になる仕様があります。 この時、狭められた分のviewのサイズはどうなっているか調べてみました。

基本的な考え方

まずiOS7の場合viewは通常画面の一番上から始まるので、透過したステータスバーと20px分重なっているのが通常状態です。

通話中などにステータスバーが2倍の40pxになると、20px重なっている分は変わらず、さらに20px分viewが縮められます。

ではこれを元にいくつかのパターンを見てみます。

普通のviewの場合

まず普通のviewの場合、基本的な考え方の通り、ステータスバーが2倍になるとyが+20され、heightが-20されます。 (画像中の数字は{{x,y},{width,height}}) 通話中はステータスバーが透過しなくなるため一番上のグレーのバーがステータスバーの下に潜り込んで見えなくなっています。

f:id:y_310:20140416204847p:plain f:id:y_310:20140416204859p:plain

UINavigationControllerの場合

UINavigationControllerの中のviewの場合はheightのみ-20されます。親のviewに対する起点は変わってないので当然ですね。

f:id:y_310:20140416204906p:plain f:id:y_310:20140416204911p:plain

ChildViewControllerの場合

以下のようにchildViewControllerを2つセットした画面で試してみます。1つ目はUINavigationController、2つ目はただのViewControllerです。

f:id:y_310:20140416210840p:plain

親のviewは基本通りy+20, height-20で、赤いただのViewControllerをセットしたChild ViewControllerは特に変化していません。 ここまではいいのですが、なぜか1つ目のUINavigationControllerをセットしたところだけheightが-20されています。

f:id:y_310:20140416204914p:plain f:id:y_310:20140416204917p:plain

この挙動は使う側としてはあまり期待していない挙動だと思うのですが、もしかするとステータスバーの高さが変わった時にUINavigationControllerの高さを問答無用で20px縮める処理がどこかにあるのかもしれません。

原因はautoresizingMaskの設定でした。UINavigationController側のContainer Viewだけ以下の様な設定になっていて高さがリサイズされていました。

f:id:y_310:20140421225406p:plain

このようにUIViewAutoresizingFlexibleHeightを外してやると高さが変わらなくなって期待通りの挙動になりました。

f:id:y_310:20140421225418p:plain

今回の検証に使ったコードはこちらにあります。 https://github.com/y310/StatusBarExample

UITableViewControllerのchildViewControllersにUITableViewControllerを入れる時のTips

UITableViewControllerの各セクションをChildViewControllerの仕組みを使って別のUITableViewControllerで管理しようとしてハマったのでメモ。

この画像のようにStatic cellにcontainer viewを入れて、そこに別のUITableViewControllerをembed segueで接続することで、セクションごとに別のデータを扱う場合にViewControllerを分けることができます。

f:id:y_310:20140119234519p:plain

ただ、このままだと、各セルの中で上下にスワイプした時に画面全体ではなくセルの中だけがスクロールしてしまいます。 それを防ぐためにはスクロールしないようにすればいいだろうと思ってScroll ViewのScrolling Enabledのチェックを外し一見問題なく動くようになったのですが、色々いじっているうちに奇妙な挙動をすることに気づきました。。

f:id:y_310:20140119234854p:plain

スクロールしてから適当な行をタップすると時々1回目のタップに反応しないのです。 ChildViewControllerのtableView:didSelectRowAtIndexPath:も呼ばれません。 2回目以降のタップは正常に反応します。

試行錯誤した結果、原因はわからないのですがScrolling EnabledをNOにするのをやめて、BouncesをNOにすることで、この問題を起こさずにスクロールさせない状態を実現できました。 ただし、当然ですがスクロールをしないようにしているわけではないので親のcontainer viewとChildViewControllerのviewのサイズを同じにしておかないと子のviewがスクロールしてしまいます。 そのためtableView:heightForRowAtIndexPath:で高さを調整してやる必要があります。

f:id:y_310:20140119235510p:plain

検証のために作ったサンプルコードです。

486edd が正しく動かない設定、 10945a が期待通りの挙動をする設定です。

https://github.com/y310/NestedViewControllerSample

第2回elasticsearch勉強会でKibanaの発表をしてきました #elasticsearchjp

2013年11月12日にリクルートテクノロジーズで開催された「第2回 ElasticSearch勉強会」で「Kibana入門」というタイトルで発表してきました。 社外で発表をするのは初めてだったのでどんな雰囲気になるか不安でしたが、色々質問していただけて安心しました。懇親会でも話しかけていただけてありがとうございました。

長さの関係で発表資料に書かなかったことをいくつかこちらに書いておきます。

クエリのピン止め

クエリは名前をつけてピン止めすることができます。ヒストグラムパネルやテーブルパネルはグラフに使用するクエリをすべてのクエリ、ピン止めしたクエリ、ピン止めしていないクエリ、選択したクエリから選べますのでパネルごとに異なるクエリを指定したい時に便利になります。

f:id:y_310:20131116223540p:plain

f:id:y_310:20131116223838p:plain

f:id:y_310:20131116224250p:plain

ヒストグラムの一部を拡大

画面上特にヒントがないので意外と気づきづらいですが、ヒストグラムはグラフ上でドラッグすることで選択した範囲を拡大できます。

設定の保存場所

資料ではelasticsearchに保存する場合のみ記載しましたが、その他にjsonファイルとしてエクスポートする方法と、ブラウザのLocal Storageに保存する方法があります。ただしLocal Storageは"Set as my home"つ まり自分のデフォルトダッシュボードとして1件しか保存できません。 またデータのロードについては、elasticsearch、ファイル以外にgistからURLを指定して読み込む方法もあります。これによってApache用のダッシュボード等一般的な設定についてはオープンに共有していくことも可能です。

f:id:y_310:20131116224847p:plain

logstash形式ではないログを表示する

fluent-plugin-elasticsearchなどでログをelasticsearchに入れると、インデックス名がlogstash-YYYY.MM.DD、各ドキュメントは必ず@timestampというフィールドを持つというデータが作られます。 しかしこの形式はKibanaにとっては必須ではありません。インデックス名に日付が入っていることでKibanaは自動的に過去のインデックスに順番にクエリを投げていって結果を集約してくれますが、1つのインデックスにすべてのデータが入っている場合など、そういった挙動を必要としない場合は任意のインデックス名を指定することができます。 また@timestampというフィールドについても名前はKibanaの設定で変更できますし、時刻フィールドが存在しなくてもfacetを使った円グラフ等の表示は可能です。 とは言え、デフォルトの設定値は上記のようなlogstash形式になっているので特に事情がない場合はそれに従うのが良いでしょう。

最後に

今回の発表で、現時点のKibanaで機能的にできることは概ね紹介できたかと思っています。あとはこれらの機能をどのように実際のユースケースに応用していくかというのが今後の課題になるかと思いますので、利用事例を公開してくださる方が増えるのを楽しみにしています。

Kibana 3 + Rails + Fluentdのサンプルアプリを作ってみた

miyagawaさんのPodcast Rebuild: 19でKibanaの話があってちょっと盛り上がり始めてるので、簡単に動作を試せるサンプルアプリセットを作ってみました。

https://github.com/y310/kibana-trial

git cloneしてREADMEに書いてある手順を実行していくと大体動くと思います。

railsからfluentdにログを送る部分は、こんな感じでrack middlewareを使って送ります。

# application_controller.rb
class ApplicationController < ActionController::Base
  around_filter :collect_metrics

  def collect_metrics
    yield
  # ensureを使うのは例外時のログも捕捉するため
  ensure
    # controllerのコンテキストで取りたい情報はここで追加する
    env['rack.metrics'] = params.slice(:user_id, :controller, :action).merge(
      path: request.path,
      user_agent: request.user_agent,
      method: request.method
    )
  end
end

# lib/rack/metrics_logger.rb
module Rack
  class MetricsLogger
    def initialize(app)
      @app = app
    end

    def call(env)
      status, headers, body = @app.call(env)

      # リクエストの終わりに取りたい情報はここで追加
      metrics = (env['rack.metrics'] || {}).merge(
        status: status,
        runtime: headers['X-Runtime']
      )
      logger.post("kibana", metrics)
      [status, headers, body]
    end

    def logger
      @logger ||= Fluent::Logger::FluentLogger.new(nil)
    end
  end
end

ひと通り立ち上がったら、curl localhost:3000を何回か叩くと http://localhost:4567/index.html でKibanaのグラフが表示されるはずです。 これはjmeter/test_request.jmxを使ってJMeterで1分間リクエストを流した時のグラフです。

f:id:y_310:20130911223533p:plain

バー1本が1秒なので、8 rpsくらいのリクエストが流れていることがわかります。

平均レスポンスタイムはどうなっているでしょうか。 右上のヒストグラムの設定リンクをクリックすると以下の設定画面が出てくるので、Modeをmeanにして、Value Fieldをruntimeにすると平均レスポンスタイムのグラフに変わります。

f:id:y_310:20130911224048p:plain

サンプル数が少ないのでばらつきが激しいですが大体40msくらいで返せているようです。

f:id:y_310:20130911224103p:plain

次は、エラーになったリクエストがどれくらいあるか見てみます。 検索クエリはstatusが200のものと、それ以外のものとします。 Kibanaでは複数の検索クエリに名前をつけてPinしておくことができます。 ちょっとわかりづらいですが、クエリフィールドにクエリ(ここではstatus:200)を入力してから、フィールドの端にある緑の丸をクリックすると画像のようなポップアップが表示されるので、ここにクエリの名前を入力して左上のピンアイコンをクリックすると保存されます。

f:id:y_310:20130911224857p:plain

同様にエラーのリクエストについても-status:200と入力して保存します。 この時点でグラフには2つのクエリの結果がstackされて表示されます。

f:id:y_310:20130911230237p:plain

OKに比べてErrorが少なすぎて見づらいのでグラフを分けてみます。 設定からそのグラフで表示させるクエリを選ぶことができるので、グラフを追加した上でそれぞれのグラフに1つずつクエリを割り当てます。

f:id:y_310:20130911225721p:plain

するとこのように成功したリクエストとエラーリクエストを分けて見ることができます。

f:id:y_310:20130911230249p:plain

最後にここまで作ったDashboardを保存します。右上のフロッピーアイコンをクリックすると保存ウィンドウが出てくるので、そこに名前を入力して保存してください。保存した設定はフォルダアイコンの方からロードすることができます。この設定はElasticSearchのkibana-intというインデックスに保存されます。

f:id:y_310:20130911231010p:plain

まとめ

こんな感じでクエリを保存してグラフに設定することで見たい情報を一覧できるダッシュボードを簡単に作ることができます。 データの表示形式はヒストグラム以外にも円グラフや地図、表など色々ありますが、今までの経験ではヒストグラムと表以外はあまり実用上役に立ったことはありませんでした。他の表示方法で便利に使っているユースケースがあったらぜひ教えて下さい!

OmniDiskSweeperが便利

f:id:y_310:20121102234814p:plain

なんだかディスク容量がやけに少ないように思って内訳を確認したところ「その他」が肥大化していることがわかりました。

その他って何だって話で地道にduしたりしてたけど埒が明かないのでちょっと調べてみたらOmniDiskSweeperというツールを見つけました。OmniGraffleなどの高いツールで有名なOmni社の製品だけどこれは無料。

f:id:y_310:20121102235334p:plain

こんな感じでディレクトリごとに容量を表示してくれるのでどこに巨大なデータが眠っているのかすぐわかります。

今回は以前試しにmysqlにインポートしたWikipediaのデータが25GBもあったのが原因でした。もう不要なのでさくっとdropして解決。

edge rails(3.2.0.beta)に上げてみた

戯れに手元のrailsをmasterを見るようにしてみたらサーバが起動しなくなりました。

hamlのエラー

#gem 'rails', '3.0.9'
# Bundle edge Rails instead:
gem 'rails', :git => 'git://github.com/rails/rails.git'
$ bundle exec rails s
=> Booting WEBrick
=> Rails 3.2.0.beta application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
Exiting
/Users/y_310/work/sample_prj/vendor/bundle/ruby/1.9.1/gems/haml-3.1.2/lib/haml/template/patch.rb:16:in `alias_method': undefined method `delegate_template_exists?' for class `ActionView::Base' (NameError)

エラーをぐぐってみるとどうやらhamlのバグっぽい。
https://github.com/nex3/haml/issues/395
そしてこの問題はここですでに修正済。
https://github.com/nex3/haml/pull/398

ただ普通にgem installして入るバージョンは3.1.0でギリギリこの修正コミットは入っていないみたい。

仕方ないのでhamlもedgeを見るようにする。

gem 'haml', :git => 'git://github.com/nex3/haml.git'

rjsのエラー

hamlを最新にして再度サーバを起動しようとするとまたエラーが出ました。

=> Booting WEBrick
=> Rails 3.2.0.beta application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
Exiting
/Users/y_310/work/sample_prj/vendor/bundle/ruby/1.9.1/bundler/gems/rails-3b5eb11664b8/actionpack/lib/action_view/railtie.rb:34:in `block (3 levels) in <class:Railtie>': undefined method `debug_rjs=' for ActionView::Base:Class (NoMethodError)

これもググッてみるとこのページがヒットしました。
http://weblog.rubyonrails.org/2011/4/21/jquery-new-default

rails3.1で標準のJSライブラリがprototype.jsからjQueryになった際に、rjsもデフォルトで入らなくなったらしい。
rjsは必要ないので書いてあるとおりにconfig/environments/development.rbから以下の行を削除

# remove next line
config.action_view.debug_rjs = true

これで無事にサーバが起動しました。