Railsアプリの本番環境での運用 -godとか-

perldaemontools + Server::Starterで面倒見てたあたりをRailsでもしっかりやりたいなぁと。
以外としっかりとやってるサンプルがウェブに落ちてなかったので、いろいろ調べました。
いろいろ検証してみたけど、最終的に以下のように落ち着きそうです。試行錯誤した中身もまとめたい気もする。

やること

  • graceful restart
  • Railsアプリの永続化
  • 永続化してるやつの自動起動とか永続化とか
  • capistranoからアプリの再起動などできるように

終着点

Railsアプリをgod + unicornで上げて、godをinit script置いてchkconfigで自動起動とかの設定。
あと、capistranoからgod監視下のアプリを再起動できるように

unicornをgodで動かす

よくあるやつ。

rails_env  = ENV['RAILS_ENV']  || 'production'
rails_root = ENV['RAILS_ROOT'] || '/path/to/application'

God.watch do |w|
  w.name     = 'application_name'
  w.interval = 30.seconds

  w.uid = 'app'
  w.gid = 'app'

  w.start   = "bundle exec -- unicorn_rails -c #{rails_root}/config/unicorn.rb -E #{rails_env} -D"
  w.stop    = "kill -QUIT `cat #{rails_root}/tmp/pids/unicorn.pid`"
  w.restart = "kill -USR2 `cat #{rails_root}/tmp/pids/unicorn.pid`"

  w.start_grace   = 10.seconds
  w.restart_grace = 10.seconds
  w.pid_file      = "#{rails_root}/tmp/pids/unicorn.pid"
  w.log           = File.expand_path(File.join(File.dirname(__FILE__), '..', 'log', 'god.log'))

  w.behavior(:clean_pid_file)

  w.start_if do |start|
    start.condition(:process_running) do |c|
      c.interval = 5.seconds
      c.running  = false
    end
  end

  w.restart_if do |restart|
    restart.condition(:memory_usage) do |c|
      c.above = 300.megabytes
      c.times = [3, 5]
    end

    restart.condition(:cpu_usage) do |c|
      c.above = 50.percent
      c.times = 5
    end
  end

  w.lifecycle do |on|
    on.condition(:flapping) do |c|
      c.to_state     = [:start, :restart]
      c.times        = 5
      c.within       = 5.minutes
      c.transition   = :unmonitored
      c.retry_in     = 10.minutes
      c.retry_times  = 5
      c.retry_within = 2.hours
    end
  end
end

init script

はこんな感じ。
/etc/init.d/godに置く。

#!/bin/bash
. /etc/rc.d/init.d/functions

RBENV_ROOT=/opt/rbenv
RBENV_VERSION=""
export RBENV_VERSION

PATH=$RBENV_ROOT/shims:$RBENV_ROOT/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
RUBY_PATH=$RBENV_ROOT/shims/ruby
DAEMON=$RBENV_ROOT/shims/god
PID_FILE=/var/run/god.pid
LOG_FILE=/var/log/god.log
SCRIPT_NAME=/etc/init.d/god
CONFIG_FILE_DIR=/etc/god

#DEBUG_OPTIONS="--log-level debug"
DEBUG_OPTIONS=""

test -x $DAEMON || exit 0

RETVAL=0

god_start() {
  start_cmd="$DAEMON -l $LOG_FILE -P $PID_FILE $DEBUG_OPTIONS"
  echo $start_cmd
  $start_cmd || echo -en "god already running"
  RETVAL=$?
  if [ "$RETVAL" == '0' ]; then
    sleep 2
    if [ -d $CONFIG_FILE_DIR ]; then
      echo "god: loading master config"
      $DAEMON load $CONFIG_FILE_DIR/master.conf
    fi
  fi

  return $RETVAL
}

god_stop() {
  stop_cmd="god terminate"
  echo $stop_cmd
  $stop_cmd || echo -en "god not running"
}

