將Rails佈署(Deploy)到VPS上


一般來說,Rails佈署是非常麻煩的事情,尤其整個技術的變遷實在太過快速,很多網路上的資料都舊了。自己最近在研究佈署,發現很多資料都已經過期,在這邊彙整自己將Rails佈署到VPS上的心得。以下示範的是非常精簡的版本,沒有效能調教、沒有安全措施,基本上就只是個能動、稍微對Ruby增加一點彈性的佈署結果。如果有需要更深入的佈署,網路上有非常多關於主機調教的教材。

本機環境:Mac OSX 10.9
Capistrano版本:3.4.0
VPS主機:Digital Ocean
遠端主機作業系統:Ubuntu 14.04
遠端Ruby版本:2.2.0
遠端Rails版本:4.1.2
遠端資料庫:mysql
Web Server:nginx
App Server:passenger

0.登入VPS

首先,在Digital Ocean開啟一個新的droplet,這在任何VPS應該都相同,接著利用terminal的ssh登入該主機。

$ssh root@111.222.333.444

請將111.222.333.444換成你的主機ip。

1.切換至新的user

所有事情用root來做,事後都會有權限的問題需要解決,因此登入後第一件事情就是開另一個user來用。

$adduser motionex
(新增user需要輸入密碼)
$sudo usermod -aG sudo motionex
$su motionex

請將motionex換成你想要的使用者名稱,第二步是給予該user root權限。

2.安裝必要套件

利用Ubuntu標準動作apt-get安裝必須要用的套件,內容繁多這邊就直接列表。

$sudo apt-get update
$sudo apt-get -y install build-essential libssl-dev libyaml-dev libreadline-dev openssl curl git-core zlib1g-dev bison libxml2-dev libxslt1-dev libcurl4-openssl-dev libsqlite3-dev sqlite3 mysql-common mysql-client libmysqlclient-dev mysql-server

沒有意外的話會下載及安裝許多套件,另外安裝mysql時他會向我們詢問密碼,同樣輸入即可。

3.安裝nginx

一般來說也可以用apt-get安裝,但可能會安裝到過期的版本,這邊是用Railscast上另外新增repo的安裝方式。

$ sudo add-apt-repository ppa:nginx/stable
$ sudo apt-get -y update
$ sudo apt-get -y install nginx

如果安裝完成正確無誤,可執行:

$ sudo service nginx start

並打開瀏覽器,輸入主機的ip位置,就會出現Welcome to nginx的頁面。

4.安裝node.js

如同我們一般在Rails教學上所學到的,要執行Ruby,也要先安裝Node.js

$ sudo add-apt-repository ppa:chris-lea/node.js
$ sudo apt-get -y update
$ sudo apt-get -y install nodejs

安裝完成以後輸入:

$node -v

只要有出現版本號,就代表安裝成功。

5.安裝rbenv

許多教學為了避免安裝過多其他套件,導致離題,因此都是直接從Ruby官網提供的版本,利用make指令來安裝Ruby。但這未來會有一個比較麻煩的問題是如果要更新Ruby會比較麻煩。這邊安裝rbenv,要切換版本比較方便,且安裝過程也不麻煩。執行Railscast建議的rbenv-installer:

$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer | bash

正確的指令請參考rbenv-installer官方repo。如果指令有改,請以repo當中的為主。安裝完成以後請繼續執行:

$ nano ~/.bashrc

在這個檔案中,我們要在最前面加上一段rbenv指定要加上的code:

export RBENV_ROOT="${HOME}/.rbenv"
if [ -d "${RBENV_ROOT}" ]; then
  export PATH="${RBENV_ROOT}/bin:${PATH}"
  eval "$(rbenv init -)"
fi

加上以後,按ctrl + x離開,並按大寫Y儲存,再按一下Enter確定儲存離開。這邊用的是nano編輯器,你也可以用自己喜歡的編輯器貼上code。接著執行:

$ source ~/.bashrc
$ rbenv --version

正確的話會出現版本號,代表安裝成功。接著安裝最新穩定版本的Ruby,目前是2.3.1:

$ rbenv install 2.3.1
$ rbenv rehash
$ rbenv global 2.3.1
$ ruby -v

正確無誤的話會看到Ruby版本顯示出來。

另外,ihower有說明直接安裝Ruby的方法,若不想安裝rbenv,也可參考他的內容,但本人依照他的教學一直安裝失敗,可能本人機器狀況不佳,人品也不好。

6.安裝passenger

