こんにちは、tahara です。
突然ですが、弊社では現在エンジニアを募集しています。 仕事内容は情シス業務と自社サービの開発です。 開発は主に Ralis で PHP もときどきあります。 たまぁに Common Lisp もあます(増やしていきたいです)。 詳細はこちらをご覧ください。
それでは本題です。
いまさらではありますが、弊社で運営している http://iko-yo.net を Rails3 に移行しました。 今回はその移行作業について書いていきたいと思います。
rvm で ruby 1.9.3 をインストールし gemsent を作成する。
bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer) rvm install ruby-1.9.3 rvm use ruby-1.9.3 gem install bundler rvm gemset create iko-yo-rails3 rvm --rvmrc --create 1.9.3@iko-yo-rails3 cd .. cd -
きれいする
rm -r vendor/rails rm -r vendor/gems rm -r vendor/plugins/*
gem を入れる
vi Gemfile
assets は使わない。。。
source 'http://rubygems.org' gem 'rails', '3.1.0' # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' gem 'mysql2' gem 'jquery-rails' gem 'exception_notification' gem 'geokit-rails3' gem 'jpmobile' gem 'nokogiri' gem 'paperclip' gem 'restful-authentication' gem 'ssl_requirement' gem 'acts_as_taggable_on_steroids' gem 'acts_as_commentable' gem 'will_paginate' gem 'dynamic_form' gem 'mecab-ruby', :require => 'MeCab' gem 'twitter' gem 'oauth' gem 'garb' gem 'gdata_19', :require => 'gdata' gem 'holiday_jp' gem 'dalli' gem 'newrelic_rpm' #;; config.assets.enabled = false #;; # Gems used only for assets and not required #;; # in production environments by default. #;; group :assets do #;; gem 'sass-rails', " ~> 3.1.0" #;; gem 'coffee-rails', "~> 3.1.0" #;; gem 'uglifier' #;; end # Use unicorn as the web server gem 'unicorn' # Deploy with Capistrano gem 'capistrano' gem 'capistrano-ext' # To use debugger # gem 'ruby-debug19', :require => 'ruby-debug' group :test, :development do # Pretty printed test output gem 'turn', :require => false gem 'spork' gem 'rspec-rails', "~> 2.6" gem 'capybara' gem 'ZenTest' gem 'autotest-stumpwm' gem 'remarkable_activerecord', '>=4.0.0.alpha4' gem 'spork' end
gem 入れて rails3 にする。
bundle install bundle exec rails new
ソースの編集
config/rootes.rb はがんばる。 メールまわりも完全に書きなおし。
ソースをちまちま書きかえる(以下はイメージです。実際に動作するものではありません)。
config/boot.rb に次を追加 # /home/ancient/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/psych.rb:148:in `parse': couldn't parse YAML at line 18 column 13 (Psych::SyntaxError) require 'yaml' YAML::ENGINE.yamler= 'syck' 各ファイルの1行目に次を追加 # -*- coding: utf-8 -*- helper 系メソッドに .html_safe を付加 あは以下のようなイメージでどんどん書きかえていく。 s/adapter: mysql/adapter: mysql2/ database.yml.release s/named_scope/scope/ s/RAILS_ROOT/Rails.root/ s/returning/tap/ s/request_uri/fullpath/ s/<% form/<%= form/ s/<%= f.fields_for/<%= f.fields_for/ layout が使われなかったのは ApplicationController#initialize で super を呼んでなかったからだった。 s/(.*).merge_conditions (.*)/where(\1).where(\2)/ s/mobile_filter :hankaku => true/hankaku_filter :input => true/ s/include ActionController::UrlWriter/include Rails.application.routes.url_helpers/ s/.class_name/.name/ s/link_to_remote .*/link_to \1, :remote => true/ :complete, :before 等は js で bind('ajax:complete', ...), bind('ajax:before', ...) にする。 http://www.alfajango.com/blog/rails-3-remote-links-and-forms/ error_messages がなくなったので gem 'dynamic_form' s/choice/sample/ s/observe_field/ふつうの手書き jQuery/ s/action mailer/スーパークラスは Jpmobile::Mailer::Base で書きなおす/ /self.include_root_in_json = false/d config/initializers/wrap_parameters.rb s/model.save(false)/model.save(:validate => false)/ s/errors.or\((.*)\)/errors[\1]/
他にもいっぱいあったような気もしますが、だいたいこんな感じです。
次に実行環境まわり。
実行環境
Apache と Passenger だったのを nginx と unicorn にしました。 unicorn についているサンプルをもとに設定しました。
まずは ngix
nginx.conf
user deployer; worker_processes 2; pid /var/run/nginx.pid; events { worker_connections 1024; } http { client_max_body_size 50m; client_header_buffer_size 4k; include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 15; gzip on; gzip_disable "msie6"; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; #open_file_cache max=2000 inactive=300s; #open_file_cache_valid 360s; #open_file_cache_min_uses 2; #open_file_cache_errors off; include /var/www/outing/current/config/unicorn/production/nginx-site.conf; }
nginx-site.conf
upstream outing { # for UNIX domain socket setups: #server unix:/tmp/.outing.sock fail_timeout=0; # for TCP setups, point these to your backend servers server 127.0.0.1:8080 fail_timeout=0; } server { listen 80; root /var/www/outing/current/public; server_name iko-yo.net; location / { #auth_basic "Restricted"; #auth_basic_user_file /etc/nginx/outing-password; if ($request_uri ~* "\.(jpg|jpeg|gif|css|png|js|ico)\?[0-9]+$") { expires max; access_log off; break; } if (-f $request_filename) { expires 24h; access_log off; break; } try_files $uri @app; } location @app { # an HTTP header important enough to have its own Wikipedia entry: # http://en.wikipedia.org/wiki/X-Forwarded-For proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # enable this if and only if you use HTTPS, this helps Rack # set the proper protocol for doing redirects: # proxy_set_header X-Forwarded-Proto https; # pass the Host: header from the client right along so redirects # can be set properly within the Rack application proxy_set_header Host $http_host; # we don't want nginx trying to do something clever with # redirects, we set the Host: header above already. proxy_redirect off; # set "proxy_buffering off" *only* for Rainbows! when doing # Comet/long-poll/streaming. It's also safe to set if you're using # only serving fast clients with Unicorn + nginx, but not slow # clients. You normally want nginx to buffer responses to slow # clients, even with Rails 3.1 streaming because otherwise a slow # client can become a bottleneck of Unicorn. # # The Rack application may also set "X-Accel-Buffering (yes|no)" # in the response headers do disable/enable buffering on a # per-response basis. # proxy_buffering off; proxy_pass http://outing; } } # HTTPS server server { listen 443; root /var/www/outing/current/public; server_name iko-yo.net; ssl on; ssl_certificate /etc/ssl/iko-yo.net/iko-yo.net.crt.cer; ssl_certificate_key /etc/ssl/iko-yo.net/iko-yo.net.key; ssl_session_timeout 5m; ssl_protocols SSLv3 TLSv1; ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP; ssl_prefer_server_ciphers on; try_files $uri @app; location @app { # an HTTP header important enough to have its own Wikipedia entry: # http://en.wikipedia.org/wiki/X-Forwarded-For proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # enable this if and only if you use HTTPS, this helps Rack # set the proper protocol for doing redirects: proxy_set_header X-Forwarded-Proto https; # pass the Host: header from the client right along so redirects # can be set properly within the Rack application proxy_set_header Host $http_host; # we don't want nginx trying to do something clever with # redirects, we set the Host: header above already. proxy_redirect off; # set "proxy_buffering off" *only* for Rainbows! when doing # Comet/long-poll/streaming. It's also safe to set if you're using # only serving fast clients with Unicorn + nginx, but not slow # clients. You normally want nginx to buffer responses to slow # clients, even with Rails 3.1 streaming because otherwise a slow # client can become a bottleneck of Unicorn. # # The Rack application may also set "X-Accel-Buffering (yes|no)" # in the response headers do disable/enable buffering on a # per-response basis. # proxy_buffering off; proxy_pass http://outing; } } # for munin server { listen 127.0.0.1; server_name localhost; location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; } } # redirect sub domain server { listen 80; server_name *.iko-yo.net; rewrite ^(.*) http://iko-yo.net$1 permanent; }
次に unicorn
次の unicorn-init.sh を /etc/init.d/outing へ ln -s します。
#!/bin/sh ### BEGIN INIT INFO # Provides: outing # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Example initscript # Description: This file should be used to construct scripts to be # placed in /etc/init.d. ### END INIT INFO # sudo ln -s /var/www/outing/current/config/unicorn/production/unicorn-init.sh /etc/init.d/outing # sudo update-rc.d outing default # sudo update-rc.d outing enable set -e # Example init script, this can be used with nginx, too, # since nginx and unicorn accept the same signals # Feel free to change any of the following variables for your app: . "/usr/local/rvm/environments/ruby-1.9.3-p0@iko-yo-rails3" TIMEOUT=${TIMEOUT-60} APP_ROOT=/var/www/outing/current PID=$APP_ROOT/tmp/pids/unicorn.pid CMD="unicorn_rails -E production -D -c $APP_ROOT/config/unicorn/production/unicorn.conf.rb" INIT_CONF=$APP_ROOT/config/init.conf action="$1" set -u test -f "$INIT_CONF" && . $INIT_CONF old_pid="$PID.oldbin" cd $APP_ROOT || exit 1 sig () { test -s "$PID" && kill -$1 `cat $PID` } oldsig () { test -s $old_pid && kill -$1 `cat $old_pid` } case $action in start) sig 0 && echo >&2 "Already running" && exit 0 $CMD ;; stop) sig QUIT && exit 0 echo >&2 "Not running" ;; force-stop) sig TERM && exit 0 echo >&2 "Not running" ;; restart|reload) sig HUP && echo reloaded OK && exit 0 echo >&2 "Couldn't reload, starting '$CMD' instead" $CMD ;; upgrade) if sig USR2 && sleep 2 && sig 0 && oldsig QUIT then n=$TIMEOUT while test -s $old_pid && test $n -ge 0 do printf '.' && sleep 1 && n=$(( $n - 1 )) done echo if test $n -lt 0 && test -s $old_pid then echo >&2 "$old_pid still exists after $TIMEOUT seconds" exit 1 fi exit 0 fi echo >&2 "Couldn't upgrade, starting '$CMD' instead" $CMD ;; reopen-logs) sig USR1 ;; *) echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>" exit 1 ;; esac
unicorn.conf.rb
# Sample verbose configuration file for Unicorn (not Rack) # # This configuration file documents many features of Unicorn # that may not be needed for some applications. See # http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb # for a much simpler configuration file. # # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete # documentation. # Use at least one worker per core if you're on a dedicated server, # more will usually help for _short_ waits on databases/caches. worker_processes 4 # Since Unicorn is never exposed to outside clients, it does not need to # run on the standard HTTP port (80), there is no reason to start Unicorn # as root unless it's from system init scripts. # If running the master process as root and the workers as an unprivileged # user, do this to switch euid/egid in the workers (also chowns logs): user "deployer", "deployer" # Help ensure your application will always spawn in the symlinked # "current" directory that Capistrano sets up. working_directory "/var/www/outing/current" # available in 0.94.0+ # listen on both a Unix domain socket and a TCP port, # we use a shorter backlog for quicker failover when busy #listen "/tmp/.outing.sock", :backlog => 64 listen 8080, :tcp_nopush => true # nuke workers after 30 seconds instead of 60 seconds (the default) timeout 60 # feel free to point this anywhere accessible on the filesystem pid "/var/www/outing/current/tmp/pids/unicorn.pid" # By default, the Unicorn logger will write to stderr. # Additionally, ome applications/frameworks log to stderr or stdout, # so prevent them from going to /dev/null when daemonized here: stderr_path "/var/www/outing/current/log/unicorn.stderr.log" stdout_path "/var/www/outing/current/log/unicorn.stdout.log" # combine REE with "preload_app true" for memory savings # http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow preload_app true GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true before_fork do |server, worker| # the following is highly recomended for Rails + "preload_app true" # as there's no need for the master process to hold a connection defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! # The following is only recommended for memory/DB-constrained # installations. It is not needed if your system can house # twice as many worker_processes as you have configured. # # # This allows a new master process to incrementally # # phase out the old master process with SIGTTOU to avoid a # # thundering herd (especially in the "preload_app false" case) # # when doing a transparent upgrade. The last worker spawned # # will then kill off the old master process with a SIGQUIT. # old_pid = "#{server.config[:pid]}.oldbin" # if old_pid != server.pid # begin # sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU # Process.kill(sig, File.read(old_pid).to_i) # rescue Errno::ENOENT, Errno::ESRCH # end # end # # Throttle the master from forking too quickly by sleeping. Due # to the implementation of standard Unix signal handlers, this # helps (but does not completely) prevent identical, repeated signals # from being lost when the receiving process is busy. # sleep 1 end after_fork do |server, worker| # per-process listener ports for debugging/admin/migrations # addr = "127.0.0.1:#{9293 + worker.nr}" # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true) # the following is *required* for Rails + "preload_app true", defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection # if preload_app is true, then you may also want to check and # restart any other shared sockets/descriptors such as Memcached, # and Redis. TokyoCabinet file handles are safe to reuse # between any number of forked children (assuming your kernel # correctly implements pread()/pwrite() system calls) end
問題は unicorn の upgrade (sudo service outing upgrade) で失敗する場合があること。 なぜでしょう。。。
あと unicorn は production 環境ではメモリ使用量が少ないのですが、 development 環境ではどんどんメモリをくっていきます。 これもなぜでしょう。。。