2014年05月

Gemfile を編集せずに任意の gem を追加していじる2つの方法

OSSをいじっていると、byebug を使いたくなって、Gemfile に

gem 'byebug'

の1行を追加して、bundle install して、そのあと Gemfile を間違ってコミットしないように気をつけながら、git commit でファイルを1つずつ指定したりとかやってたんだけど、いい加減イライラしてきたので、サクッとできるようにしてみた。

方法その1

最初、bundle config  でひょっとしたらできるのかな、と思ったんだけど無理ぽだったので、あきらめて雑なシェル関数を作って、.zshrc (.bashrc) に追加することにした。

bundol () {
  if [ -e Gemfile ]; then
mkdir -p /tmp/$(pwd)
    sed -e "s|gemspec.*|gemspec path: \"$(pwd)\"|" Gemfile > /tmp/$(pwd)/Gemfile
    echo 'gem "byebug"' >> /tmp/$(pwd)/Gemfile
    bundle --gemfile=/tmp/$(pwd)/Gemfile
  else
echo 'Gemfile is not found' 1>&2
  fi
}

be () {
  if [ -e /tmp/$(pwd)/Gemfile ]; then
BUNDLE_GEMFILE=/tmp/$(pwd)/Gemfile RUBYOPT="-r byebug" bundle exec $@
  else
bundle exec $@
  fi
}

bundol (ぶんどる) って打つと、Gemfile を /tmp/$(pwd)/Gemfile にコピーして、gem 'byebug' を追加して、bundle --gemfile=/tmp/$(pwd)/Gemfile って感じで、bundle install する。

gem の場合は、Gemfile に gemspec の行が大体あるので、gemspec の行を gemspec path: $(pwd) ってかんじで置き換えている(雑だ)

bundle exec の代わりに、be と打つと、/tmp/$(pwd)/Gemfile があれば BUNDLE_GEMFILE 環境変数で指定しつつ bundle exec してくれるようにしている。

・・・雑だ

方法その2

Rぺこ氏に昔似たようなことやったと教えてもらったので、その方法の発展版。

Rぺこ氏は、tapp を丸ごとディレクトリコピーして ruby の lib に入れていたけれど、byebug だと依存 gem があってそれもやろうとすると辛いので、gem install で ruby の site_dir 以下に入れるようにして、
sitedir=$(ruby -rrbconfig -e 'puts RbConfig.const_get(:CONFIG)["sitedir"]')
gem install byebug --install-dir $sitedir
そこに
export RUBYOPT="$(\ls -d ${sitedir}/gems/*/lib/ | xargs -I{} echo -n \ \-I {}) -r byebug"
 ってかんじで、-I オプションで $LOAD_PATH 追加しつつ、-r オプションで require するようにした。
依存 gem 全部に LOAD_PATH 通すために、\ls -d うんちゃらとかやってる。※ ここもう少しうまくできないかなぁ。

追記:これでできたと思ったんだけど、$sitedir 以下の gem と bundler が入れた gem の両方が読み込まれて、定数二重定義 warning が出てしまうなぁ
 
おわりに

bundle config でできるようになるのが理想系だけど、とりあえずは bundol で頑張ろうと思う。いつかプルリク送るかもしれない。

acts_as_file という gem を作った

https://github.com/sonots/acts_as_file

モデルの指定した field の内容をファイルに保存してくれるやつ。こんなかんじで使う。
class Post < ActiveRecord::Base
  include ActsAsFile
  def filename
    "#{Rails.root}/files/#{self.id}.txt"
  end
  acts_as_file :body => self.instance_method(:filename)
end 
これで、#save メソッドを呼ぶと、body フィールドの内容を #filename メソッドで指定したファイルに保存してくれる。読み込みは body フィールドにアクセスしたときにされる(キャッシュされる)。#destroy メソッドを呼ぶとファイルは削除される。ActiveRecord には依存してないけど、#save, #destory メソッドとか言ってる時点で、ActiveRecord 想定してる感はある。

複数フィールドを acts_as_file したい場合は
acts_as_file :body => self.instance_method(:filename), :body2 => self.instance_method(:filename2)
みたいに指定できる。

