heartbeatで仮想IPを冗長化

目的

仮想IPアドレス冗長化する。

仮想IP:192.168.24.130
fedora-a:192.168.24.111 <- heartbeat -> fedora-c:192.168.24.122

heartbeatのインストール

yum install heartbeat heartbeat-pils heartbeat-stonith

heartbeatの設定

  • 基本設定

/etc/ha.d/ha.cf

use_logd yes

keepalive 500ms
deadtime 10
warntime 5
initdead 60

ucast eth0 192.168.24.122

auto_failback off
#auto_failback on

node fedora-a
node fedora-c

uuidfrom nodename
ucast eth0 192.168.24.111

/etc/ha.d/haresources

fedora-a IPaddr::192.168.24.130/24
  • 認証の設定

/etc/ha.d/authkeys

auth 1
1 crc
    • 権限を変える
chmod 600 /etc/ha.d/authkeys

/etc/hosts

192.168.24.111 fedora-a
192.168.24.122 fedora-c
  • 起動と停止コマンド
service heartbeat start
service heartbeat stop

動かしてみた

fedora-a と fedora-c の両方の heartbeat を動かした後、しばらくすると fedora-a 側に仮想IPが設定された。

fedora-a の heartbeat を停止した後すぐに fedora-c に仮想IPが設定された。

再び fedora-a を立ち上げた後、fedora-c を停止したらすぐに fedora-a に仮想IPが設定された。


簡単すぎてビックリ

memcached1.4.5に対応したrepcaheをインストールしてみる

参考

  • ここで知った。

repcachedのmemcached 1.4対応 - Atzy-&gt;getLog()

  • 64bit版だとエラーがでる。
memcached.c:1074:16: error: dereferencing type-punned pointer will break strict-aliasing rules

"-Wstrict-aliasing"で抑止する。
フリーソフトウェア徹底活用講座(12)

Fedora14にインストール

Gitとコンパイルツールをインストール
yum install git
yum install libevent-devel
yum install autoconf automake sysconftool m4
memcachedのインストール
cd /usr/local/src
git clone https://github.com/mdounin/memcached.git
  • repcachedのブランチを引っ張ってくる
cd memcached
git pull origin repcached
export CFLAGS=-Wstrict-aliasing
./configure --enable-replication --enable-64bit
make
make install
mkdir -p /var/run/memcached
chown -R root:root /var/run/memcached
cp -p scripts/memcached-tool /usr/local/bin/.

memcachedのサービス設定

※ 前のコピー Repcacheを調べてみる - sadaharu28の日記

  • 設定ファイル作成

/etc/sysconfig/memcached

PORT=11211
USER=root
MAXCONN=1024
CACHESIZE=640
OPTIONS=
REPHOST=リモート側のノード

/etc/init.d/repcache

#! /bin/sh
#
# chkconfig: - 55 45
# description:  The memcached daemon is a network memory cache service.
# processname: memcached
# config: /etc/sysconfig/memcached
# pidfile: /var/run/memcached/memcached.pid

# Standard LSB functions
#. /lib/lsb/init-functions

# Source function library.
. /etc/init.d/functions

#EXEC=/opt/memcached-1.2.8-repcached-2.2/bin/memcached
EXEC=/usr/local/bin/memcached
REPHOST=localhost

PORT=11211
USER=memcached
MAXCONN=1024
CACHESIZE=64
OPTIONS=""

if [ -f /etc/sysconfig/memcached ];then
        . /etc/sysconfig/memcached
fi

# Check that networking is up.
. /etc/sysconfig/network

if [ "$NETWORKING" = "no" ]
then
        exit 0
fi

RETVAL=0
prog="repcache(memcached)"
pidfile=${PIDFILE-/var/run/memcached/memcached.pid}
lockfile=${LOCKFILE-/var/lock/subsys/memcached}

start () {
        echo -n $"Starting $prog: "
        # Ensure that /var/run/memcached has proper permissions
        if [ "`stat -c %U /var/run/memcached`" != "$USER" ]; then
                chown $USER /var/run/memcached
        fi

        daemon --pidfile ${pidfile} $EXEC -d -x $REPHOST -p $PORT -u $USER  -m $CACHESIZE -c $MAXCONN -P ${pidfile} $OPTIONS
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ] && touch ${lockfile}
}
stop () {
        echo -n $"Stopping $prog: "
        killproc -p ${pidfile} $EXEC
        RETVAL=$?
        echo
        if [ $RETVAL -eq 0 ] ; then
                rm -f ${lockfile} ${pidfile}
        fi
}