case "$1" in
  start)
    god_start
    RETVAL=$?
    ;;
  stop)
    god_stop
    RETVAL=$?
    ;;
  restart)
    god_stop
    god_start
    RETVAL=$?
    ;;
  status)
    $DAEMON status
    RETVAL=$?
    ;;
  *)
    echo "Usage: god {start|stop|restart|status}"
    exit 1
    ;;
esac

exit $RETVAL

で、/etc/god/master.confに各設定のloadを書く。
ここは、/etc/god以下にconfig fileを複数用意するのもありだけど、
master.confだけ置いて中でloadする形にした。

capistranoのtask.

capistrano 3なので、lib/capistrano/tasks/god.capとかに置く感じで。

namespace :god do
  task :environment do
    set :god_config, "#{current_path}/config/god/#{fetch(:rails_env)}.rb"
  end

  task :start do
    on roles(:web) do
      execute :sudo, "/opt/rbenv/shims/god start #{fetch(:application)}"
    end
  end

  task :restart do
    on roles(:web) do
      execute :sudo, "/opt/rbenv/shims/god restart #{fetch(:application)}"
    end
  end

  task :stop do
    on roles(:web) do
      execute :sudo, "/opt/rbenv/shims/god stop #{fetch(:application)}"
    end
  end
end

これでcapistrano経由でrestartとかできる。
god(というかrbenv)のpathは分離したい感じだ。

capistrano-unicornとか使うとcapistrano経由でunicornでアプリ上げれるけど、永続化とかサーバ再起動時にアプリがあがらないのとか気になってたのでちゃんと調べた。冗長なサーバ構成である程度アプリ落ちても動作する+監視で落ちたのに気づけるというところで少しは大丈夫なところもあるのかもと思いもしたけど、やっぱり気になったので。

zshの設定紹介

ふだん僕が使ってるzshの設定を紹介します。
学生時代からzsh使ってて特に気にせず使ってるのですが、新しいPCとかに移るとどんだけzshに助けられてたかを実感したりするわけです。

export WORDCHARS="*?_-.[]~=&;!#$%^(){}<>+"

単語境界にならない文字の設定。/が抜いてあるので、ファイルパスの入力中に^wで戻ると1ディレクトリ分戻れて便利。

autoload -U colors
colors
PS1="[%{${fg[green]}%}%t%{${fg[default]}%}]%# "

色を使えるようにする設定と、お気に入りの左prompt。時間が表示されます。

autoload -U compinit
compinit

いろんな補完を有効にする設定。とりあえずこれ。
optionの補完とかもしてくれてすごく便利。

zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'

補完するときに小文字入力でも大文字にマッチするようにする。しばらくなしでやってみたけど、やっぱりこれがないとってなった。

SAVEHIST=100000
HISTSIZE=100000
HISTFILE=~/.histfile
setopt SHARE_HISTORY
setopt HIST_IGNORE_DUPS
setopt HIST_REDUCE_BLANKS
setopt HIST_NO_STORE
if [ $UID = 0 ]; then
    unset HISTFILE
    SAVEHIST=0
fi

コマンドヒストリ関連の設定。
terminal複数開いてる場合とかにヒストリ共有されるのですごい便利。

DIRSTACKSIZE=10
setopt AUTO_PUSHD
setopt PUSHD_IGNORE_DUPS
setopt PUSHD_SILENT

cdで異動するときに勝手にpushdしたことにする設定。popdすれば前いたディレクトリに戻れる。便利。

bindkey "^P" history-beginning-search-backward
bindkey "^N" history-beginning-search-forward

ヒストリサーチのキーバインド設定。

setopt prompt_subst
autoload -Uz VCS_INFO_get_data_git; VCS_INFO_get_data_git 2> /dev/null

function rprompt-git-current-branch {
  local name st color gitdir action
  if  "$PWD" =~ '/\.git(/.*)?$' ; then
    return
  fi
  name=$(basename "`git symbolic-ref HEAD 2> /dev/null`")
  if  -z $name ; then
    return
  fi

  gitdir=`git rev-parse --git-dir 2> /dev/null`
  action=`VCS_INFO_git_getaction "$gitdir"` && action="($action)"

  st=`git status 2> /dev/null`
  if  -n `echo "$st" | grep "^nothing to"` ; then
    color=%F{green}
  elif  -n `echo "$st" | grep "^nothing added"` ; then
    color=%F{yellow}
  elif  -n `echo "$st" | grep "^# Untracked"` ; then
    color=%B%F{red}
  else
     color=%F{red}
  fi
  echo "$color$name$action%f%b"
}