便利機能として、post.body(offset, length) のようにアクセスすると offset バイト分だけ File#seek してから、length バイト分だけ読み込んでくれる機能もある。こっちはキャッシュされない。 

今のところはこんなかんじ。 

fluentd-server から serf 使って td-agent を再起動できるようにした

fluentd-server 作った の続き。fluentd-server は Fluentd の設定ファイルを中央集権的に管理するやつ。

Fluentd Server で conf を書き換えた後に、Fluentd に反映するためには、Fluentd を再起動して回らないといけないのだけど、Fluentd Server 側からキックできるようになると良いよね、と思っていたので実装してみた。コードはこちら => fluentd-server

9426888a5ef54c3a0c75dd859ddcc56d

これを実現しようと思った場合、capistrano かなにかで、ssh で対象サーバにログインして sudo /etc/init.d/td-agent restart して回る、という仕組みにしがちだと思うんだけど、そうしようとすると td-agent が入っているホスト一覧を fluentd-server で管理する必要があって実はあまり良くない。すでに chef-server でホスト管理してたりとか、内製の鯖管ツールでホスト管理していたりするような所だと、二重管理になってしまうし、ホストがじゃんじゃん増減しがちな Immutable Infrastructure とか考えると、ホスト一覧のメンテとかできればやりたくない。

じゃあ、どうするか、というと serf を使うのがよさそうと思ったのでそうしてみた。serf については以前、正月休みだし Serf 触ってみた の記事を書いたのでそちらを見てもらうと良いのだけど、一言でいうとサーバのオーケストレーションツールというやつで、もう少し言うと、serf で組んだクラスタ全体にイベントを伝搬しながら、イベントに対応したスクリプトを実行してくれるやつ。

Fluentd Server を起動するとローカルで serf agent が1つ勝手に起動するので、あとは td-agent を動かすホストで、
$ fluent-gem install serf-td-agent
$
export PATH=$(fluent-gem path serf-td-agent)/bin:$PATH
serf agent -join={Fluentd Server のアドレス} -event-handler=serf-td-agent 
ってかんじで、今回作った serf-td-agent gem を入れつつ、Fluentd Server で動いている serf あてに join してもらえば、td-agent のクラスタができあがる。あとは、Fluentd Server の Task タブで Restart ボタンを押すと、Fluentd Server がローカル serf に
$ serf event td-agent-restart
ってかんじでイベントを送るので、td-agent サーバ全体にイベントが伝搬され、sudo /etc/init.d/td-agent restart が実行されるようになる。ちなみに、Fluentd Server のローカル serf はイベントを伝搬するだけで、特に event handler スクリプトは実行しない。

これから

Status ボタンのほうでは serf event (非同期、結果を待たない) ではなく、serf query (同期、スクリプトの実行結果を受け取れる)を使っているのだけど、そのコマンド結果を逐次画面に更新するあたりはまだ実装してない。最初、websocket でやろうかと思ってたんだけど、em-websocket を使うとかってことになると、unicorn サーバとは別ポートを開ける感じになって、運用しづらいツールになっちゃうかな、と思って悩んでる。古きよき Ajax にして unicorn サーバで捌いた方が良いだろうか。

おことわり

heroku デモでは動かない。

おわりに

serf をいい感じに応用できたんじゃないかな、と思っている。Enjoy!

追記:ajax 更新対応しました。v0.2.0 (2014/05/26)

[書評] Chef実践入門

『Chef実践入門』をご恵贈いただきました。ありがとうございます。

Chef実践入門 ~コードによるインフラ構成の自動化 (WEB+DB PRESS plus)
吉羽 龍太郎 安藤 祐介 伊藤 直也 菅井 祐太朗 並河 祐貴
技術評論社
売り上げランキング: 350

入門Chef SoloChef活用ガイド に続く第三の chef 本でしょうか。

早速読んでみた感想ですが、書名の通りChef による環境構築を「実践」するにあたって「入門」するのに適した本だと思いました。 

