Herokuへのデプロイのデモで失敗しないための手順

先日の セミナーの中で Heroku を使えば Ruby on Rails アプリの公開(デプロイ)は超簡単! という話しをしデモを行ったのですが、見事に失敗してしまいました ^^;

2度と失敗しないように記事を書きました。


失敗する手順

1. セミナー等でデモを行う場合は事前に練習!!

と言うことで以下の手順で無事にデプロイ出来る事を確認

$ heroku create
$ git push heroku master
$ heroku run rake db:migrate
$ heroku open
2. heroku の dashboard でアプリを削除

上の画像のような dashboard で練習で作ったアプリを削除

3. セミナー本番

1. と同じ手順でデプロイを実行すると・・・・

$ heroku create
$ git push heroku master

 !  No such app as xxxx-yyyy-9999.

なんと、エラー! 何度かリトライする内に原因が判ったのですが、焦っていたので失敗し、時間切れでデモは中止 (v_v)

失敗しない手順 (1)

1. セミナー等でデモを行う場合は事前に練習!!

と言うことで以下の手順で無事にデプロイ出来る事を確認

$ heroku create
$ git push heroku master
$ heroku run rake db:migrate
$ heroku open
2. heroku コマンド でアプリを削除
$ heroku list
=== My Apps
xxxx-zzzzz-2222    ← アプリのID. destroyコマンドで指定
$ heroku destroy  xxxx-zzzzz-2222
 !    WARNING: Potentially Destructive Action
   ...
> xxxx-zzzzz-2222  ← アプリのID を再入力
3. セミナー本番

1. と同じ手順でデプロイを実行すれば 成功!

失敗しない手順 (2)

1. セミナー等でデモを行う場合は事前に練習!!

と言うことで以下の手順で無事にデプロイ出来る事を確認

$ heroku create
$ git push heroku master
$ heroku run rake db:migrate
$ heroku open
2. heroku のdashboard でアプリを削除

上の画像のような dashboard で練習で作ったアプリを削除、そして以下のgitコマンドを実行。

$ git remote remove heroku
3. セミナー本番

1. と同じ手順でデプロイを実行すれば 成功!

失敗した理由

失敗しない手順 (2) で判ると思いますが、heroku create コマンドは .git/config に heroku というリモートリポジトリーを設定するのですが、既にある場合は変更しません。
失敗した手順では git push heroku master を行った時点でアプリが無いのでエラーになったわけです。
したがって herokuコマンドでアプリを削除(コマンドはリモートリポジトリーの設定を削除してくれます)するか git コマンドで heroku リモートリポジトリーを削除すれば OK です。

さくらのクラウドでサーバーのIPアドレスを変えずにサーバーを入れ替える方法

クラウドでサービスインしているサーバーのOSやサーバー環境(RDB、Webサーバー、言語・・・)を更新するには、新たにサーバーを準備し更新した環境を作りサーバーを入れ替えるのがダウンタイムが少なく良いですよね。Amazon EC2 には Elastic IP アドレス という機構があり、保持しているIPアドレスに任意の EC2サーバーを割り当てられるので、同一IPでのサーバー入れ替えは簡単です。
しかし、さくらのクラウド にはElastic IP アドレスのような機構は無く、同一IPでのサーバー入れ替えは簡単には出来ませんが、短時間のダウンタイムで入れ替える事が出来る事が分かりました。


方法

さくらのクラウドは、サーバーを作成すると(グロバールな)IPアドレスが付いてきますが、既に使っているサーバーのIPアドレスを再割り当ては出来ません。サーバーを作成する際にストレージ(ディスク、SSD)も作成しますが、ストレージはサーバーから切り離し別々に管理で来ます。
また、ストレージ管理メニューの「ディスク修正」でストレージ内のOSに設定されているホスト名、IPアドレス等を書き換える事が出来ます。この「ディスク修正」を使う事で新たなサーバー用に作ったストレージのIPアドレスを書き換え、従来のサーバーにつなぎ替える事で同一IPで新しいOS/サーバー環境に入れ替える事が出来ます。

