402v /posts/capistrano-unicornda-jian-railszi-dong-hua-fa-bu-xi-tong

Capistrano+Unicorn搭建Rails自动化部署

断断续续研究Ruby on Rails也有两年时间,因为纯粹基于个人爱好所以一直没有认真总结一下,借着这次搭建Ghost博客的机会重新搞了一次Rails的自动化部署。

有些问题(比如database.yml文件处理、unicorn重启、首次发布是否必须先运行cap deploy:setup)纠结了挺久,最终本文采用的是全程自动化并且保证操作一致性的方案。

#运行环境

介绍中默认shell用的是zsh,bash或者sh的话只需要在操作过程中将.zshrc配置文件换成bash的.bashrc(ubuntu下默认)或者.bashprofile即可。

同时推荐一下zsh,主要是因为强大的Oh My Zsh

我的工作环境本地电脑是MBP,所以用mac osx编写代码托管到github,服务器端是Ubuntu 14.04,每次客户端项目本地开发调试完成之后,自动化部署提交到Ubuntu服务器上。因此项目的环境和配置都分为两个方面

###Server端

  1. Ubuntu14.04;
  2. DigitalOcean在SGP的Droplet,直接用的Ghost Application镜像;
  3. 自动化部署到deploy账户,除了nginx重启其他都不需要root权限(sudo)
  4. rbenv 0.4.0-183-gc18a3f9
  5. Ruby 2.0.0p645
  6. Rails 4.0.0
  7. Nginx: nginx version: nginx/1.4.6 (Ubuntu)
  8. unicorn 4.9.0

###Client端

  1. Mac OSX 10.11.1
  2. rbenv 0.4.0-183-gc18a3f9
  3. Ruby 2.0.0p645
  4. Rails 4.0.0
  5. Capistrano 3.1.0

#Ubuntu上账户环境的前期准备

  1. 创建deploy账户
  2. 给deploy账户增加权限

#Ruby开发环境搭建

Mac OSX 和 Ubuntu都适用下面的配置方法,稍有不同的地方都已经注明。注意一下shell基于的zsh,其他shell的rc文件名会不同。

Ubuntu配置在deploy账户下。

###安装Ruby版本控制工具(rbenv)

ruby版本管理工具用rvm和rbenv都可以,这里已rbenv为例

  1. 克隆rbenv@github到本地

     git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
    
  2. 将rbenv配置到shell环境

     echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.zshrc
     echo 'eval "$(rbenv init -)"' >> ~/.zshrc
    
  3. 使shell配置生效

     source ~/.zshrc
    
  4. 这时候在shell中输入type rbenv输出rbenv is a shell function,表示安装成功。

建议直接去rbenv@github上仔细的读一遍Readme,反正这些内容早晚都是要了解的。按照教程快速配置完环境之后建议其他的部分也都去读一下官方文档。

其实在Mac OSX上更方便的方式是用homebrew安装rbenv,但上面介绍的办法通用而且不麻烦,在Ubuntu和mac上都适用。

###安装Ruby

  1. rbenv默认不提供install命令,需要安装ruby-build插件:

     git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
    
  2. Ubuntu下安装ruby需要先安装依赖,运行:

     apt-get install autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm3 libgdbm-dev
    

其他环境的依赖可以在这里找到。

  1. 正式进入Ruby的安装,运行rbenv install 2.0.0-p645(2.0.0-p645我通常用这个版本的ruby,初学搭建时建议严格按照教程中的ruby和gems版本,否则可能出现版本冲突等问题)

  2. waiting for a really long time,然后可以看到ruby安装完成

  3. 使用这个ruby版本

     rbenv global 2.0.0-p645
    

###安装Rails Rails的安装很简单,gem install rails即可,也可以指定gem版本为4.0.0。

特别要注意的是,在Mac安装rails需要先安装Command Line Tools

安装方法可选择下面其中一种:

  1. 在Terminal运行xcode-select --install
  2. 也可以打开Xcode.app,选择"Xcode" - "Open Developer Tool" - "More Developer Tools"打开网站直接安装;
  3. 直接跳转到官网安装。