本を読む前に、chef を「実践」しようと思った場合に、何を知りたいかな、と自分なりに考えてみた所、
  1. chef-solo の使い方
  2. chef-server の使い方、chef-solo 運用との使い分け
  3. chef cookbook の開発環境(disposableな環境)の用意方法
  4. chef cookbook のテストの書き方、CI環境の用意方法
  5. chef cookbook の書き方ベストプラクティス
あたりが分かればいいんじゃないかな、と。で、chef 実践入門を読んでみた所、


全部書いてありましたーーーー!!!└(゚∀゚└)(┘゚∀゚)┘


さらには 、chef レシピのエラーメッセージの追い方や、Appendix にチートシートまで書いてあって、これは本当に実践的だな、という印象です。

特に5章は Vagrant や packer を使った開発環境(disposableな環境)の構築に費やされていて、Vagrant 本か!というぐらいの趣がありましたし、7章の  test-kitchen と serverspec でテストを回すための解説などなど、chef の「周辺技術も含めて実践」する場合にとても参考になるのではないかと思います。

ただ「入門」と冠しているように、chef の細かい挙動にまで突っ込んだ本ではないので、そちらを知りたい方はChef活用ガイドを購入されると良いでしょう。献本頂いてなくて読んでないのでわかりませんが!!
 
Chef活用ガイド コードではじめる構成管理
澤登亨彦 樋口大輔
KADOKAWA/アスキー・メディアワークス
売り上げランキング: 19,592

まとめ

入門Chef Solo を読んで chef-server とか serverspec とかもちょっと知りたい、と思っているそこのあなたにピッタリな本だと思います。

複数の fluentd plugin のテストを一気に流すやつ

表題のやつ作った => https://github.com/sonots/fluentd-plugin-ci

こんかんじのスクリプト。

# run-tests.rb
require "rubygems"

specs = Gem::Specification.find_all { |s| s.name =~ /fluent-plugin/ }

passes = specs.map do |spec|
  puts "\e[31m\e[43m\e[5mRunning tests for #{spec.name}\e[0m"

  Bundler.with_clean_env do
    system <<"EOF"
cd #{spec.full_gem_path}
echo "bundle install"
bundle install
echo "RUBYLIB=lib:test:$RUBYLIB bundle exec rake"
RUBYLIB=lib:test:$RUBYLIB bundle exec rake
EOF
  end
end

exit 1 unless passes.all?

何やっているのかというと、Gemfile に書いてある gem の中から fluent-plugin なものだけピックアップして、その gem のディレクトリに入って、bundle install && bundle exec rake を実行している。

テストは全部流すんだけど、どれか1つでも失敗したらコマンドとして最終的には失敗、ということで exit 1 している。

あとは travis でこれを流せるように、.travis.yml をこんなかんじにしただけ。

rvm:
  - 2.1
gemfile:
  - Gemfile
script: "bundle exec ruby run-tests.rb"

対象プラグインを追加したい場合は、Gemfile に追加するだけだし、最新 fluentd でテストしたい場合は git commit --allow-empty -m 'test with fluentd v0.10.47' みたいなかんじでリリースされたタイミングで空コミットしてあげれば travis さんがテストしてくれる。

ところで最初、ローカルではうまくいったんだけど、travis で bundle install が上手くいかなくて四苦八苦してた。bundle のなかで bundle する - 刺身☆ブーメランのブログ の記事を見つけて、Bundler.with_clean_env 付けるようにしたら travis でも上手くいった。BUNDLE_GEMFILE を指定したりとか、RUBYLIB を指定したりとかしても上手く行かなくて bundler のコード読み始めたりとか、そちらの記事と同じことやり始めてたから助かった。@kyanny++

追記:  Bundler.with_clean_env 付けるようにして BUNDLE_GEMFILE の指定は不要になったけどやっぱり RUBYLIB は必要だった。Bundler の環境変数じゃないので、こっちは clean されないわけか。ふむ。

A Ruby and Fluentd committer working at DeNA. 記事本文および記事中のコード片は引用および特記あるものを除いてすべて修正BSDライセンスとします。 #ruby #fluentd #growthforecast #haikanko #yohoushi #specinfra #serverspec #focuslight
はてぶ人気エントリー