写出来的都是shit,快关闭!

Rails生产环境部署 Mina + Unicorn + Nginx

前前后后部署过若干个rails生产环境,踩过一丢丢坑,有丢丢心得,有丢丢经验,分享一下,给新手一个参考。

本教程将会涉及以下工具:

  1. Ubuntu14.04LTS (有些童鞋可能会问CentOS行不行啊?当然行啦,服务器选自己熟手的就好,如果Linux初学者,我是大大的建议ubuntu)
  2. RVM
  3. Ruby 2.3.1
  4. Rails 4.2.6
  5. Nginx
  6. unicorn 5.1.0 (有些童鞋可能又会问了,puma行不行啊?当然行啦,其实这俩设置类似,性能接近。也就只是运行思路不一样而已。)
  7. mina (最后还是会有童鞋可能会问,Capistrano行不行啊?这次就真的抱歉啊!不行!!!cap功能很强大,但是单纯部署来说,又长又臭!配置复杂,可扩展性差,除非你闲着蛋疼,否则不会去使用。)

创建部署专用账号

出于安全等方面考虑,部署应当使用普通账号,而非root账号。

所以,我们先创建一个部署专用的账号,比如名字叫做 deploy

以下操作假设你已经登陆到部署的服务器:

1
# useradd -m -s /bin/bash deploy

将用户加入 sudo 群组,以便使用 sudo 命令:

1
# adduser deploy sudo

为 deploy 用户设置密码:

1
# passwd deploy

设置免密码登陆

本地计算机用ssh-keygen创建公钥

1
$ ssh-keygen -t rsa

然后会在本地计算机的~/.ssh/目录下创建了两个文件:id_rsa(私钥),id_rsa.pub(公钥)

使用scp把公钥文件拷贝到服务器

1
$ scp id_rsa.pub deploy@192.168.111.111:~/.ssh/authorized_keys

为了防止密码被暴力破解,我们可以配置下服务器 /etc/ssh/sshd_config:

1
2
3
4
5
6
7
8
AuthorizedKeysFile      .ssh/authorized_keys [1]
PermitEmptyPasswords    no [2]
PermitRootLogin         no [3]
PasswordAuthentication  no [4]
[1] 这是一种适合多用户的配置,比如,多个开发者登陆服务器,sshd 会校验每个登陆账户下的 .ssh/authorized_keys。
[2] 禁止空密码访问,这是默认的
[3] 禁止 root 访问,当我们开通服务器时,这个选项默认是 yes,这样我们可以使用 root 登陆。当设置完 ssh 后,建议第一时间关闭它。
[4] 不使用密码校验,这是 ssh 会自动读取、开发机器上的私钥校验,如果成功匹配,则自动登陆服务器。

设置完后,重启sshd服务:

1
$ /etc/init.d/sshd restart

设置服务器免密连接代码仓库

这里的操作跟上面完全一样。 设置完成后,可以使用下面命令测试是否成功:

1
2
#如果代码仓库在github的话
$ ssh -T git@github.com

安装 RVM 和 Ruby

更新 apt,并安装 curl:

1
2
$ sudo apt-get update
$ sudo apt-get install curl

然后安装 RVM:

1
2
3
4
5
6
7
$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
如果提示 gpg: keyserver timed out,那么恭喜你,这个keys.gnupg.net服务器就是这么烂,每天能挂几十次。
解决方法:
$ gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3

提示导入成功后,就可以安装rvm了
$ \curl -sSL https://get.rvm.io | bash -s stable

然后重新登陆,让rvm的环境变量生效,然后安装ruby和rails

1
2
3
$ rvm use --install --default 2.3.1
$ rvm use 2.3.1@xxx_gemset --create --default
$ gem install rails

安装各种什么的

MongoDB:

1
2
3
4
5
$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
$ echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list
$ sudo apt-get update
$ sudo apt-get install mongodb-org
$ sudo service mongod start

MySql:

1
$ sudo apt-get install mysql-client mysql-server

redis:

1
$ sudo apt-get install redis-server

nodejs:

1
sudo apt-get install nodejs

mina

Mina是一个强大的部署工具,mina setup和mina deploy只是mina工具中的两个tasks。Mina还提供了各种tasks。用mina tasks可以列出所有的task。我们也可以编写我们自己的task。

添加下面这几行到Gemfile文件

1
2
3
4
5
gem 'mina', require: false  #超级666的部署工具
gem 'mina-multistage', require: false #mina插件,用于多环境发布,比如英文、中文或者正式发布环境、测试环境

gem 'unicorn'
gem 'mina-unicorn', require: false

注:因为多环境发布比较常见,所以本教程针对多环境发布:production版本以及staging版本。

然后执行

1
$ bundle

编辑config/deploy.rb文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
set :stages, %w(staging production)
set :default_stage, 'staging'

require 'mina/multistage'
require 'mina/bundler'
require 'mina/rails'
require 'mina/git'
require 'mina/rvm'    # for rvm support. (http://rvm.io)
require 'mina/unicorn'
require 'mina_sidekiq/tasks'