手順

1. 新しいサーバーを作成し、新しい環境を構築する

クラウドの管理画面でサーバーを作成し、起動したら Chef などで新しい環境を構築します。

2. 新しいサーバーを止めて、ストレージを外す

クラウドのサーバー管理メニューでサーバーからストレージを外します


3. ディスク修正でIPアドレス等を変更する

クラウドのストレージ管理メニューのディスク修正で旧サーバーと同じ値を設定する


4. サービスインしているサーバーを止め、ストレージを付け替える

クラウドの管理画面で現在サービスインしているサーバーを止め、 2.と同様にストレージを外し、下の画像のように 新サーバーに付いていたストレージを割付け、サーバーを起動。
この作業の間はサービスが停止しますが、予め練習しておけば時間は数分だと思います。

以上です。

おまけ、さくらのクラウドの感想

お客様のサービスをさくらの専用サーバーからクラウドに移行して運用していますが、Amazon EC2と比べた感想は

良い点

  • 値段が安い。一時的な利用ではなく、基本的に動かしたままの場合はサーバーの利用料が安い
  • 同一価格で比較すると、サーバーが早くてインストール等が早く済む。CPUやメモリー容量も多い上、SSDも使えるので早いです。
  • 質問にはちゃんとした回答がある (Amazonでは質問したことが無いので比較はわかりません)

悪い点

  • 管理コンソールが使いにくい。Webベースの管理コンソールはバージョンアップし、使いやすさは向上していますが、まだまだAmazonの比べると使い勝手が良くない
  • 技術情報が圧倒的に少ない! 管理コンソールのヘルプは画面操作のみですし、FAQも簡単な解説のみです。ちゃんとした解説はさくらのナレッジ ぐらです(?)
  • Linuxの知識がより必要。Amazonにくらべ管理コンソールで出来ない事もあり、Linuxのシステム管理の知識がAmazonより必要になります。

最後に、今のところ大きな問題もなく安くて快適なさくらのクラウドには満足しています。

S3へのアップロード速度を上げる方法

以前、S3への転送が遅い件について に書いたように、自宅 Mac mini からS3へのバックアップファイル転送ですが、単純に aws-sdk(Ruby版)を使うより2倍程度になったので書いておきます。


aws-sdk でのアップロードプログラム

次のようなコードで、ローカルにある PATH_TO_FILE ファイルを S3 の BUCKET_NAME にコピー出来ます(もちろん、access_key_idやsecret_access_keyにはあなたのキーを設定して下さい)。

#!/usr/bin/env ruby
require 'rubygems'
require 'aws-sdk'

AWS.config(
  :access_key_id     => 'XXXXXXXXXXXXXXX',
  :secret_access_key => 'YYYYYYYYYYYYYYY',
  :s3_endpoint       => 's3.amazonaws.com'
)
s3 = AWS::S3.new
bucket = s3.buckets['BUCKET_NAME']
path = 'PATH_TO_FILE'
obj = bucket.objects[File.basename(path)]
obj.write(:file => path)

writeメソッドのオプションを指定しよう

AWS::S3::S3Objectのwriteメソッドのドキュメントを見ると、いくつかオプションがあります。ここで転送速度と関係しそうなものには

  • : single_request 転送を一回で行うか、分割して行うか。デフォルトは分割(false)
  • :multipart_threshold 小さいファイルの場合は分割しない、しきい値。デフォルト 16Mbye
  • : multipart_min_part_size 分割する際の大きさ。デフォルト 5Mbye

今回はバックアップなので転送するファイルは 数100Mbye〜数Gbyte です。 デフォルトだと5Mbyeのファイルに分割して送っています。試しに multipart_min_part_size を大きくしたところ、転送時間が多少改善しました。

:single_request => true にして試したところ、デフォルトの半分程度の転送時間になりました!

single_requestで送るとということは一旦ファイルをメモリーに全部取り込んでからS3へ転送されます、したがってメモリーを大量に消費します。バックアップを行っている時に Mac mini は他には何も行っていないので今回は問題ないですが、使用環境によっては multipart_min_part_size を調整する方が良い場合もあると思います。