restart () {
        stop
        start
}

# See how we were called.
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  status)
        status -p ${pidfile} $prog
        RETVAL=$?
        ;;
  restart|reload|force-reload)
        restart
        ;;
  condrestart|try-restart)
        [ -f ${lockfile} ] && restart || :
        ;;
  *)
        echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart|try-restart}"
        RETVAL=2
        ;;
esac

exit $RETVAL
chmod +x /etc/init.d/repcache
chkconfig --add repcache
chkconfig repcache on
chkconfig --list | grep repcache
  • サービスとして起動&停止
service repcache start
service repcache stop

cache_fuとcache_fu_find_hookを使ってみる

環境

OS Window XP
Ruby 1.8.7p302(MinGW)
Rails 2.3.10

cache_fuとcache_fu_find_hookのインストール

cache_fuのインストール
ruby script/plugin install git://github.com/defunkt/cache_fu.git

エラーになるので・・・

Initialized empty Git repository in C:/rails2/app2/vendor/plugins/cache_fu/.git/

remote: Counting objects: 39, done.
remote: Compressing objects: 100% (37/37), done.
remote: Total 39 (delta 3), reused 23 (delta 0)
Unpacking objects: 100% (39/39), done.
From git://github.com/defunkt/cache_fu
 * branch            HEAD       -> FETCH_HEAD

** Checking for memcached in path...
script/plugin: No such file or directory - which memcached
Plugin not found: ["git://github.com/defunkt/cache_fu.git"]

vendor/plugins/cache_fu/install.rb の以下をコメントアウト

puts "** Checking for memcached in path..."
if `which memcached`.strip.empty?
  $errors += 1
  puts "!! Couldn't find memcached in your path.  Are you sure you installed it? !!"
  puts "!! Check the README for help.  You can't use acts_as_cached without it.  !!"
end

もう一度インストール

ruby vendor/plugins/cache_fu/install.rb
** Checking for memcache-client gem...
** Trying to copy memcached.yml.default to ./config/memcached.yml...
** Trying to copy memcached_ctl.default to ./script/memcached_ctl...

** acts_as_cached installed with no errors. Please edit the memcached.yml file t
o your liking.
** Now would be a good time to check out the README.  Enjoy your day.
cache_fu_find_hookのインストール
ruby script/plugin install git://github.com/morimori/cache_fu_find_hook.git

config/environment.rbを修正。

Rails::Initializer.run do |config|
  ...
  config.plugins = [:cache_fu, :cache_fu_find_hook, :all]
  ...
end

使ってみる

Modelにこれだけを定義

models/user.rb

class User < ActiveRecord::Base
  acts_as_cached
  after_save :reset_cache
  after_destroy :expire_cache
end
有効期限やfinderを指定することも可能
acts_as_cached :ttl => 1.minutes,
               :find_by => "col1"

※ finder を変更した場合は、:reset_cache, :expire_cache等は使えない。

Observerに定義できるか確認してみた

models/sqlite_observer.rb

class SqliteObserver < ActiveRecord::Observer
  observe :user
  
  acts_as_cached
  after_save :reset_cache
  after_destroy :expire_cache
end

エラーになる・・・

C:/rails2/app2/vendor/plugins/cache_fu_find_hook/lib/cache_fu_find_hook.rb:20:in `acts_as_cached': undefined method `find' for class `Class' (NameError)
	from C:/rails2/app2/app/models/sqlite_observer.rb:4
	from c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.10/lib/active_support/dependencies.rb:406:in `load_without_new_constant_marking'
・・・

Observerに設定するのは無理っぽい

cache_fuのsessionについて

config/memcached.yml の sessions: を true にすると、セッション作成時にエラーになってしまう。

"1424"
=> Booting Mongrel
=> Rails 2.3.10 application starting on http://0.0.0.0:3002
=> Call with -d to detach
=> Ctrl-C to shutdown server
Wed Jan 12 01:10:08 +0900 2011: Read error: #<RuntimeError: #<ActionController::Session::MemCacheStore:0x2895468> unable to find server during initialization.>
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.10/lib/action_controller/session/mem_cache_store.rb:21:in `initialize'
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.10/lib/action_controller/middleware_stack.rb:72:in `new'
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.10/lib/action_controller/middleware_stack.rb:72:in `build'
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.10/lib/action_controller/middleware_stack.rb:116:in `build'
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.10/lib/active_support/inflector.rb:364:in `inject'
・・・

環境の所為なのか、どうなのか分からないが、cache_fuのsessionは使えない模様。


