10分でできるSwitchtower

前回の用意を踏まえて、10分でできるSwitchtower本編です。
本エントリでは以下のシナリオで、基本的なSwitchtowerの動きを追っていきたいと思います。

  1. サンプルアプリケーションをsubversionからチェックアウト
  2. Switchtower-izeをと基本的な設定
  3. 実際にdeploy
  4. アプリケーションに修正を加えて再度deploy
  5. deployしたあとに障害発生、rollback
  6. 障害修正したものを再度deploy. 障害収束

かなり長くなってしまったのでご注意を。ホントに10分でできるかは不明です。30分以内にはできると思います。ではどうぞ。

サンプルアプリケーションをsubversionからチェックアウト

前回のエントリで作成したサンプルアプリケーションを用意します。すでにあるひとはそのままで。もしないという方は、こちらにtar.gzを用意しましたのでお使いくださいませ。

 $ tar xzf sw_10m.repos.tar.gz # 必要な場合のみ。
 $ svn checkout file:////home/moro/switchtower_10m_cooking/sw_10m.repos BookShelf.work
   (略)

Switchtower-izeと基本的な設定

ここからがようやくSwitchtowerの使いかたに入ります。
まずは、switchtower --apply-to オプションでアプリケーションにSwitchtowerをapplyします。

 $ switchtower --apply-to BookShelf.work
     exists  config
     create  config/deploy.rb
     exists  lib/tasks
     create  lib/tasks/switchtower.rake

ここではconfig/deploy.rbとlib/tasks/switchtower.rakeのふたつのファイルができています。

config/deploy.rb::その名のとおりswitchtowerの設定ファイルです。このファイルを修正することでdeploy用の各種設定を行います。
lib/tasks/switchtower.rake::switchtower-izeしたあとでrake -Tをするといろんなタスクが増えていますが、その追加タスクはこのファイルで定義されています。今回のチュートリアルでは直接修正することはありませんが、使い倒していくためにもご一読することをお薦めします。

次はconfig/deploy.rbを編集します。

 $ cd /home/moro/switchtower_10m_cooking/BookShelf.work
 $ vim config/deploy.rb

変更すべきところは、6行目付近に「REQUIRED VARIABLES」とある(1)アプリケーション
名、(2)レポジトリURI、17行目付近から始まる「ROLES」の(3)各サーバの役割、33行目
付近の(4)deploy_toです。では順番に。

まずは「REQUIRED VARIABLES」(1)(2)

 set :application, "BookShelf"
 set :repository, "file:////home/moro/switchtower_10m_cooking/sw_10m.repos"

続いて「ROLES」の定義(3)。今回はすべて一台のローカルマシンで賄うので、次のようにします。

 role :web, "localhost"
 role :app, "localhost"
 role :db,  "localhost", :primary => true

さらに(4)deploy_toを設定します。これはアプリケーションの配置ディレクトリになる
んですが、今回は/home/moro/switchtower_10m_cooking/webapp以下とします。
にします。

 set :deploy_to, "/home/moro/switchtower_10m_cooking/webapp"

以上で基本的な設定は完了です。

実際にdeploy

では実際にdeployしてみましょう。

まずは以下のコマンドでSwitchtowerでdeployするときの基本となるディレクトリ構造を作成します。*1 *2

 $ rake remote_exec ACTION=setup
   (in /home/moro/switchtower_10m_cooking/BookShelf.work)
   loading configuration
   /usr/lib/ruby/gems/1.8/gems/switchtower-0.10.0/lib/switchtower/recipes/standard.rb
   loading configuration ./config/deploy.rb
   executing task setup
   executing "mkdir -p -m 775
   /home/moro/switchtower_10m_cooking/webapp/releases
   /home/moro/switchtower_10m_cooking/webapp/shared/system &&\n    mkdir -p -m
   777 /home/moro/switchtower_10m_cooking/webapp/shared/log"
   servers: ["localhost"]
   Enter password for /home/moro/.ssh/id_dsa: xxxxxxxxxxxxxxxxxxx

   processing command
   [localhost] executing command
   command finished