ssh ログインで ~/.ssh/id_ras が優先されるのを防ぐには

GitLab を開発用サーバーに入れて運用し始めたのですが ~/.ssh/config に接続用の秘密キーを指定しても ~/.ssh/id_ras を使って接続しょうとしエラーになり困っていました。

実用SSH 第2版―セキュアシェル徹底活用ガイド

GitHub風システム、GitLab は ssh 接続のgitコマンドからのアクセス時には、sshのキーを使いユーザーを管理しています。

もし1人のUnix(Mac)アカウントがGitLabに複数のユーザーを登録していて、ユーザーAの公開キーが ~/.ssh/id_ras に対応するもので、ユーザーBの 公開キーが ~/.ssh/id_ras_git の場合を考えてみましょう(各ユーザーのリポジトリーはユーザーだけがアクセス出来ます)。ユーザーBに対応するリポジトリーをアクセスする際に ~/.ssh/config に

Host user-b-repo
  Hostname gitlab.xxxx.com
  User git
  IdentityFile ~/.ssh/id_ras_git

と書き git clone git@user-b-repo:user-b/zzzz.git の様にアクセスすると認証エラーになってしまいます。
ssh に -v オプションを付けて user-b-repo をアクセスしてみると

% ssh -v user-b-repo
OpenSSH_5.9p1, OpenSSL 0.9.8y 5 Feb 2013
debug1: Reading configuration data /Users/aaaaaa/.ssh/config
debug1: Reading configuration data /etc/ssh_config
   ....
debug1: Connection established.
debug1: identity file /Users/aaaaaa/.ssh/id_ras_git type 1    ← ~/.ssh/id_ras_git が使われています
  ...
debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Offering RSA public key: /Users/aaaaaa/.ssh/id_rsa       ← ここに注目
   ...

なぜか ~/.ssh/id_ras を使って接続しています。この際に GitLab は ユーザーA がアクセスしてきたと解釈し、ユーザーBのリポジトリーにはアクセス権がないのでエラーを返します。

ここ数日悩んでいたのですが、今日判りました。 ~/.ssh/config に IdentitiesOnly yesを追加すれば、指定した秘密キーのみが使われるのでした!


ということで ~/.ssh/config は以下の様に書きましょう

Host user-b-repo
  Hostname gitlab.xxxx.com
  User git
  IdentityFile ~/.ssh/id_ras_git
  IdentitiesOnly yes

Chef を学んで使ってみた

空前の DevOps ブームに乗り遅れてはいけないとChefを学び、お客様の次期サーバーやRuby on Rails教育で使うサーバーを構築してみました。

http://docs.opscode.com/_static/opscode_chef_html_logo.png

感想

今回、お客様の次期サーバーを作るにあたりChefを使ってみたたところ、一度recipesを作ってしまえば サーバー環境が 10 〜30分くらいで自動的に出来てしまうのは画期的だと感じました!

私は教育・開発者なので、新しいサーバーの構築は年に数回、管理しているサーバーは数台程度です。従来はサーバーの構築・管理は手動で行い、作業内容をメモファイルに残していました。また、再度使う可能性があるサーバー環境はEC2のイメージとして保存して来たりしました。
しかし、作業のメモは必ずしも完璧ではないですし、メモを見落としてインストールに失敗する事もままありました。また保存してあるイメージですが、RubyRailsがバージョンアップし教育では使えなくなってしまう事もあります。

手動でサーバー環境を構築しようとすると半日仕事になってしまいますが、Chefがあれば 10 〜30分くらい。しかも勝手にやってくれます。今までは、環境構築がおっくうで一つのサーバーに色々なアプリをrbenvとかを駆使して動かしていましたが、コスト面で問題がなければクラウドVPSでサーバーを立ち上げ、すぐに環境が作れてしまいます。
また、recipe はコードなので少し考えて作っておけば再利用が可能で、新たな環境用に Chef も比較的短時間で作れます。