ただ、config/memcached.yml の sessions: を false にして、別にセッションの保管先をmemcachedに指定することで、セッションをmemcachedに保管することは可能。

以下、確認した時の session_store.rb と cache_fu の設定


config/initializers/session_store.rb

config = YAML.load(ERB.new(IO.read(File.dirname(__FILE__) + "/../memcached.yml")).result)

ActionController::Base.session = {
  :memcache_server => config["defaults"]["servers"],
  :expire_after => config["defaults"]["session_ttl"],
  :namespace => config["defaults"]["namespace"] + "-#{ENV['RAILS_ENV']}",
  :key => "_app2_session",
  :secret => "789db92e8a6162f2185368ab0a1e5d8f2761bb25ebfb88e0b3e7ae4fe803d42981d32d96ca544aeb178e840f00f477c83bd370ff7d11f3de3f232052b59c83ce"
}

ActionController::Base.session_store = :mem_cache_store

config/memcached.yml

defaults:
  ttl: 1800
  readonly: false
  urlencode: false
  c_threshold: 10000
  compression: true
  debug: false
  namespace: app2
  sessions: false
  session_servers: false
  fragments: true
  memory: 640
  servers:
    - 192.168.24.111:11211
    - 192.168.24.122:11211
  benchmarking: false
  raise_errors: false
  fast_hash: false
  fastest_hash: false
  session_ttl: 86400

Repcacheを調べてみる

Repcacheを調べるに至るまでの経緯

Railsにおけるデータの高速化及び可用性を高めたい!
ということで、ネットを彷徨っていたら memcachedという素晴らしいツールがあるということが分かった。


高速化については、memcachedでキャッシュサーバを構築し、セッションやDBのデータをそこで保管する方法がある。
しかし、memcached単体では単なるキャッシュサーバとしてしか機能せず、データの保全という意味では冗長化は望めない。
もともと冗長化のコンセプトはなさそうなので、仕方のないところ。


Webアプリ側で、冗長化を図ることは可能だが、アプリ側が意識しないところでデータが冗長化されているのが望ましい。
そして、なによりRails(Ruby)に、memcached冗長化させるオプションがない。
なお、PHPには"redundancy"というオプションがあるらしい。


ネットを彷徨っていると、memcachedレプリケーション機能を実装しているrepcacheに出会った。
これを利用しない手はない。


http://lab.klab.org/wiki/Repcached
素晴らしいツールをありがとうございますm(_ _)m

環境

OSと使用パッケージなど

OS Fedora14(x86_64)*1
memcached 1.2.8
repcache 2.2

レプリケーションの構成

node1:* node2:11212
node1:11212 node2:*
準備作業
  • 既存のmemcachedを無効にする(memcachedがインストールされている場合のみ)
chkconfig memcached off
service memcached stop
service iptables stop
chkconfig iptables off
chkconfig ip6tables off 
vi /etc/sysconfig/selinux
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - SELinux is fully disabled.
SELINUX=disabled
# SELINUXTYPE= type of policy in use. Possible values are:
# targeted - Only targeted network daemons are protected.
# strict - Full SELinux protection.
SELINUXTYPE=targeted
  • とりあえずupdate
yum update -y
  • 必要なパッケージをインストール

memcached-tool を使いたいので memcached もインストール

yum install -y memcached wget libevent-devel make gcc gcc-c++
  • なんとなく再起動
reboot
Repcacheのインストール
wget http://downloads.sourceforge.net/repcached/memcached-1.2.8-repcached-2.2.tar.gz
tar zxf memcached-1.2.8-repcached-2.2.tar.gz
cd memcached-1.2.8-repcached-2.2
./configure --enable-replication --prefix=/opt/memcached-1.2.8-repcached-2.2
make
make install
  • 起動テスト
    • node1側
cd /opt/memcached-1.2.8-repcached-2.2/bin
./memcached -v -x node2 -u root
    • node2側
cd /opt/memcached-1.2.8-repcached-2.2/bin
./memcached -v -x node1 -u root
    • Rails側でセッションをmemcachedに保管するように設定

config/initializers/session_store.rb

ActionController::Base.session = {
  :memcache_server => ["node1:11211", "node2:11211"],
  :expire_after => 10,
  :namespace => "app-#{ENV['RAILS_ENV']}",
  :key         => '_app_session',
  :secret      => '789db92e8a6162f2185368ab0a1e5d8f2761bb25ebfb88e0b3e7ae4fe803d42981d32d96ca544aeb178e840f00f477c83bd370ff7d11f3de3f232052b59c83ce'
}