これをおこなうと、以下のようなディレクトリ構造ができています。このなかでreleases配下が各回のdeployしたアプリの置き場所となり、shared/logは各リリース間で共有されるディレクトリになります。

 $ cd ..
 $ find ./webapp
   ./webapp
   ./webapp/shared
   ./webapp/shared/system
   ./webapp/shared/log
   ./webapp/releases

ここまでうまく完了したらswitchtower --apply-toしたときに加えられたファイルをレポジトリに登録します。

 $ svn add config/deploy.rb lib/tasks/switchtower.rake
 $ svn commit -m "add Switchtower config files."

いよいよdeployします。*3

 $ rake deploy
  (略)
  command finished

画面には、実際に実行されたコマンドとその実行結果が出力されます。内部の動きを理解できますので、読んでみることをお薦めします。

さて、deploy結果ですが、上でdeploy_toに定義した"/home/moro/switchtower_10m_cooking/webapp"以下に、アプリケーション本体がdeployされているはずです。早速確認してみましょう。

 $ cd /home/moro/switchtower_10m_cooking/webapp 
 $ find ./releases  -type d -maxdepth 2
   ./releases
   ./releases/20060116145842
   ./releases/20060116145842/config
   ./releases/20060116145842/components
   ./releases/20060116145842/.svn
   ./releases/20060116145842/lib
   ./releases/20060116145842/public
   ./releases/20060116145842/script
   ./releases/20060116145842/test
   ./releases/20060116145842/app
   ./releases/20060116145842/doc
   ./releases/20060116145842/db
   ./releases/20060116145842/vendor
 $ ls -l current
   (略) current -> /home/moro/switchtower_10m_cooking/webapp/releases/20060116145842

いい感じですね。では、実際に起動して動作確認をしてみましょう。

 $ cd /home/moro/switchtower_10m_cooking/webapp/current
 $ ruby script/server

 $ firefox http://localhost:3000/books/list & # 別端末、ないしブラウザから入力


いつものようにscaffold画面が表示されましたか?表示されていれば成功です。もしうまくいかない、という方がいれはコメントなどでその旨おっしゃっていたければ何かお手伝いできるかもしれません。
動作を確認したらCtrl+Cでlighttpdを停止させておいてください。

アプリケーションに修正を加えて再度deploy

お気付きかもしれませんが、上の例では本番環境にdeployしたあともrailsはdevelopment環境で動作しています。やはりここはproduction環境で動かしたいもの。暫定的ではありますが、config/lighttpd.confを書き換えてproduction環境で動かしてしまいましょう。

 $ cd /home/moro/switchtower_10m_cooking/BookShelf.work
 $ vim config/lighttpd.conf
  --
  (24行目付近)
         "bin-path"  => "public/dispatch.fcgi",
-        "bin-environment" => ( "RAILS_ENV" => "development" )
+        "bin-environment" => ( "RAILS_ENV" => "production" )
     )

で、これもcommitします。

 $ svn commit -m "activate *production* environment."

ここまでの変更が終わったら、変更を有効にするため、再度deployします。
deploy後のディレクトリ構造を見てみると、deployした日付でディレクトリツリーができており、currentのリンク先も更新されているのが分かると思います。

 $ rake deploy
   (略)
 $ cd /home/moro/switchtower_10m_cooking/webapp
 $ find ./releases -type d -maxdepth 1 
   ./releases
   ./releases/20060116145842
   ./releases/20060116151802
 $ ls -l current
  (略) current -> /home/moro/switchtower_10m_cooking/webapp/releases/20060116151802/

deployしたあとに障害発生、rollback

ではさっそくproduction環境を試してみましょう。先ほど修正したlighttpd.confを使ってscript/serverを実行すると、Railsアプリがproduction環境で実行されます。

 $ cd /home/moro/switchtower_10m_cooking/webapp/current
 $ ruby script/server

 $ firefox http://localhost:3000/books/list & # 別端末、ないしブラウザから入力


どう見ても障害です。たいへんありがとうございました。

なにはともあれ緊急復旧が必要ですね、ということでrollbackします。

 $ rake rollback

rollback結果を確認してみましょう。

 $ cd /home/moro/switchtower_10m_cooking/webapp
 $ find ./releases -type d -maxdepth 1 
   ./releases
   ./releases/20060116145842