# Manually create these paths in shared/ (eg: shared/config/database.yml) in your server.
# They will be linked in the 'deploy:link_shared_paths' step.
set :shared_paths, ['config/database.yml', 'config/secrets.yml', 'log', 'tmp', 'public/uploads']

task :environment do
  queue! %[source /home/deploy/.rvm/scripts/rvm]
  queue! %[rvm use 2.3.1@xxx_gemset]
end

task :setup => :environment do
  ['config', 'log', 'tmp', 'public/uploads'].each do |dir|
    queue! %[mkdir -p "#{deploy_to}/shared/#{dir}"]
    queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/#{dir}"]
  end

  ['config/database.yml', 'config/secrets.yml'].each do |file|
    queue! %[touch "#{deploy_to}/shared/#{file}"]
    queue  %[echo "-----> Be sure to edit 'shared/#{file}'."]
  end

  queue! %[mkdir -p "#{deploy_to}/shared/pids/"]
  queue! %[mkdir -p "#{deploy_to}/shared/tmp/sockets"]
  queue! %[mkdir -p "#{deploy_to}/shared/tmp/pids"]
  queue! %[mkdir -p "#{deploy_to}/shared/log/"]
end

desc "Deploys the current version to the server."
task :deploy => :environment do
  deploy do
    # Put things that will set up an empty directory into a fully set-up
    # instance of your project.
    invoke :'git:clone'
    invoke :'deploy:link_shared_paths'
    invoke :'bundle:install'
    invoke :'rails:db_migrate'
    invoke :'rails:assets_precompile'

    to :launch do
      invoke :'unicorn:restart'
    end
  end
end

执行bundle exec mina multistage:init生成config/deploy/staging.rb还有config/deploy/production.rb,刚好是我们需要的环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# config/deploy/production.rb
set :domain, '192.168.111.111'
set :deploy_to, '/home/deploy/rails_app'
set :repository, 'git@bitbucket.org:hellorails/railstestapp.git'
set :branch, 'master'
set :user, 'deploy'
set :unicorn_config, -> { "#{deploy_to}/#{current_path}/config/unicorn/production.rb" }


# config/deploy/staging.rb
set :domain, '192.168.111.111'
set :deploy_to, '/home/deploy/rails_app_test'
set :repository, 'git@bitbucket.org:hellorails/railstestapp.git'
set :branch, 'develop'
set :user, 'deploy'
set :unicorn_config, -> { "#{deploy_to}/#{current_path}/config/unicorn/staging.rb" }

一些其他命令:

部署出错的时候,看错误详情,可以再命令后面加 -v,例如:

1
2
$ mina setup -v
$ mina deploy -v

在deploy都会锁住文件,如果不小心断线或者其他状况断掉,重新mina deploy就会出错,可以试试:

1
$ mina deploy:force_unlock

unicorn

可以参考官方例子

https://github.com/defunkt/unicorn/tree/master/examples

新增2个文件 config/unicorn/production.rb 以及 config/unicorn/staging.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#config/unicorn/production.rb
app_path = File.expand_path( File.join(File.dirname(__FILE__), '..', '..'))

worker_processes   1
timeout            180
listen             '/tmp/unicorn_railsapp_production.sock'
pid                "#{app_path}/tmp/pids/unicorn.pid"
user               'deploy', 'deploy'
stderr_path        "log/unicorn.log"
stdout_path        "log/unicorn.log"

before_fork do |server, worker|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end

  old_pid = "#{server.config[:pid]}.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|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end
end

before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = "#{app_path}/Gemfile"
end



#config/unicorn/staging.rb
app_path = File.expand_path( File.join(File.dirname(__FILE__), '..', '..'))

worker_processes   1
timeout            180
listen             '/tmp/unicorn_railsapp_staging.sock'
pid                "#{app_path}/tmp/pids/unicorn.pid"
user               'deploy', 'deploy'
stderr_path        "log/unicorn.log"
stdout_path        "log/unicorn.log"

before_fork do |server, worker|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end

  old_pid = "#{server.config[:pid]}.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|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end
end

before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = "#{app_path}/Gemfile"
end

nginx

现在unicorn跑起来,还需要一个接口把nginx反向代理到unicorn,这个通过配置nginx就好了。

在/etc/nginx/conf.d下新增2个配置文件,比如叫 production.conf 和 staging.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#production.conf

upstream production_app {
  server unix:/tmp/unicorn_railsapp_production.sock fail_timeout=0;
}

server {
  listen 80;
  server_name www.site.com;

  root /home/deploy/railstestapp_production/current/public;

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

  location @production_app {
    proxy_pass http://production_app;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 50M;
  keepalive_timeout 10;
}

#staging.conf

upstream staging_app {
  server unix:/tmp/unicorn_railsapp_staging.sock fail_timeout=0;
}

server {
  listen 80;
  server_name staging.site.com;

  root /home/deploy/railstestapp_staging/current/public;

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

  location @staging_app {
    proxy_pass http://staging_app;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 50M;
  keepalive_timeout 10;
}

最后执行以下命令让配置生效。

1
$ sudo nginx -s reload

恭喜你,你已成功使用 mina + unicorn + nginx 部署了你的rails应用!

欢迎留言。

完。