ActionController::Base.session_store = :mem_cache_store
    • Railsのアプリを適当に動かす
    • node1側のキャッシュを確認
memcached-tool node1 dump
    • node2側のキャッシュを確認
memcached-tool node2 dump
    • 両nodeに同じ情報が書き込まれていたら成功

Dumping memcache contents
Number of buckets: 1
Number of items : 1
Dumping bucket 6 - 1 total items
add app-development:35863ec3d930ce3d21239e4de3e31293 0 1294514243 211

Repcache用のサービス作成
vi /etc/init.d/repcache
#! /bin/sh
#
# chkconfig: - 55 45
# description:  The memcached daemon is a network memory cache service.
# processname: memcached
# config: /etc/sysconfig/memcached
# pidfile: /var/run/memcached/memcached.pid

# Standard LSB functions
#. /lib/lsb/init-functions

# Source function library.
. /etc/init.d/functions

EXEC=/opt/memcached-1.2.8-repcached-2.2/bin/memcached
REPHOST=localhost

PORT=11211
USER=memcached
MAXCONN=1024
CACHESIZE=64
OPTIONS=""

if [ -f /etc/sysconfig/memcached ];then
        . /etc/sysconfig/memcached
fi

# Check that networking is up.
. /etc/sysconfig/network

if [ "$NETWORKING" = "no" ]
then
        exit 0
fi

RETVAL=0
prog="repcache(memcached)"
pidfile=${PIDFILE-/var/run/memcached/memcached.pid}
lockfile=${LOCKFILE-/var/lock/subsys/memcached}

start () {
        echo -n $"Starting $prog: "
        # Ensure that /var/run/memcached has proper permissions
        if [ "`stat -c %U /var/run/memcached`" != "$USER" ]; then
                chown $USER /var/run/memcached
        fi

        daemon --pidfile ${pidfile} $EXEC -d -x $REPHOST -p $PORT -u $USER  -m $CACHESIZE -c $MAXCONN -P ${pidfile} $OPTIONS
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ] && touch ${lockfile}
}
stop () {
        echo -n $"Stopping $prog: "
        killproc -p ${pidfile} $EXEC
        RETVAL=$?
        echo
        if [ $RETVAL -eq 0 ] ; then
                rm -f ${lockfile} ${pidfile}
        fi
}

restart () {
        stop
        start
}

# See how we were called.
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  status)
        status -p ${pidfile} $prog
        RETVAL=$?
        ;;
  restart|reload|force-reload)
        restart
        ;;
  condrestart|try-restart)
        [ -f ${lockfile} ] && restart || :
        ;;
  *)
        echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart|try-restart}"
        RETVAL=2
        ;;
esac

exit $RETVAL

※/etc/init.d/memcachedをコピーして作った。

chmod +x /etc/init.d/repcache
chkconfig --add repcache
chkconfig repcache on
chkconfig --list | grep repcache
vi /etc/sysconfig/memcached
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=""

REPHOST=node1
  • サービスとして起動&停止
service repcache start
service repcache stop

調べた結果

OS的に同一スペックのマシン同士であれば、何の支障もなくレプリケーションできる。
しかし少しでもスペックが異なるとレプリケーション出来なかった。

  • 試した例と結果
node1 node2 結果 内容
CentOS 5.5 32bit Fedora14 64bit NG node1からnode2にコピーした場合、node2が残りっぱなしになる。node2からnode1にコピーできない。
Fedora14 64bit(ノーマル) Fedora14 64bit(Oracleインストール版)*2 NG node1からnode2にコピーした場合、node2が残りっぱなしになる。node2からnode1にコピーできない。
Fedora14 64bit(ノーマル) Fedora14 64bit(ノーマル) OK 正常に動いた。


レプリケーションを有効にした場合、マルチスレッドが無効になるところが辛いが、Webアプリの高速化と可用性を向上する方法は確認できた。
とりあえず、Repcacheを導入する方向で進めよう。

*1:本当は使い慣れたCentOSの64bit版にインストールしたかったが、memcachedが提供されていなかったので、Fedoraの64bit版にした。

*2:セマフォや共有メモリの設定がノーマル側と異なる。という意味です。

Rails+SQL Serverの文字コード対策

環境

Ruby 1.8.7p302
Rails 2.3.10
DB SQL Server

文字コード

クライアント(ブラウザ) UTF-8
DB CP932
$KCODEとWIN32OLE

config/environment.rb に次のような設定をしたが、更新時にエラーになる。