RPS1='[`rprompt-git-current-branch`]'"[%{${fg[cyan]}%}%2~%{${fg[default]}%}]"

http://d.hatena.ne.jp/uasi/20091025/1256458798
こちらの記事かな。
gitのブランチ名を作業状態に応じた色で表示します。あと、今いるディレクトリをひとつ上の階層まで表示。お気に入りのプロンプト設定その2ですね。

setopt NULL_GLOB
setopt EXTENDED_GLOB

glob関連の設定。EXTENDED_GLOB設定するとTabでglobが展開されます。

setopt MENU_COMPLETE
setopt IGNORE_EOF
setopt NO_CLOBBER
setopt RM_STAR_WAIT

MENU_COMPLETE: Tab押したときに最初の補完候補をいきなり補完する。
IGNORE_EOF: ^dでターミナルを抜けない。
NO_CLOBBER: リダイレクトですでにファイルが存在する場合に上書きせずにエラーに
RM_STAR_WAIT: rm *実行時に確認される

setopt CORRECT_ALL
alias mv='nocorrect mv'
alias cp='nocorrect cp'
alias vi='nocorrect vi'
alias git='nocorrect git'
alias touch='nocorrect touch'
alias mkdir='nocorrect mkdir'

ファイル名が間違ってたときとかに修正する?って聞いてくれる。ちょっとやりすぎだった気がする。

ざっとこんな感じです。
ネット上にいくらでも落ちてるよなーと思いつつも、とりあえず。

よく使うvagrant plugin

最近書けてなかったので、メモがてら、よく使っているVagrantのpluginをいくつか紹介します。

sahara

GitHub - jedi4ever/sahara: a plugin for vagrant that allows you manage a sandbox state

vagrantのboxの状態を保存して、必要に応じて巻き戻したりできるようにするpluginです。
chef試したりしてるときにけっこう便利です。

# install
$ vagrant plugin install sahara

# sandbox modeをonに
$ vagrant sandbox on

# boxの状態を保存
$ vagrant sandbox commit

# 最後に保存した状態に巻き戻す
$ vagrant sandbox rollback

$ vagrant sandbox off

vagrant-global-status

http://www.ryuzee.com/contents/blog/6752

こちらの記事に詳しいです。いろんな案件でいくつも環境を作っているとどのプロジェクトに対応したboxが起動してるかを一発で把握するのがなかなか面倒になってきます。

そこでこれ。Vagrantfileの置かれたpathとそのVagrantfileで定義されたvmのstatusが一覧できるのですごく便利です。

$ vagrant plugin install vagrant-global-status
$ vagrant global-status -a

vagrant-protect

http://www.ryuzee.com/contents/blog/6788

最後がこちら。ryuzeeさんのblogを追ってれば使えるvagrant pluginはだいたいキャッチアップできるのでは、と思ってたりします。

VirtualBoxのみ対応らしいですが、設定するとうっかりvagrant destroyを防止できます。
僕はローカルに環境作るときはchef使って構築する癖を付けてて、だいたいまっさらなところからコマンド2つ3つでアプリ動かせるようにしてるのですが、それでも1からbuildするの時間かかるし、必要な仮想マシン消しちゃうとグッとくるものがあるので、最近は使うようにしています。

$ vagrant plugin install vagrant-protect

# Vagrantfile
...
    config.protect.enabled = true
...

まとめ

普段使ってるpluginをメモ代わりに紹介させてもらいました。
いつもすごい助かってます。

興味あってぼくもvagrant-configspecというpluginを書いてみたりもしたので、興味ある方はどうぞ。(実用的というよりは興味本位です)

vagrant-configspecを使ってプロビジョニングする - KAYAC engineers' blog

Mac OS X(Mountain Lion)のErlangでwxを動かしたい(動かせてない)