如果本机之前安装过rails的其他版本,可以考虑卸载掉。1

###安装bundler Bundler为ruby app提供了简便的gem list以及gem版本的管理工具,是rails开发必须安装的工具。安装同样很简单:gem install bundler,使用时cd进入app目录。

  1. 初始化bundle,会帮你新建gemfile(下面rails new app的时候会直接帮你做了,所以不需要自己再运行)

     bundle init
    
  2. 添加一个gem到Gemfile,也可以vim打开手动编辑

     echo 'gem "rspec"' >> Gemfile
    
  3. 安装Gemfile中指定的gem list

     bundle install
    
  4. 执行某个bundle命令:

     bundle exec rspec
    

Gemfile的编写方式详见这里

#Mac OSX下创建Rails App并提交到Git

###Rails项目搭建

  1. rails new app_path,如果在已创建的目录中初始化rails环境则运行rails new .

  2. rails很方便,但同时也容易让我们忽略了整个项目结构,可以在terminal中运行如下命令了解rails帮你创建的目录环境:

     // 输出目录树
     ls -R | grep ":$" | sed -e 's/:$//' -e 's/[^-][^\/]*\//--/g' -e 's/^/ /' -e 's/-/|/'
    

目录树很长就不贴图了,大家的目录结构都是一样的,简单介绍几个重点目录: 3. app路径是我们编写程序,实现逻辑的地方: * rails默认采用MVC结构,包含controllermodelview三个目录,页面html相关的内容在view路径下的*.html.erb文件内; * assets目录下存储所有的页面资源:js、css(scss and less)、images、fonts等等; 4. config路径下是项目相关的配置文件,如后续要配置unicorn的unicorn.rb文件,capistrano的deploy.rb文件和设置页面访问路由的routes.rb等等; 5. lib路径主要用于各个gem放置一些他们需要的文件,如assets文件和可执行的rake文件(tasks); 6. public目录即为访问网站的root路径,nginx配置时的root会指定到它。

###第一次运行Rails项目 在rails app目录下运行rails s或者rails server,在本地浏览器访问:http://localhost:3000(3000是rails server的默认端口)即可看到rails的初始项目,非常简单。

###Rails项目提交到Github

  1. 在rails项目目录下初始化git环境并commit;
  2. 提交代码到github的流程大家都会就不细说了,详细步骤参见这里

后续开发就因网站内容而异了,简单的可以去网上down一个Bootstrap的模板拷贝到自己的项目中,别忘了添加bootstrap-sass之类的gem。

#Nginx+Unicorn的服务器配置

服务端访问rails项目的实现一般是基于nginx+unicorn,nginx监听80端口,收到对应域的请求之后通过socket或者端口转发给unicorn处理。How unicorn talks to nginx这篇文章把这个转发过程介绍的非常好,建议后续找时间读一下;

还有一个注意事项是:log、pid等文件的访问权限问题,配置出现问题首先要想到的就是对某个文件是否没有访问权限,后面配置cap也一样。

这部分的内容需要在Mac OSX本地和Ubuntu服务器上都有操作,注意区分。如Unicorn gem的配置是本地开发之后提交到github上去的,Nginx配置肯定是在服务器上,这里特别强调一下开发的方式:推荐在本地开发,通过git同步代码,服务器上只做git pull。其实在后面的介绍中有了capistrano这个神器之后git pull都省了,完全不需要登录服务器就可以完成网站的发布

###安装Unicorn 在Gemfile中添加gem 'unicorn',保存退出运行bundle或者bundle install更新gem。

###新建Unicorn配置文件 在config路径下新建unicorn.rb文件,模板内容如下,针对自己的环境做些配置调整,一定要注意访问路径的权限问题,仔细阅读配置文件,如果只是复制粘贴,有任何一条配置不理解的话,以后遇到错误都是要重新阅读查阅的,还不如一次性的都了解清楚。为了叙述上的连贯性,直接在注释部分解释配置目的和注意事项:

# 没有指定环境的话默认在开发环境下运行
env = ENV["RAILS_ENV"] || "development"

# 需要开启的unicorn worker进程数,详见:http://unicorn.bogomips.org/Unicorn/Configurator.html.
worker_processes 4

# app相关设置
# app工作路径,默认向上找两级路径,即projects/config/unicorn.rb
app_dir = File.expand_path("../..", __FILE__)

# 应用名称(项目根目录名)
app_name = "projects"

# 保存unicorn进程id的文件路径,重要,确保当前用户有访问此文件的权限
pid_path = "#{app_dir}/tmp/pids/unicorn.#{app_name}.pid"

# nginx与unicorn进行socket通信的文件路径,重要,确保当前用户有访问此文件的权限
sock_path = "#{app_dir}/tmp/sockets/unicorn.#{app_name}.sock"

# 监听socket文件,也可以在此处指定一个端口号
# 减小backlog的长度以得到更快的速度(这里理解的不深,先把原文留下了:we use a shorter backlog for quicker failover when busy)
listen sock_path, :backlog => 64

# 销毁worker的时间改为30s,默认为60
timeout 30

# 指定pid保存路径
pid pid_path

# Production环境下的特殊配置,即网站发布之后的服务器端配置
if env == "production"
  # Help ensure your application will always spawn in the symlinked
  # "current" directory that Capistrano sets up.

  # 指定production下的工作路径为current,项目自动发布(deploy)到服务器上的时候,capistrano会将当前工作的站点放在current路径下,在后续deploy的时候会详细讲到。
  working_directory "#{app_dir}/current"

  # production环境下运行的用户和组,确保这个用户有访问app_dir、socket、pid路径的权限
  user 'deploy', 'deploy'

  # share目录,每次发布时不需要改变或者无法改变的文件会放在这个目录下,如log等,deploy中会讲到
  shared_path = "#{app_dir}/current/shared"

  # unicorn的错误和输出日志
  stderr_path "#{shared_path}/log/unicorn.stderr.log"
  stdout_path "#{shared_path}/log/unicorn.stdout.log"
end

# 在创建fork process之前预加载app以得到更快的启动速度,但这样的话必须保证其他的连接(如数据库连接)都被正确的关闭和重启,于是就用到了before_fork和after_fork
preload_app true

# unicorn通过fork processes来实现多进程,因此需要在before_fork和after_fork配置数据库连接的关闭和重启。
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
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end

  # 杀掉位于 .oldbin 这个PID上的master进程.
  # deploy的时候很有用,可以实现rails服务的zero downtime重启
  old_pid = "#{pid_path}.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  # the following is *required* for Rails + "preload_app true",
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end

  # 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 启动Unicorn的方式有几种:

  1. 在服务端命令行手动启动和停止,伪代码如下:

     # 启动unicorn
     cd #{current_path} && #{fetch(:unicorn_binary)} -c #{fetch(:unicorn_config)} -E #{fetch(:rails_env)} -D
    
     # 关闭unicorn
     kill `cat #{fetch(:unicorn_pid)}`
    
  2. 生成一个unicorn的服务放在/etc/init.d下,通过service unicorn_appname start等命令控制unicorn进程。

    • 运行sudo vi /etc/init.d/unicorn_appname创建shell文件,

    • 在文件内指定服务的参数,shell文件的具体内容参见这里

    • 权限配置

        sudo chmod 755 /etc/init.d/unicorn_appname
        sudo update-rc.d unicorn_appname defaults
      
    • 之后就可以运行start、stop、restart等命令运行unicorn了。

        sudo service unicorn_appname start
      
  3. 最后一种方式也是最实用的方式是启动unicorn的操作放到capistrano或者mina等自动化发布工具上去做,也是本文所采用的方式,后面在讲capistrano的deploy配置时会详细说。

###Nginx配置 nginx的目录配置(如:site-avaliables、site-enables)可以参见:Ghost博客搭建系列之三 - 多重域名访问b,这里默认本地nginx畅通,那么我们需要做的是添加一个nginx配置文件,内容如下(具体说明在注释中):