先ほどdeployしたものが消え、直前の動いていたバージョンの身が残っていることがわかると思います。
では再度 http://localhost:3000/books/list へアクセスしてみてください。ちゃんと表示されましたか? current/log 配下を見るとわかるとおり、development環境が動作しています。

障害修正したものを再度deploy. 障害収束

緊急復旧が完了したところで、なぜ先ほどdeployした物に障害発生してしまったんでしょうか。

実は今回の手順では config/database.yml に定義したproduction環境用のデータベース(db/book_shelf)を作っていませんでした。ということは、production用のDBを作ってあげればよさそうですね。

幸いにも、前回のエントリではテーブル定義の段階からmigrationを使って行っていました。せっかくですから、Switchtowerの中からmigrationを使ってDBを作成することにします。

まずはconfig/deproy.rbを確認してみますと、long_deployというタスクでmigrationを使ったdeproyが定義されていま。

 $ cd /home/moro/switchtower_10m_cooking/BookShelf.work
 $ cat confg/deproy.rb
 (略)
 desc "A task demonstrating the use of transactions."
 task :long_deploy do
   transaction do
     update_code
     disable_web
     symlink
     migrate
   end

   restart
   enable_web
 end

あとはこれをrakeから呼べるようにしてあげればよさそうですね。lib/tasks/switchtower.rakeに次のコードを追加します。

(lib/tasks/switchtower.rake)
 +
 +desc "Push the latest revision into production and migrate DB."
 +task :long_deploy do
 +  switchtower_invoke :long_deploy
 +end
 +

その後、いつものようにcommitします。

 $ svn commit -m "add long_deploy task."

これで準備は整いました。いざdeproyして確認してみましょう。

 $ rake long_deploy
 (略)
 $ cd /home/moro/switchtower_10m_cooking/webapp
 $ find ./releases -type d -maxdepth 1
   ./releases
   ./releases/20060116145842
   ./releases/20060118144125
 $ ls -l current
   current -> /home/moro/switchtower_10m_cooking/webapp/releases/20060118144125/

よさそうですね。ではlighttpdをCtrl+Cで停止し、再度script/serverでサーバを再起動させましょう。*4

 $ cd /home/moro/switchtower_10m_cooking/webapp/current
 $ ruby script/server

ではまたまた http://localhost:3000/books/list へアクセスしてみましょう。

今度こそよさそうですね。

何も表示されないのは、現在使っているproduction用のDBが作成されたばかりで空だからです。CRUDをすると、log/production.logやdb/book_shelfが更新されているのがわかると思います。

以上、アプリケーションのswitchtowerizeからdeproy、rollback、再deproyまでつっ走ってみたわけですが、いかがでしたでしょうか?

長々とおつきあいいただき、大変ありがとうございました。

まとめ

  • Switchtowerを使うための基本設定は4ステップ。
    • switchtower --apply-to (app)
    • config/deproy.rbを編集
    • rake remote_exec ACTION=setup
    • rake deploy
  • rollbackをすると動いていた(rollbackされた)バージョンのコピーは本番環境から削除される。
  • migrationするのは:long_deployタスク。lib/tasks/switchtower.rakeへの追加も必要。
  • restartタスクがまともに動かないのはもろはしの宿題ということで、あとで書く、かも。
  • update_codeとかsvn exportする設定とか、最近入ってきた機能もいろいろ試してみたいですね。

誤字脱字から内容の不備・誤りの指摘、うまく動かねーよというお問い合わせまでいろいろとどうぞ。

*1:Switchtowerを介した各種操作ではsshを使います。そのため、事前にローカルでsshdを起動させておいてください。

*2:sshでログインする際には入力したパスワードがそのままが面に表示されてしまいます。御注意を。

*3:本家のチュートリアルではrestartタスクも修正していましたが、現在ではrestartはscript/process/reaperで行うため、書き換え不要です

*4:restartタスクで再起動されるはずなんですが、私の環境ではCtrl+Cで一端停止→再起動させないと変更がうまく反映されませんでした。環境依存なのか、あとで調査してみます。