私のように、頻繁にサーバーを構築する事のない開発者も学んだ方が良い技術だと思います。

学び方

入門

入門は、やはり naoyaさんの 入門Chef Solo を読みましょう。原理的な事から実践的な事までがコンパクトにまとっまている良い書籍だと思います。私は kindle版が出た時に買ってしまったのでMacの横にiPadを置きKindleアプリで見ながら作業をしましたが、今なら 達人出版会からPDF(EPUB)版を買えば PC, Mac で見ながら作業出来るので、こちらがお勧めです。

http://tatsu-zine.com/images/books/78/cover_s.jpg

実際に作ってみる

実際にrecipe作る際には、

Recipe作りの基本

Resources

詳細は、入門書やリファレンスを読むとして、実際に Ruby on Railsアプリを動かすサーバーを構築するにはよく使う 以下のResources (Ruby DSL) を recipe に並べるだけです。

  • package: OSのパッケージ(apt, yum) 等のインストール
  • gem_package: RubyGemsのインストール
  • cookbook_file: 設定ファイル等の作成、用意した設定ファイルのコピー+α
  • template: 設定ファイル等の作成、設定ファイルをテンプレート化(Ruby のERB)出来るの汎用性がある
  • remote_file: ネット上からファイルをダウンロードする
  • file, directory: ファイル、ディレクトリーの作成
  • user, group: Unixのユーザー、グループの作成
  • service: デーモン(サーバープロセス)の起動、停止・・・
  • execute: OSコマンドを実行することで上記の Resources では出来ないような操作を行う

べき等性 (冪等性, idempotence)

Chefのrecipeでは既にrecipeと同じ状態になっている場合は、何も行わないという べき等性を保つようにrecipeを作る必要性がります。
これは、Chef がインストールツールではなく、サーバー等の環境の変更を管理する為のツールだからです。packageをはじめ殆どのResourceは、既にインストールされている場合は何もおきません。
ただし、execute などは自分でべき等になるように作る必要があります、その為の仕組みが幾つかあります。

  • creates属性: executeを実行した場合に出来るファイルを書いておくと、そのファイルがある場合は execute が実行されません
  • only_if, not_if属性: 書かれたRubyの式、またはshellコマンドの文字列を実行した結果で実行する・しないを制御出来ます

Notifies

設定ファイルが作成・変更された時だけサーバーをリスタートする事が良くあります、このような場合は以下のサンプルのように設定ファイルを作るresource内に notifies を書くことで、設定ファイルが作成・変更された時にサーバーのリスタートを実行できます。
ちなみに、service "mysql" do 〜 のactionがnothingになっているので、最初に service "mysql" do 〜 が実行される際には何も起きません。

service "mysql" do
  supports :status => true, :restart => true, :reload => true
  action :nothing
end

cookbook_file "/etc/mysql/conf.d/character_set_utf8.cnf" do
  source "character_set_utf8.cnf"
  owner 'root'
  group 'root'
  mode  0644
  notifies :restart, 'service[mysql]', :immediately
end

Ruby

recipe は Rubyのコードなので、当然 Rubyの式、変数、メソッド(関数)が使えます、何度も出てくるパス名や処理は変数やメソッドを使うとメンテナンス性の高い recipe になります。 ただし、resource内で呼び出すメソッドは libraries ディレクトリーに置きます。

repository > cookbook > recipe

複数のrecipe が集まり cookbook になり、複数の cookbook が集まりrepository になります。 したがって、一つ recipe ファイルはある程度の粒度(作業単位)で書き、cookbook, chef を分ける事が再利用性の高い recipe 作りに役立つと思います。

Chef Cookbooks

http://community.opscode.com に既に作られたrecipe (Cookbooks) がたくさんあります。naoyaさんの本にもChef初心者はCookbooks を使わずに自分で recipe を作った方が良いと書かれていますが、私もその通りだと思います。
一般的にCookbooksにあるものは汎用的で高機能ですが、自分専用の環境を作ったりする際には resouces や LWRP を使い recipe を作った方が勉強になるだけでなく、後々のバージョンアップ等でトラブルが起きにくいと思います。