upstream app {
    # unicorn.rb中设置的unicorn的socket文件路径 unix:/home/deploy/apps/projects/current/tmp/sockets/unicorn.projects.sock fail_timeout=0;
}

server {
    listen 80;
    server_name {my-domain}.com www.{my-domain}.com;

    # rails的public文件路径
    root /home/deploy/apps/{my-app}/current/public;

    try_files $uri/index.html $uri @app;

    # location到app这个upstream
    location @app {
        proxy_pass http://app;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }

    # 资源路径
    location ~ ^/assets/ {
      expires 1y;
      add_header Cache-Control public;

      add_header ETag "";
      break;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
}

配置成功后重启nginx:service nginx restart,如果发生错误运行nginx -t快速测试查看原因。

这个时候如果nginx的root对应路径有项目文件的话,运行unicorn已经可以访问到网站。

可以先git clone一个项目到/home/deploy/apps/{my-app}/current/public,手动启动unicorn测试一下效果。或者直接阅读下面的Cap自动化部署。

#基于Capistrano自动发布(Deploy)

我们之所以使用rails的就是为了简单方便,于是就少不了自动化发布我们的项目,配置之后只要每次运行cap deploy就可以把本地开发的项目发布到服务器上,没有任何多余的操作而且是zero downtime的!

Rails自动化发布通常有可以有两个不同的gem来实现,本文所讲的Capistrano是其中之一,另外一个mina算是较轻量级的deploy库,使用也很广泛,配置的方式与cap3比起来也是大同小异。

###Capistrano初始化 首先在gem中添加Capistrano相关的gems,因为除了cap的gem外,cap的官网上还提供了一些插件用于控制bundler、rails、rbenv等等。

group :development do

	# Use Capistrano for deployment
	gem 'capistrano', '~> 3.1.0'

	# rails specific capistrano funcitons
	gem 'capistrano-rails', '~> 1.0.0'

	# integrate bundler with capistrano
	gem 'capistrano-bundler'

	# if you are using RBENV
	gem 'capistrano-rbenv', "~> 2.0"
end

运行bundle install安装gems;

然后运行bundle exec cap install初始化capistrano的环境,会创建这样几个文件:

我们一个一个来解释:

  • deploy.rb是最主要的一个,运行cap deploy时的主要配置都在这里;

  • Capistrano有stage的概念,类似于rails的environments,默认有两个starge:staging和production。因此新版的cap运行deploy时需要指定stage:cap production deploy,后面会讲到如何指定一个默认的stage。deploy/staging.rbdeploy/production.rb两个文件就是在运行不同stage时特定配置的指定文件,有区别的配置放在这里,各个stages通用的配置尽量放在deploy.rb

  • lib/capistrano/tasks:之前提到lib文件夹时已经说过这个文件夹主要存放默写gem用到的资源和rake文件。Capistrano3整体迁移到rake上来,所以cap3的task编写基本和rake文件一样。

  • 除此之外还要介绍一个与Gemfile统计目录下的Capfile,这个文件主要是用来为cap模块载入需要的.cap文件和.rb文件。根据需要require对应的插件,Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }这一句将tasks路径下的task文件都加载进来。当然还可以增加Dir.glob('lib/capistrano/tasks/*.rb').each { |r| import r }这样一行load所有的.rb文件。根据我们接下来需要的配置和已引入的插件gems,我们会开启下列几个require:

      require 'capistrano/rails'
      require 'capistrano/rbenv'
      require 'capistrano/bundler'
      require 'capistrano/rails/assets'
    

###Deploy文件配置 deploy文件分为两个主要部分:

  1. 参数配置部分(多数以set方法开头,设置各种各样的环境变量);
  2. task部分,默认只有deploy这一个namespace。

deploy文件(或者说Capistrano部署)的核心是deploy这个namespace下的task,Cap3在deploy的时候会按照一定的次序运行这些task来完成工作。

  1. 如果想要git update之后执行bundle install,需要rbenv和bundler两个plugin;
  2. /usr/bin/env bundle not found 问题的解决办法,原因是cap默认使用non-login, non-interactive shell这幅图解释了shell启动时加载环境变量的过程。
  3. app_name & git repo etc.
  4. default stage
  5. deploy_to: /var/www/app_name 需要root权限,可以发布到/home/#{fetch(:deploy_user)/apps/app_name
  6. linked_files & linked_dirs: 影响其他配置,如nginx、unicorn
  7. database.yml,
    • 如果现在deploy会报错:ERROR linked file /home/deploy/apps/projects/shared/config/database.yml does not exist on server.com,因为database.yml文件内配置的是数据库用户名密码等私密信息,因此不能通过放到git库中同步到server。

    • 原因如Capistrano官方所说敏感文件一定要在本地ignore掉,ignore这步rails new的时候已经做了;

    • 最正确的处理办法是通过scp把database.yml文件拷贝到server上:

        scp config/database.yml deploy@oneboxapp.com:/home/deploy/apps/projects/shared/config/database.yml
      
    • 这里还有一些关于database.yml等敏感文件处理的办法。

    • 自动化task的做法是新建setup task然后在udpated工作流之后运行它:

          task :setup do
            sh "scp config/database.yml deploy@oneboxapp.com:/home/deploy/apps/projects/shared/config/database.yml"
          end
      
  8. deploy成功,根据我们之前的配置,80端口收到的网络请求是通过nginx转发给unicorn的,此时需要重启之前配置好的unicorn_projects服务:
  9. assets:precompile,找不到database.yml的解决办法:

###需要自动化的部分

  1. git clone之后bundle install
  2. database.yml拷贝
  3. unicorn init程序
  4. nginx网站配置
  5. unicorn重启
  6. nginx重启

之前每次配置deploy.rb文件都是从网上down一份,没有仔细研究,所以这次手打了一份,从最基础的参数配置和task编写开始,因此中间也遇到一些比较初级的问题,希望对遇到这些问题的人有所帮助。

  1. deploy unicorn:

    • 遇到一个问题:无论如何deploy,production页面的assets MD5也不会改变。问题的原因是deploy的过程中没有重启unicorn,而unicorn在执行过程中会把整个rails缓存下来,因此无论如何生成都会展示老页面。
  2. asset-pipeline配置:(fingerprint,production,pre-compile)

###deploy tasks

  1. unicorn的重新启动,配置文件如下:

     #!/bin/sh
    
     ### BEGIN INIT INFO
     # Provides:          unicorn
     # Required-Start:    $all
     # Required-Stop:     $all
     # Default-Start:     2 3 4 5
     # Default-Stop:      0 1 6
     # Short-Description: starts the unicorn app server
     # Description:       starts unicorn using start-stop-daemon
     ### END INIT INFO
    
     set -e
    
     USAGE="Usage: $0 <start|stop|restart|upgrade|rotate|force-stop>"
    
     # app settings
     USER="deploy"
     APP_NAME="projects"
     APP_ROOT="/home/$USER/apps/$APP_NAME/current"
     ENV="production"
    
     # environment settings
     PATH="/home/$USER/.rbenv/shims:/home/$USER/.rbenv/bin:$PATH"
     # -su: bundle: command not found
     CMD="cd $APP_ROOT && /home/$USER/.rbenv/shims/bundle exec unicorn -c config/unicorn.rb -E $ENV -D"
     PID="$APP_ROOT/tmp/pids/unicorn.$APP_NAME.pid"
     OLD_PID="$PID.oldbin"
    
     # make sure the app exists
     cd $APP_ROOT || exit 1
    
     sig () {
       test -s "$PID" && kill -$1 `cat $PID`
     }
    
     oldsig () {
       test -s $OLD_PID && kill -$1 `cat $OLD_PID`
     }
    
     case $1 in
       start)
         sig 0 && echo >&2 "Already running" && exit 0
         echo "Starting $APP_NAME"
         su - $USER -c "$CMD"
         ;;
       stop)
         echo "Stopping $APP_NAME"
         sig QUIT && exit 0
         echo >&2 "Not running"
         ;;
       force-stop)
         echo "Force stopping $APP_NAME"
         sig TERM && exit 0
         echo >&2 "Not running"
         ;;
       restart|reload|upgrade)
         sig USR2 && echo "reloaded $APP_NAME" && exit 0
         echo >&2 "Couldn't reload, starting '$CMD' instead"
         $CMD
         ;;
       rotate)
         sig USR1 && echo rotated logs OK && exit 0
         echo >&2 "Couldn't rotate logs" && exit 1
         ;;
       *)
         echo >&2 $USAGE
         exit 1
         ;;
     esac
    