雖然Ruby自己有提供passenger的gem,但passenger需要sudo權限,必須要在sudo底下再安裝一個ruby,比較麻煩,權限也很容易混淆,因此我們改用apt-get來安裝。

$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 561F9B9CAC40B2F7
$ sudo nano /etc/apt/sources.list.d/passenger.list

打開檔案以後,加入以下這行:

deb https://oss-binaries.phusionpassenger.com/apt/passenger trusty main

接著用同樣方式離開nano編輯器,使用ls檢查該檔案的所屬人是誰,如果是你新開的帳號而非root,則我們要將該檔案的所有者改為root:

$ sudo chown root:root /etc/apt/sources.list.d/passenger.list
$ sudo chmod 600 /etc/apt/sources.list.d/passenger.list

接著從nginx外掛當中安裝passenger:

$ sudo apt-get update
$ sudo apt-get install nginx-extras passenger

7.確認Ruby路徑是否正確

安裝完成以後,有可能會將Ruby的連結打亂,因此檢查一下Ruby的路徑:

$ which ruby

正確的結果:/home/motionex/.rbenv/shims/ruby錯誤的結果:/usr/bin/ruby

motionex這個user名稱是我們在步驟1新增的,你電腦上顯示的會是你新增的名稱。

如果很不幸的路徑錯了,代表rbenv並沒有正常運作,請回到步驟5重新安裝一次rbenv。

8.將passenger掛到nginx上

打開nginx文件。

$ sudo nano /etc/nginx/passenger.conf

會看到以下兩行:

passenger_root /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini;
passenger_ruby /usr/bin/ruby;

將第二行修改為rbenv的路徑,讓整個檔案變成:

passenger_root /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini;
passenger_ruby /home/motionex/.rbenv/shims/ruby;

motionex請改為你的帳號名稱。另外打開原本nginx的設定檔:

$ sudo nano /etc/nginx/nginx.conf

找到以下這行:

# include /etc/nginx/passenger.conf;

移除註解符號,讓他使用我們剛調整的passenger設定。

9.將原本的伺服器port關閉

這個步驟要做的事情是將原本我們在瀏覽器打開的welcome to nginx頁面關閉。在terminal中輸入:

$sudo nano /etc/nginx/sites-available/default

找到以下兩行:

listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

將這兩行的前面加上#符號,註解掉,儲存以後伺服器就不會接收從預設的80 port進來的http request,再打開一次瀏覽器,應該會出現「無法顯示網頁」。

10.建立我們要佈署的rails資料夾

接下來我們要用同樣方式寫一個設定檔案,用來取代原本的設定檔。輸入:

$ sudo nano /etc/nginx/sites-available/rails

這個指令當中的rails可以是任何名字,只要記得即可。打開以後在裡面填入:

server {
  listen 80 default_server;
  server_name www.mydomain.com;
  passenger_enabled on;
  passenger_app_env production;
  root /var/www/rails/current/public;
}

這邊有兩件事情需要注意:

  1. server_name 的地方,如果你沒有申請域名(例如www.domain.com),這行可以省略。
  2. root 代表你的code放在哪,記得務必指向你rails資料夾當中的public資料夾。這邊的資料夾結構說明:
/var/www 是server預設的資料夾
/rails 是我們剛才的所設定的資料夾名稱,建議這邊的名稱和前面的資料夾名稱相同
/current 是capistrano佈署時所產生的資料夾,如果你沒打算用capistrano佈署,可以不用管這層的資料夾
/public 是rails當中的public資料夾,nginx這邊的設定必須指向一個public資料夾

11.將網站設定啟用

將剛剛的設定檔,設定一個link到nginx啟用網站的資料夾:

$ sudo ln -s /etc/nginx/sites-available/rails /etc/nginx/sites-enabled/rails
$ sudo nginx -s reload

重啟之後,這邊的設定就算完成了,接著要設定本機的rails app。

12.本機安裝capistrano

這邊回到本機,假設已經有一個準備好要佈署的專案(空專案也行),安裝 gem capistrano:

group :development do
  gem 'capistrano-rails'
  gem 'capistrano-passenger'
end

# 並加上稍後會使用的資料庫gem
gem 'mysql2'

並且

$ bundle

完成之後,在專案的根目錄底下產生capistrano設定檔:

$ cap install

會產生一些設定檔。

注意:capistrano可能因為版本的更新而造成指令不同,請在官方repo頁面確認正確的指令。

13.設定capistrano

接著進行設定,進入config/deploy.rb,以下兩行原本就存在,請改成你想要的設定:

set :application, "rails"
set :repo_url, "git@bitbucket.org:my_accoung/rails.git"

