因為要讓單一機器正常運作多個 Rails app 且互不干擾,閱讀這篇文章 https://codepany.com/blog/rails-5-and-docker-puma-nginx/ 的內容後,決定採用第六種作法:Rails app in docker and web server at host environment (文章內容是 Dockerize Rails app into container and run Nginx on host,我改文字符合自己環境的現況)。本文只提 apache 的設定檔。
headers
, proxy
, proxy_http
, rewrite
Host environment 架設 apache service,由 apache 當作 web server 和 reverse proxy ,處理從 Browser 傳給 App farm 的 request、自App farm 回覆給 Browser 的 response。如下圖。
App farm: B Rails app + Puma + Postgres at docker network
|
| http https
Docker <———> apache at host environment <————> requests from Browsers
為了讓讀者容易理解文章內容, 以下文章內容提到 App farm 和 Rails app server 將用 backend server 取代,以求一致於 apache 文件的用詞。
xx-csie-io.conf
<VirtualHost *:80> # apache 用 virtualhost 運作多個站就是要用 virtualHost tag. port 80 一般是不加密的 http 協定
ServerName xx.csie.io # 本站的網址
Redirect permanent / https://xx.csie.io/ # 如果使用者輸入 http://xx.csie.io ,將被 apache 導向到 https://xx.csie.io
RewriteEngine on # 開啟自動比對符合條件的網址內容,轉換成想要的結果網址
RewriteCond %{SERVER_NAME} = xx.csie.io # 當 網址的 server name 是 xx.csie.io
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] # 那麼就永久自動轉址轉向 https://xx.csie.io 並加上查詢的網址資源. [END, NE, R=permanent] 是 rewriterule flag ,意思是 END=結束此階段的比對 Rewrite rule 且不比對後續其他 RewriteRule 規則,NE=遇到特殊字元不使用跳脫字元輔助解譯, R=向browser 說明是永久轉址(屬於 SEO 方面的意義)
</VirtualHost>
<VirtualHost *:443> # 另一個 virtualhost 開 port 443
SSLEngine On #當網站有 SSL 證書時,需要開啟這個功能
Include /etc/letsencrypt/options-ssl-apache.conf # 使用 letsencrypt 的 ssl 選項給 apache 套用
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
#ServerName www.example.com
ServerAdmin [email protected] # 本 virtualhost 的管理員
ServerName xx.csie.io # port 443 下的網址名
# for reverse proxy
ProxyRequests Off # 因為開啟 reverse proxy 功能,所以建議關掉 ProxyRequests 功能, off 代表已關閉
RequestHeader set X-Forwarded-Proto "https" # 因為 apache 兩端有一端不是 https ,所以設定 request header 在 Rails app 和 apache 之間,X-Forwarded-Proto 值都改成 https
SSLProxyEngine on # proxy 在 Backend server 之間是 SSL
ProxyPreserveHost On # proxy 在轉傳 request 資料到 Backend server 會保留 browser 送過來的header
ProxyPass "/" "http://127.0.0.1:3000/" retry=0 # apache 轉傳來自 Browser 的 request 到 http://127.0.0.1:3000 (docker-compose blogapp對 host environment 的port) ,調整 request 網址成 blogapp server網址。 retry=0代表當收到Request後不使用等候秒數的限制,立刻傳給 Rails app at docker
ProxyPassReverse "/" "http://127.0.0.1:3000/" # apache 轉傳來自 Backend server at docker 的 request 到 Browser 前, 替換 request 中的 server name 成 apache 的server name,以避免 Backend server 重導向避開 apache
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/xxx_error.log # 如果 apache 運作上遇到錯誤, 像是找不到路徑或啟動 Rails app 失敗,一般能夠從 /var/log/apache2 找到 xxx_error.log
CustomLog ${APACHE_LOG_DIR}/xxx_access.log combined # 如果 apache 運作上收到正常request,一般能夠從 /var/log/apache2 找到 xxx_access.log
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
# Configuration for mod_expires
ExpiresActive On
ExpiresDefault "access plus 1 year"
SSLCertificateFile /etc/letsencrypt/live/xx.csie.io/cert.pem # let’s encrypte ssl file 1
SSLCertificateKeyFile /etc/letsencrypt/live/xx.csie.io/privkey.pem # let’s encrypte ssl file 2
SSLCertificateChainFile /etc/letsencrypt/live/xx.csie.io/chain.pem # let’s encrypte ssl file 3
</VirtualHost>
ProxyPassMatch
和 RewriteRule
寫法如果你的 Rails app 的靜態檔案所放置目錄有跟其他不同的地方,你可以寫成以下形式讓 apache 幫你重導 request 向 Backend server 的自訂路徑去拿,如下面的語句:
ProxyPassMatch "^/assets/(.*)" "http://127.0.0.1:3000/assets/$1" # request 網址內容中是 /assets/* 系列檔案,都改從 Backend server 的 assets 目錄下載相同名稱的檔案。(Backend server 一般放置編譯過的靜態檔案是在 public/assets 目錄,而不是 assets 目錄 )
或者是改成下面,並跟樓上一起用:
# serve static files
RewriteEngine On
RewriteRule "^/assets/(.\*)" "/assets/$1" [P] # 代表用 apache `mod_proxy` 去向 backend server 連線索取資料
但如果要一起用,我覺得直接用 ProxyPassMatch
就好。
ProxyPassMatch
和 RewriteRule
的原因原本 xx-csie-io.conf
內容中,有本文上述的 ProxyPassMatch
、 RewriteEngine
和 RewriteRule
語句預計讓 apache server 傳送靜態檔案(assets: css, js, img, ……etc)到 Browser,但瀏覽器一直下載不到靜態檔案,導致頁面跑版。尋找問題的原因,推測以下三點:
X-sendfile
X-sendfile
for apache 功能(本來在 apache 2.4.7 就預設關閉 X-sendfile,在 Rails app 啟用也不會正常運作)過程一度認為是 apache reverse proxy 的設定問題,所以寫 ProxyPassMatch 和 RewriteRule 語句嘗試解決問題,但沒有解決。後來決定讓 Rails app server 自行處理傳送靜態檔案的事情,雖然速率較 web server 方式慢,但至少能用就好。
Q1. xx_error.log
出現訊息:
HTTP Origin header (https://xxxx) didn't match request.base_url (http://xxxxx) 。
A1. 在 xx-csie-io.conf
設定檔,加入以下語句:
RequestHeader set X-Forwarded-Proto “https”