可以用两种方式: * 保存一个shell文件放在/etc/init.d下,每次通过service重启这个服务。

        sudo chmod 755 /etc/init.d/unicorn_appname
        sudo update-rc.d unicorn_appname defaults

 通过下面这条命令重启

            sudo service unicorn_appname start
* 另一种方式是将shell文件放在github库里面,通过capistrano的deploy命令触发它。

		# in deploy.rb
		set(:executable_config_files, %w(
		  unicorn_init.sh
		))

		# in some setup cap
		executable_files = fetch(:executable_config_files)
		executable_files.each do |file|
		  execute :chmod, "+x #{shared_path}/config/#{file}"
		end

 `#{shared_path}/config/#{file}`即为unicorn启动shell文件的路径。

###Stage文件配置及默认Stage Staging的概念可以看官方文档,如果不配置默认stage,每次运行是必须指定,比如: cap production deploy这样,在Capfile中添加:

Rake::Task[:production].invoke

即可指定默认stage为production,可参见so上的回答

#Q&A(待整理)

最后说一下我再Rails项目配置过程中遇到的一些问题及可能的解决方案,希望对大家有帮助。后续会详细的整理。

  1. 首次运行cap deploy时显示:repo' already exists and is not an empty directory错误;
  2. 第二次运行cap deploy时显示:An error occurred while installing activesupport (4.0.0), and Bundler cannot
  3. set :pty, true后输入密码没反应
  4. cap之后没有css、js等:因为没有uncommentrequire 'capistrano/rails/assets'这句话,所以deploy不会做 precompile
  • so
  • nginx 增加静态文件索引:
  1. Errno::ENOMEM: Cannot allocate memory - node
  1. 运行了precompile之后仍然不能显示css和js,查看页面是因为页面缓存了原先的precomfile文件,而不是服务器上的正确文件:

  1. cap deploy的时候报错fatal: destination path '/home/deploy/apps/aboutme/repo' already exists and is not an empty directory.,原因上是git clone --mirror命令执行了两次,而最根本的原因是在配置deploy files的时候,同一个rolerole :app, %w{deploy@oneboxapp.com}配置了两次,如图:

so上的解释

terminal里面报错的截图:

  1. 报错failed to create symbolic link '/home/deploy/apps/aboutme/releases/20151101035433/config/database.yml' DEBUG [205b27ed] : File exists,原因是database.yml文件应该是个link而不是直接把rails new生成的database.yml文件上传,在.gitignore中忽略database.yml之后,执行mv database.yml database.example.yml然后commit,删掉git库中的此文件,再次cap deploy就可以了。

#参考连接

  1. Capistrano 3 Tutorial - 通过配置文件控制deploy过程
  2. guide
  3. rails command line
  4. Nginx:worker_connections are not enough while connecting to upstream

#Rails开发常用命令

  1. rake routes

  2. rake db:create

  3. rake db:migrate

  4. 创建controller及controller的index行为:rails generate(g) controller welcome index

  5. 创建Model:rails generate model Post title:string text:text

  6. MVC销毁:

     rails destroy controller lalala
     rails destroy model yadayada
     rails destroy scaffold hohoho
    

#脚注

Footnotes

  1. Rails的卸载方法

    gem uninstall rails -v 4.2.4
    rails -v  // Rails 4.2.4
    gem uninstall railties -v 4.2.4
    rails -v // Rails 4.0.0
    

评论 · 0

还没有评论。