使用capistrano會搭配使用git,如果你的專案是不開放的,建議放到Bitbucket,就不會變成public專案。否則放在github即可。

回到設定檔中,以下是原本註解掉或不存在的設定,請刪除註解或補上:

set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')

role :web, %w{motionex@111.222.333.444}

motionex及後方的ip請換成自己的設定及VPS ip。

$ cap production deploy:check

capistrano會開始幫你檢查及調整VPS上的設定,可能會需要你輸入密碼。

首先會碰到的問題是你可能需要將本機的ssh key加到github或bitbucket上,才有可能進行佈署。 Bitbucket ssh文件說明Github ssh文件說明

如果你接下來看到一個錯誤訊息:

mkdir:
cannot create directory '/var/www/rails'
: Permission denied

恭喜你,前面的部份都過關了,接下來請進入下一個步驟來解決這個問題。

14.設定database.yml及secrets.yml

這段有點囉嗦,如果你真的很不在乎安全性,那可以跳過這段。但基本上把db密碼和網站的hash直接存放到git是很怪的事情,容易受到有心人的攻擊,所以我們要直接在VPS上建立設定檔,而不從本機佈署上去。

如果你的config/database.ymlconfig/secrets.yml兩個已經push上去到repo過了,請加到.gitignore中,並且用git rm --cache指令來從repo當中刪除。

前一個步驟最後所產生的錯誤,是因為/var/www底下需要有sudo權限才能編修檔案。我們需要先在VPS上把幾個必須的結構建立起來,capistrano才能幫我們完成剩下的工作。

請在回到VPS上,如果你的連線已經不見了,可重新用你的新帳號ssh連線回去,記得不要用root帳號。接著輸入:

$ cd /var/www
$ sudo mkdir rails
$ sudo chown motionex:motionex rails

最後一個指令是修改我們新開的資料夾權限為自己可以修改,以免佈署時權限不足,記得將motionex改為自己的使用者帳號。

只要有權限問題,記得用ls -l指令檢查權限,並用chown指令修改權限。

接著:

$ cd /var/www
$ mkdir rails/releases
$ mkdir rails/shared
$ mkdir rails/shared/config
$ touch rails/shared/config/database.yml
$ touch rails/shared/config/secrets.yml

打開你在本機的config/secrets.yml檔案,整個複製起來,並且回到VPS上我們剛剛建立的secrets.yml檔案當中,整個貼上。

而database.yml比較麻煩一點,因為我們要使用的是mysql資料庫,因此請在剛剛建立的database.yml當中寫入以下內容:

default: &default
  adapter: mysql2
  encoding: utf8
  username: root
  password: my_password
  host: 127.0.0.1
  port: 3306

development:
  <<: *default
  database: rails_development

test:
  <<: *default
  database: rails_test

production:
  <<: *default
  database: rails_production

其中的my_password請設定為你剛剛安裝mysql時所設定的密碼,而三個資料庫名稱,也可以隨意取。

以上這個步驟是要把我們繞過git的檔案手動增加上伺服器上。

兩個檔案都增加完成以後,再回到本機執行:

$cap production deploy:check

正確無誤的話會看到完成檢查的訊息,此時可以正式執行:

$cap production deploy

就會快樂的看到他把檔案丟上去了,但目前連還不算佈署完成,本機依然需要一些Rails的相關設定。

15.Rails設定

接下來的步驟大家就應該都很熟悉了,請連線到VPS並進入rails資料夾:

$ cd /var/www/rails/current
$ gem install bundler
$ bundle
$ rake db:create
$ rake db:migrate
$ sudo service nginx restart

這時再打開瀏覽器,就會看到熟悉的rails首頁啦!

問題:如果你是佈署空專案的話,會出現404頁面,請加上一個首頁,用以確認佈署成功
問題:如果有遇到db無法create的問題,請確定'mysql'和'mysql2'兩個gem都有安裝在VPS上,執行$gem install 'mysql'即可
問題:如果重開機以後瀏覽器顯示Incomplete response received from application,代表secrets的hash有問題,可用以下步驟解決:
  1. 執行$rake secret
  2. 複製產生的一長串hash
  3. /var/www/rails/shared/config/secrets.yml檔案當中,將原本production底下的secretkeybase換成新的hash即可。

如果有任何可以再精簡的地方或改善的地方,歡迎指正!

後續操作

在Capistrano上設定Rails自動化指令

參考資料

ihower Ruby on Rails實戰聖經

Digital Ocean官方教學

Railscast

圖片來源

CC授權:WikiMedia