私の勉強のために iptablesの設定に simple_iptables を使ってみましたが、自分でシンプルなrecipeを書いた場合に比べ良いのかは疑問です ;-)
ちなみに、Cookbooksは http://community.opscode.com/cookbooks ここで検索すると、評価(rating)やダウンロード数が判るので、どれを使うかの参考になると思います。

Vagrant

recipe の作成、確認はローカル(Mac)上のVM(仮想マシン、私の場合はVMWare fusionを持っていたのでこれを利用)で行います。recipeは べき等に出来ているので、OSだけがインストールされた新しいVMから再実行する必要な状況が良くおきます。その際には Vagrant を使うと、コマンド一つで新しいVMが直ぐに作れとても便利です。

Postfix 2.10 から中継制限の設定が変わった (smtpd_recipient_restrictions はダメ)!

Ubuntu 13.04 を使ってRailsを動かす環境を作っていたのですが、ひさびさにはまり何時間もロスしてしまったので書いておきます。

http://www.postfix.org/postfix-logo.jpg

PostfixSMTPで認証を行い任意のIPアドレスからメール中継を可能にする設定は、2.09までは main.cf に

smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination

と指定します。検索すると、どこにもこの記述ができてきます。しかし Postfix 2.10 では 554 Relay access denied エラーになってしまいます・・・・
以前に設定した Ubuntu 12.04 (Postfix 2.09)の環境では上手く動作します。うぅぅぅ・・

いろいろと検索し、やっと http://www.postfix.org/SMTPD_ACCESS_README.html に答えが書いてありました

NOTE: Postfix versions before 2.10 did not have smtpd_relay_restrictions. They combined the mail relay and spam blocking policies, under smtpd_recipient_restrictions. ....

ということで、 smtpd_relay_restrictions を設定しましょう!

smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination

Redmineをバージョン 2.0 に上げてみた

Redmine 2.0.0 がリリースされてから時間が経ってしまいましたが、今まで使っていた 1.0.2 を 最新の 2.0.3 にアップデートしてみました。


インストール

アップデートの手順は、Redmine 2.0.3 をインストールし、1.0.2 から DB と添付ファイルをコピーすることにしました。

Redmine 2.0.3 のインストール
% wget http://rubyforge.org/frs/download.php/76259/redmine-2.0.3.tar.gz
% tar xzf redmine-2.0.3.tar.gz
% ln -s redmine-2.0.3 redmin
% cd redmin
rvm を設定

Redmine 2.0 は Ruby 1.9.3 が使えます!! Rails も 3.2.6 です。

rvm 1.9.3@rails3.2
Gemインストール(bundle)

Gem のインストールは bundle で簡単。ただしデフォルトの Gemfile には MySQL,PostgreSQLなども書かれていますので不要なものは コメントアウトします。

LDAPOpenIDも使わないのですが、インストールしないとサーバーが起動しませんでした・・・・

今回の Gemfile

source 'http://rubygems.org'

gem 'rails', '3.2.6'
gem 'prototype-rails', '3.2.1'
gem "i18n", "~> 0.6.0"
gem "coderay", "~> 1.0.6"
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
gem "builder"

# Optional gem for LDAP authentication
group :ldap do
  gem "net-ldap", "~> 0.3.1"
end

# Optional gem for OpenID authentication
group :openid do
  gem "ruby-openid", "~> 2.1.4", :require => "openid"
  gem "rack-openid"
end

# Optional gem for exporting the gantt to a PNG file, not supported with jruby
#platforms :mri, :mingw do
#  group :rmagick do
#    # RMagick 2 supports ruby 1.9
#    # RMagick 1 would be fine for ruby 1.8 but Bundler does not support
#    # different requirements for the same gem on different platforms
#    gem "rmagick", ">= 2.0.0"
#  end
#end

