What's "Reverse Proxy Model" ?
在提起 Unicorn 及 Nginx 的配置之前,我們得先了解一下「反向代理機制」,下面這張圖應該可以幫助你了解:(轉自: ROR實戰聖經) |
在用戶端向 server 作請求的時候,不是直接與 app server 連結 (中間那三個),而是先連到 web server ,通常是 Apache 或 Nginx,再由 web server 指派 request 給 app server、或直接 serve static assets。
所以在這樣的架構下你可以想像你的系統需要常駐著以下的服務:
- web server
- 數個 app server
- DB server
這樣的架構是與常見的 Apache/Nginx + Passenger 架構有所不同的,需要作一點觀念上的調適。下面我們就來介紹一下如何做相關的配置。
Nginx 配置
以下是預設的 Nginx 設定檔 - nginx.conf:user deployer; # 定義操作 nginx 的使用者 worker_processes 1; # 定義 worker 的數量 error_log /var/log/nginx/error.log; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; access_log /var/log/nginx/access.log; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; gzip on; gzip_http_version 1.0; gzip_comp_level 2; gzip_proxied any; gzip_vary off; gzip_types text/plain text/css application/x-javascript text/xml application/xml application/rss+xml application/atom+xml text/javascript application/javascript application/json text/mathml; gzip_min_length 1000; gzip_disable "MSIE [1-6]\."; server_names_hash_bucket_size 64; types_hash_max_size 2048; types_hash_bucket_size 64; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; }
上面這段預設的設定檔有兩個地方需要注意:
第一行的使用者身分必須為負責 deploy 的用戶,否則常常會出現 403 權限問題,因為未經設定的話 nginx 會沒有權限存取 deployer 的檔案,如 js, css。
第二行的 worker 數量建議別設定超過 cpu 數目,多了也沒好處。
再來就是重頭戲了,我們要開始加入 app server 的設定,這段設定需要加在 http 的括號內:
upstream unicorn { server 127.0.0.1:8080 fail_timeout=0; } server { listen 80 default deferred; server_name flytutor.com; root /var/www/flytutor/public; location ^~ /assets/ { gzip_static on; expires max; add_header Cache-Control public; } location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; if (!-f $request_filename) { proxy_pass http://unicorn; } } error_page 500 502 503 504 /500.html; client_max_body_size 4G; keepalive_timeout 10; }
upstream:
首先我們先來講解一下第一部分的 upstream。首先,upstream 後的「unicorn」並非一定的,端看你希望怎麼命名,但是要注意的是 proxy_pass 也需作對應的設定。再來,upstream 內的 server 可以不只一個,如果你有數個 app server 在不同的 host 的話,你也可以做如下的設定:upstream flytutor.com { server 192.168.0.111:8080 weight=3; server 192.168.0.222:8080 weight=2; server 192.168.0.333:8080 weight=3; }其中 weight 代表的是「被配發 request 的權重」,當 weight 越高的時候被分配到的機率越大,可以做 loading balance。
而除了走 TCP/IP 外,如果是在 web server 和 app server 都在同一個主機的情況下,你也可以使用 socket 來做設定,基本上效能會比 TCP/IP 好一點點(因為少了 header):
upstream unicorn { server unix:/tmp/unicorn.sock fail_timeout=0; }
server:
接下來我們要介紹一下 server 的設定,基本上這邊指的 server 就是指 app server,一台 nginx server 可以依 server 設定派發 request ,所以可以做 vhost 的應用,例如判斷 request 的不同網域 (設定檔中的 "server_name")、或是同網域但是不同 port (設定檔中的 "listen"),來派發到不同的服務。proxy_pass 很重要,許多網友都死在這個地方,切記要將他設置成 upstream 的命名,例如 "http://<upstream_name>"。
你可以想像當一個 request 經過 location(path), server_name(domain), listen(port) 重重篩選後,你卻不指派 proxy_pass 的目的地給他,讓這個 request 就此隱沒在數據流中,成了「digital phantom」,你於心何忍?
Unicorn 配置
unicorn 的配置通常是放在 rails project 中的 config 資料夾,長得像這樣:app_root = "/var/www" app_name = "flytutor" listen "127.0.0.1:10101" #, :backlog => 2048 #這邊要跟nginx虛擬主機檔中upstream內定義的務必一樣 worker_processes 2 #看情況開 preload_app false timeout 30 module Rails class <<self def root File.expand_path(__FILE__).split('/')[0..-3].join('/') end end end _working_directory = File.join(app_root, app_name) working_directory _working_directory logs_path = "#{_working_directory}/log" pid "#{_working_directory}/tmp/pids/unicorn.pid" stderr_path "#{logs_path}/unicorn.stderr.log" stdout_path "#{logs_path}/unicorn.stdout.log" GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true before_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! old_pid = "#{Rails.root}/tmp/pids/unicorn.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 puts "Send 'QUIT' signal to unicorn error!" end end end after_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection end
這邊要注意的地方有兩個:
- listen 的對象必須與 nginx.conf 中 upstream 設定的一樣,unicorn 與 nginx 就是靠這行設定連起來的。還有,如果是走 TCP/IP 的設定,盡量不要使用 1024 以下的 port 以免 deployer 沒有權限啟動 unicorn 導致失敗(你總不能老用 root 權限作 deploy 吧?)。
- unicorn 是採用 master-slave 機制,啟動 unicorn 後叫出 process 列表你就會了解的。而這裡的 worker_processes 的數目顧名思義就是指定 worker 的數目。開多少個才好?基本上沒有一定的標準答案,還是要依你的使用情況及環境做測試後才知道,如果懶得想的話就直接設定為「CPU 數目 + 1」吧。
操作 unicorn
許多網友在網路上搜尋到的啟動法是以 unicorn_rails 來啟動:/usr/bin/unicorn_rails -c config/unicorn.rb -E $RAILS_ENV -D
。但現在 unicorn 官方已經不建議使用這種作法而是直接改用 unicorn
來啟動/重啟 unicorn。
請問 這樣的配置 跟Apache + Passenger 有什麼不同呢?
回覆刪除因為我們是用 Amazon 的 ELB + Apache + Passenger .
想問看看您這樣的配置是基於什麼樣的考量, 優點只有 zero down time ?
還希望您不吝賜教.
很抱歉,我沒有實際用過ELB。
刪除但依我的了解,你的架構應該是:開數臺instance,每台上面都同時 run Apache+Passenger,再用ELB擋在前面。
當然,因為AWS把一系列的服務整合的很好,ELB可以偵測EC2的狀況並調整Loading分配,相對這裡提到的反向代理架構的分配機制就非常陽春。
雖然沒有實際比較過兩者的效能差異,但是可能有幾點是您需要考量的:
1. 我提到的配置是將web server 和 app server分開,各司其職。而您提及的架構則是兩者並存在同一instance,有N個instance,就有「N個apache+N個passenger」,效能比起「1個Nginx+N個Unicorn」孰優孰劣呢?
2. 因為Nginx會負責serve所有的靜態資源,所以當用戶請求靜態資源時,會直接由Nginx返回,不用再request App server,相對快速。而 Nginx 本身 static file serving 的效能也相對Apache較優異一些。當然,如果您的大部分靜態檔案都有上CDN的話,這部份的差異應該不會太明顯才是。
希望有幫助到你,如有錯誤也希望你不吝指正,謝謝。