Erlang/OTP trainingでobserver便利そうだったけど、手元で動かせてなかったので。
ちなみに、以下のようなわけで64bitのMac OS Xではwxは動かないらしいです。
http://erlang.org/pipermail/erlang-questions/2013-March/072687.html

以下を参考に入れてみました。
https://gist.github.com/jj1bdx/5441077

Xquartz, homebrew, kerl, gitが必要という事で、Boxen使っているのとkerlは前回入れたのでクリアということで、Xquartzをboxenで入れるところから。

$ vi /opt/boxen/repo/modules/people/manifests/ankoromochi.pp
class people::ankoromochi {
…
    package {
        'XQuartz':
            provider => 'pkgdmg',
            source   => 'http://xquartz.macosforge.org/downloads/SL/XQuartz-2.7.4.dmg';
    }
…
}
$ boxen

wxgtk.rbはさっきのgistにあるやつを持ってくる。

$ brew install wxgtk.rb 
==> Downloading http://downloads.sourceforge.net/project/wxwindows/2.8.12/wxGTK-2.8.12.tar.gz
==> ./configure --with-libpng --with-opengl --with-libjpeg --with-libtiff --with-freetype --with-zlib --enable-unicode --prefix=/opt/boxen/homebrew/Cellar/wxgtk/2.8.12 ...
==> make install
brew: superenv removed: -I/opt/X11/include -Wall -Wundef -Wno-ctor-dtor-privacy -O2
clang: error: cannot specify -o when generating multiple output files
clang: error: cannot specify -o when generating multiple output files
make: *** [.pch/wxprec_qadll/wx/wxprec.h.gch] Error 1
make: *** [.pch/wxprec_htmldll/wx/wxprec.h.gch] Error 1

error出たので、以下を参考に修正。
https://github.com/mxcl/homebrew/issues/22124
wxgtk.rbに--disable-precomp-headersのoptionを追加してもっかい。
無事入ったので、patchのあたったErlangをbuildする。boxen使ってるとwxgtkが入るpathが違う。

$ KERL_CONFIGURE_OPTIONS="--enable-darwin-64bit \                  
                     --disable-hipe \
                     --enable-kernel-poll \
                     --enable-threads \
                     --enable-smp-support \
                     --with-wxdir=/opt/boxen/homebrew/opt/wxgtk \
                     --with-wx-config=/opt/boxen/homebrew/opt/wxgtk/bin/wx-config" \
./kerl build git https://github.com/jj1bdx/otp kr-r16b02-osx-wx kr-r16b02-osx-wx

…
beam/erl_bif_re.c:68:5: error: use of undeclared identifier 'erts_pcre_malloc'; did you mean 'erts_realloc'?
    erts_pcre_malloc = &erts_erts_pcre_malloc;
    ^~~~~~~~~~~~~~~~
…

と、ここでこける><
なぜか昨日は通ってた、全然関係ないErlangのbuildも通らなくなってしまった。
なんで通んなくなったんだろ・・・(まだ調べてる途中…)

今日はここまで。

Erlang/OTP training 2013に行ってきた

ふだん触ってないパラダイムの言語触るのはだいぶ楽しかったし、いい刺激になりました。
惜しいとすれば、すごい講師の方だったのでもう少し前提知識があればより貴重な情報を拾えただろうことと、@lhoguinはネットワーク系ならだいたいErlang使うって言ってたけど、会社ではなんだかんだPerl/Ruby/PHPになるので実践の機会がないとこですかね。あと、fault-tolerantなシステムの設計自体のハードルが高いです!!

Erlangは、関数型で平行プログラミングという僕にはあんまりなじみのない感じで面白かったです。あと、言語が高信頼度な分散システムを構築するための機能を提供してるってのもおもしろいですね。
講義内容は
https://gist.github.com/essen/6333394
な感じで、言語の特徴から基本的な概念の説明とか、途中から簡単な演習を挟みながら再帰やmessage passingなどについて説明していって、徐々にいろんな知識を積み上げながら、最後の方ではOTPのgen_server.erlを読むという感じでとても充実した内容でした。

