佈署 Rails App 到 Ubuntu 16

專案

現在到了一個沒有 .NET 的工作環境,臨時趕一專案需要趕快做一個 EC 平台串金流。

基於對 Rails 的愛,選了 Rails 來開發這個專案。時間雖然很趕,但 Rails 的框架很多東西都做好了,開發起來就很輕鬆。

設施&佈局

佈署目標是 Google Computing Cloud, 本來打算用 Container Engine 但因為 SSL 已經設定好在一個 Computing Cloud VM 上。找了一下暫時找不到怎麼把 Computing Enging 的 vm 跟 Container engine 內網串起來。所以還是建了 Ubuntu VM 來佈署。

>> 這次佈署的架構在這篇

在已經掛了 SSL 的 Nginx 上做簡單的 Reverse Proxy:

upstream store_milife {
    server 10.x.x.x;
    server 10.x.x.x;
    server 10.x.x.x;
}

server {
    location / {
        proxy_pass http://store-sites;
    }
}

透過內網把 Request 轉到我們部署了 Rails 的VM。這裡我們裝了另一個 Nginx 讓它先處理 Static asserts 再處理其他的 Request,我們把這些其他的 Request 轉到 unicorn 建立的 unix socket

upstream app {
    server unix:/opt/store/shared/sockets/unicorn.sock fail_timeout=0;
}

server {

    root /opt/store/public;

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

    location @app {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://app;
    }
    ...
}</pre>
<h2>Session</h2>
這麼一來因為我們有了 Load Balance 的機制,Rails App 就得把 Session 往共用的儲存空間放,不能再放本機記憶體,<a href="https://github.com/rails/activerecord-session_store" target="_blank" rel="noopener">這個 GEM</a> 可讓你很輕鬆的設定資料庫為 Session store,在原本的資料庫多一個表格存放 Session
gem 'activerecord-session_store'

送信功能

結帳後送信在 Rails 也是很簡單的,Rails 把送信的機制做在 Active Job 裏。 Active Job 讓 Rails App 在需要執行一些背景工作時可以透過一致的介面溝通。你可以選用你喜歡的 Queue 來處理在 Application 裡送出的 Job 。

Rails 預設的 Async Queue 是跟著 application 跑在同一個執行緒的。在開發這個專案的時候我選用了 Resque 。Resque 是把 Queue 存在 Redis 裡。

Email 的設定:

config.active_job.queue_adapter = :resque
# 根據不同的環境來設定 Queue 可避免類似 QA 環境的 Job 被 Production 吃到的問題
config.active_job.queue_name_prefix = "store_#{Rails.env}"
# 把要寄出的信件開在開發機上的瀏覽器預覽方便開發調整
config.action_mailer.delivery_method = :smtp
# 正式環境下用 :smtp 沒問題,但是測試環境下我用了 `letter_opener`
# 測試環境:
config.action_mailer.delivery_method = :letter_opener

接下來我們可用

$ rails g mailer StoreMailer

來建立幫我們寄件的郵差,在 app/mailers/store_mailer.rb 裏我們可以設定要寄出 Email 的內容。

寄信對 Rails 來說,就像在 Render HTML 頁面一樣,在 app/views/store_mailer 裡我們將設定對應的 view 作為 Email 的內容。比較不同的是,Mailer 要使用 heleper 的話需要自己指定 helper 不會自已載入。

class StoreMailer < ApplicationMailer     helper :application # Load app/helpers/application_helper.rb          ... end 

另外在 mailer class 裡我們需要設定內容,def order_notification 會對應到 app/views/store_mailer/order_notification.erb

 
def order_notification(user_session, order)     
    @header = {         
        :title => '訂購完成通知',
        :disclaimer => true,
    }

    @user = user_session
    @order = order
    mail(to: @user['email'], subject: @header[:title])
end

View 的寫法就與 HTML 頁面的 ERB 一樣

執行 Mailer Job

Resque config/initializers/resque.rb 可以載入 config/resque.yml 並藉此設定與 Redis 的連線

config/resque.yml

development: localhost:6379
test: localhost:6379
production: 10.x.x.x:6379

config/initializers/resque.rb

rails_root = ENV['RAILS_ROOT'] || File.dirname(__FILE__) + '/../..'
rails_env = ENV['RAILS_ENV'] || 'development'

resque_config = YAML.load_file(rails_root + '/config/resque.yml')
Resque.redis = resque_config[rails_env]

執行 Resque 的工作步驟很簡單,因為在此專案裡我有使用 foreman 管理不同的 process, 所以在 Procfile 裏我多加了下列工作:

mailer: rake environment resque:work QUEUE=store_${RAILS_ENV}_mailers

執行時,只需要如下指令即可啟動郵差的執行緒

foreman start mailer

Active Job 背景執行的工作

系統長大的時候常常會遇到一些效能的瓶頸,比如說,我們要把資料庫裡的資料匯出成 Excel 讓人下載,資料庫那邊的效能瓶頸以外,就是處理效能的瓶頸了。Active Job 讓我可以把要做的事情丟到 Queue 裡讓另一個 process 來處理。 Rails Application 就不需要在 Request Timeout 前短短回應時間內把它做完。

這邊有佈署 Active Job 的方式