$KCODE = "u"
require "win32ole"
WIN32OLE.codepage=WIN32OLE::CP_UTF8




エラーメッセージのコピペ

ODBC::Error: 37000 (105) [Microsoft][SQL Native Client][SQL Server]Unclosed quotation mark after the character string '&#65533;E&#65533;010年ももぁE&#65533;&#65533;ぐ終わめE)'.: INSERT INTO [kamis] ([naiyou], [created_at], [updated_at], [odai]) VALUES(N'大晦日も仕事(;ω;)', '2010-12-30 21:50:58.594', '2010-12-30 21:50:58.594', N'2010年ももうすぐ終わり')

参考:
http://yugui.jp/articles/572
SQL Serverを使う - azuki note


参考にしたページでは上手いこといくらしい。。。
自分の環境ではエラーになっちゃうのは何故?

Modelのコールバックを使う

Model の コールバックで変換を行うことでエラーにならないようになった。

class Kami < ActiveRecord::Base
  
  def after_find
    self.odai = Iconv.conv("UTF-8", "CP932", self.odai)
    self.naiyou = Iconv.conv("UTF-8", "CP932", self.naiyou)
  end

  def before_save
    self.odai = Iconv.conv("CP932", "UTF-8", self.odai) if self.odai_changed?
    self.naiyou = Iconv.conv("CP932", "UTF-8", self.naiyou) if self.naiyou_changed?
  end

end


参考:
Ruby on Rails - Tips - かえるの尻尾
ActiveRecord::Callbacks
http://blog.mizzy.org/articles/2005/12/30/learningRails06
after_initializeに関して - カノ エ ノ メモ - cucumberグループ
http://jror4883.rubyforge.org/svn/trunk/activerecord/lib/active_record/callbacks.rb

RailsのPooling機能覚書

環境

Ruby 1.8.7p302
Rails 2.3.10
DB SQL Server
接続タイムアウトは指定できる?

何のことはない database.yml に wait_timeout を指定するだけで良かった。
因みに秒指定。

development:
  adapter: sqlserver
  mode: odbc
  dsn: mssql_development
  database: testdb_dev
  username: testuser
  password: ********
  pool: 8
  wait_timeout: 8

だけど、これはRubyのバージョンが1.9未満の場合だけらしい。


activerecord-2.3.10/lib/active_record/connection_adapters/abstract/connection_pool.rb

        # default 5 second timeout unless on ruby 1.9
        @timeout =
          if RUBY_VERSION < '1.9'
            spec.config[:wait_timeout] || 5
          end

Ruby1.9を使う場合は、Railsのバージョンも3に上げる必要があるっつうことですな。

Failoverに対応してる?

ネットを彷徨っていると、対応していない、というページがヒットした・・・


参考:
Rails(ActiveRecord)でデータベースへのコネクションプーリングをさせなくする - はまさき
(LVS + keepalivedだとダメ、ということみたい。)


それは困る!
ということで、とりあえず2.3.10のソースコードを調べてみた。
checkoutの際、新規接続も既存接続も verify! が呼ばれるようになっている。


activerecord-2.3.10/lib/active_record/connection_adapters/abstract/connection_pool.rb

      def checkout_and_verify(c)
        c.verify!
        c.run_callbacks :checkout
        @checked_out << c
        c
      end

もう少し掘り下げて、SQL Serverのアダプタで、verify! が有効か調べてみた。


activerecord-2.3.10/lib/active_record/connection_adapters/abstract_adapter.rb

      # Checks whether the connection to the database is still active (i.e. not stale).
      # This is done under the hood by calling <tt>active?</tt>. If the connection
      # is no longer active, then this method will reconnect to the database.
      def verify!(*ignored)
        reconnect! unless active?
      end

activerecord-sqlserver-adapter-2.3.13/lib/active_record/connection_adapters/sqlserver_adapter.rb

      # CONNECTION MANAGEMENT ====================================#
      
      def active?
        connected = case @connection_options[:mode]
                    when :dblib
                      !@connection.closed?
                    when :odbc
                      true
                    else :adonet
                      true
                    end
        return false if !connected
        raw_connection_do("SELECT 1")
        true
      rescue *lost_connection_exceptions
        false
      end

      def reconnect!
        disconnect!
        connect
        active?
      end

有効っぽいな・・・とりあえず安心。
(時間が出来たら実際に試してみよう。)


もっと詳しく調べている人がいた。


参考:
ActiveRecordのDBコネクションの接続切れと再接続について。reconnectオプションは危険だなーとかも - odeの開発メモ日記