けっこう細かいところまで説明していただいた感じでしたが、実践ではほぼOTP使うらしいので、OTP編やCowboyなどのWAF編なんかもあったらぜひ受けてみたいですね。
#ErlangTrainingJp でいろいろとつぶやかれてるので、追えば内容ぼんやりわかるかもしれません。

YAPCで聞いたRiakなんかはErlangのproductで、日本ではそんなに事例ないらしいけど、海外では運用されてるらしいので、ちょっと気になったり。

当日のスライド / ソースコードは以下に。
http://ninenines.eu/talks/thinking_in_erlang/thinking_in_erlang.html
https://gist.github.com/essen/6970232

あー、たのしかった。
たまにはこういう趣味の時間もいいもんだ。

Erlang/OTP training 2013に来てます

ひょんなきっかけで、Erlang/OTP trainingに参加して、Erlang触ってます。
普段使ってる言語とまったく違う感じで、すごいおもしろい。
言語自体がfault tolerantなsystemを作れるように、みたいな感じ。
key word的には

  • concurrency
  • error encapsulation
  • fault detection
  • fault identification
  • live code upgrade
  • stable storage

とか。

講義の内容はざっと

thinking_in_erlang.md · GitHub

こんな感じで、講師はCowboyっていう軽量HTTPサーバの作者のLoïc Hoguinさん。
なんだか、去年のErlang user of the yearらしい。


というわけで、installing Erlang on OS X 10.8.4
いろいろあるけど、詳しくは公式docment (Erlang -- Building and Installing Erlang/OTP)で、
一番簡単なのは

https://www.erlang-solutions.com/downloads/download-erlang-otp

にあるdmgからいれるのっぽい。

ぼくはここ(Linuxでkerlを使用して複数バーションのErlang/OTPを導入する - Qiita)を参考にして、kerl使っていれました。
kerl (GitHub - kerl/kerl: Easy building and installing of Erlang/OTP instances)はperlbrewみたいにbuild & installしてくれるやつ。

$ echo 'KERL_CONFIGURE_OPTIONS="--disable-hipe --enable-smp-support --enable-threads --enable-kernel-poll --enable-darwin-64bit" ' > ~/.kerlrc
$ curl -O https://raw.github.com/spawngrid/kerl/master/kerl
$ chmod a+x kerl
$ ./kerl build R16B02 r16b02
$ ./kerl install r16b02 ~/erlang/r16b02
$ . ~/erlang/r16b02/activate

これで、Erlang使えるようになります。

% erl
Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:8:8] [async-threads:10] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
1> 

Cloud Watchのカスタムメトリクスで特定のurlのhttp status codeを監視する

特定のurlのhttp status codeはこんな感じのshell scriptでとれる。
知らなかったけど、curlいろいろ出力整形できて便利だった。

$ cat /home/ec2-user/cloudwatch/http_status_check.sh
#!/bin/sh
curl -s $1 -o /dev/null -w "%{http_code}"

あとは、cloud watchの権限持ったユーザをIAMで作って、key/secretをcredentialsファイルとして設置したらこんな感じでcloud watchにデータ送れるのでcronにセットするなどして、そいつにアラートつくる。PATHちゃんと設定するかフルパス指定しないと、cronからmon-put-dataが見えないので注意。

$ cat /home/ec2-user/cloudwatch/custum_metrics.sh
#!/bin/bash

export JAVA_HOME=/usr/lib/jvm/jre
export AWS_CLOUDWATCH_HOME=/opt/aws/apitools/mon
export EC2_REGION=ap-northeast-1
export AWS_CREDENTIAL_FILE=/home/ec2-user/cloudwatch/credentials
instanceid=i-******

status=`/home/ec2-user/cloudwatch/http_status_check.sh http://example.com`
if [ $status -eq 200 ]; then
Fail=0
else
Fail=1
fi
/opt/aws/bin/mon-put-data --namespace "Custom Metrix" --metric-name "Http Status ok" --dimensions "InstanceId=$instanceid" --value "$Fail" --unit "Count"