# Database gems
platforms :mri, :mingw do
#  group :postgresql do
#    gem "pg", ">= 0.11.0"
#  end

  group :sqlite do
    gem "sqlite3"
  end
end

platforms :mri_18, :mingw_18 do
#  group :mysql do
#    gem "mysql"
#  end
end
platforms :mri_19, :mingw_19 do
#  group :mysql do
#    gem "mysql2", "~> 0.3.11"
#  end
end

#platforms :jruby do
#  gem "jruby-openssl"

#  group :mysql do
#    gem "activerecord-jdbcmysql-adapter"
#  end

#  group :postgresql do
#    gem "activerecord-jdbcpostgresql-adapter"
#  end

#  group :sqlite do
#    gem "activerecord-jdbcsqlite3-adapter"
#  end
#end

group :development do
  gem "rdoc", ">= 2.4.2"
  gem "yard"
end

group :test do
  gem "shoulda", "~> 2.11"
  gem "mocha"
end

local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
if File.exists?(local_gemfile)
  puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v`
  instance_eval File.read(local_gemfile)
end

# Load plugins' Gemfiles
Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file|
  puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
  instance_eval File.read(file)
end
% bundle install
DB、添付ファイルの移行

Redmine 1.0.2 から DB、添付ファイル をコピー

cp ../redmine-1.0.2/config/database.yml config/
cp ../redmine-1.0.2/db/redmine.db db
cp ../redmine-1.0.2/files/* files/

config/database.yml の形式が変わっていました

vi config/database.yml
production:
  adapter: sqlite3
  database: db/redmine.db
  timeout: 5000
migration, 設定
% RAILS_ENV=production rake db:migrate
% rake tmp:create 
% rake generate_secret_token
確認

Rails内蔵サーバーで動作を確認

rails s -e production
Unicorn の設定

設定ファイルをコピーし、rvm wrapper を作成

cp ../redmine-1.0.2/config/unicorn.conf config
rvm wrapper 1.9.3@rails3.2 redmine unicorn_rails

前回作った /etc/init.d/redmine_unicorn start redmine が起動できました。

Redmine 2.0 にすると何が嬉しいか?

@yusuke_kokubo さんの指摘 で、不勉強に気づき、不適切な部分を変更しました。 指摘ありがとうございます!

現在の Redmine 2.0は機能的には機能的には Redmine 1.4 と同じで、 Redmine 1.0.2 と比べると機能は増えています。 現在のところ Redmine 1.4 でも最新の機能は使えます 。 ちなみに Redmine 1.4 は Rails2.3 ベースです。

Redmine 2.0 は Rails3.2 ベースになったので、 メモリー使用量が増えます、またサーバー起動時間がかかります(私の環境ではメモリー使用量に関しては Redmine 1.0.2 が 173Mbyte なのに Redmine 2.0.3 は 202Mbyte に上がりました)。

現時点ででの Redmine 2.0 のメリットは、正式にサポートされている Rails3.2 ベースの安心感でしょうか? サーバーの環境等で2.0にアップデート出来ない場合も 1.4 にはしておいた方が良いですね。

テーマ を変えてみた

Redmine 2.0 に変えても変化が無くてつまらないので、テーマ(見かけ)を変えてみました。一番上にある画像は、一見 Redmine にみえないでしょ!

実はテーマが変えられる事を、今まで知らなかったのですが今回、いろいろと調べていて知りました。 Redmineのサイトに テーマの一覧 があります。たくさんあるのですが、どんな画面になるのかはリンク先に行かないと判らないので、先頭にあった A1 を入れてみました。インストール方法は ここ に書かれています。

% cd /tmp
% wget http://redminecrm.com/projects/a1theme/products/1279
% mv 1279 a1-1_0_3.tgz
% tar xvzf a1-1_0_3.tgz
% cd  XXXXXX/redmine-2.0.3
% mv /tmp/a1 public/themes/

Redmine の 管理→設定→表示(タブ)のテーマでA1を選択し保存ボタンを押す。
これでテーマが変更できます、気分も Redmine2.0 になりました。


今日の結論: 見かけ重要