Ruby on Rails 實戰聖經

使用 Rails 5.0+ 及 Ruby 2.3+

電子書製作中,歡迎留下 E-mail,有消息將會通知您。若您有任何意見、鼓勵或勘誤,也歡迎來信給我。願意贊助支持的話,這是我的微信 QR Code,謝謝。

網站佈署

How long would it take your organization to deploy a change (to production) that involves just one single line of code? Do you do this on a repeatable, reliable basis? - Mary Poppendieck

終於要脫離開發階段,要把完成的Ruby on Rails應用程式拿來出上線見人了。在rails server指令中,其實是使用一套叫做WEBrick的伺服器,這是一套純Ruby實作的HTTP伺服器。雖然開發時拿來用很方便,但是它的效能並不適合作為正式環境來使用。因此,我們在這一章將介紹幾種在Linux上實際作為Production用途的佈署方案。

雖然RailsWindows平台上也可以執行開發,但是如第二章作業系統一節所說,RubyWindows平台上資源較少,效能也不如在Unix-like系統上,因此很少人拿來當做Production伺服器用途。

虛擬主機租用

在這雲端時代,在線上租用伺服器是最經濟實惠的選擇,常見的選擇包括:

IaaS 類型(Infrastructure as a Service)

你可以獲得一整台的root權限,這些提供服務的廠商又可以概分為 VPS 和雲端計算兩種,常見的廠商包括:

VPS 類型的服務因為價格非常便宜,計價方式也很簡單,一個月只需要美金五塊、十塊起跳,它套裝就包括很夠力 CPU 效能、流量頻寬、硬碟空間等,挑東京機房離台灣也近,所以成為小網站或個人裝機的高C/P值首選。

AmazonMicrosoftGoogle等雲端計算平台則以豐富的雲端生態系見長,除了虛擬主機之外,它還有提供資料庫、檔案儲存和NoSQL資料庫等等各式各樣的代管服務,但同時設定和計價模式也複雜的多,適合專業的網路服務。

PaaS 類型(Platform as a Service)

PaaS則是固定的執行環境,只支援特定的程式語言或框架,支援Ruby的有:

不過這些PaaS價格貴的多,而且大多只有在美國有機房,筆者通常只是拿他們的免費方案試玩。

租用 Ubuntu Linux 虛擬主機

以下我們會使用 Linode 這個服務,並搭配 Amazon S3 這個檔案儲存的服務,將使用者上傳的檔案放在 S3 上。當然,直接放 VPS 上也可以,只是需要注意 disk 容量上限以及之後搬家比較麻煩而已。

新註冊的同學,歡迎用這個 Referrals 連結 https://www.linode.com/?r=69ed98a54605a017454669c501a8b17cd2769ead

用 Vultr 的話,可以用 http://www.vultr.com/?ref=6880033 這個 referral URL

  • 進入 Linode Manager: https://manager.linode.com
  • 進入 Rebuild 選單選 Ubuntu 16.04,輸入密碼,按 Rebuild,會進入Dashboard,等他跑完按 Boot
  • (本機) 用 ssh 連到 server,主機 IP 在Network access 選單可以看到。例如:ssh root@106.185.55.19,離開打 exit
  • 若是使用AWS ssh -i your_aws_key.pem ubuntu@aws-ip

Linux 作業系統的發行版(Linux distribution)有很多種,例如 Ubuntu 之外,還有 Debian、CentOS、Redhat 等等。這裡推薦初學者使用最多人使用的 Ubuntu Server 版,比較不會碰到安裝問題,就算碰到也較容易搜尋到解答。

依照 Ubuntu 的命名慣例,建議挑.04是 LTS (Long Term Support) 版本。

購買 Domain Name 和設定 DNS

  • 推薦在 NameCheap 買網域,請點這個 referral URL: https://www.namecheap.com/?aff=91800 註冊: 讓我們賺 15% 傭金
  • 假設購買 example.com,你可以讓 1. NameCheap 代管 DNS,或改用 2. 你自己的 DNS:
  • 讓 NameCheap 代管 DNS:
    • Advanced DNS -> Domain Nameserver Type 選 Namecheap Default
    • Advanced DNS -> Host Records -> Manage -> ADD RECORDS
      • 新增 A Record,設定 host 是 @ 指向你的 server ip (這表示 example.com )
      • 新增 A Record,設定 host 是 www 指向你的 server ip (這表示 www. example.com )
  • 筆者推薦用 Cloudflare 這個服務代管你的 DNS,一來它有免費方案,二來他在全世界各地都有機房(包括台北),最後他還有 HTTP 快取伺服器的功能非常不錯,將來可以用到。

    • 首先註冊 Cloudflare,輸入你的 Domain Name,接著設定 DNS,設定一個 A Record 指向你的伺服器 IP Address。這裡我們暫時還不需要它的快取功能,所以請把 Status 設成 DNS Only,而不是DNS and HTTP Proxy (CDN):也就是選過那朵雲,而不是經過它。
    • Cloudflare 最後會告訴你它兩台 CloudFlare Nameservers 的位置

    • 回到 NameCheap 的 Advanced DNS -> Domain Nameserver Type 選 Custom,然後 Nameservers 填入:
      • <位置1>.ns.cloudflare.com
      • <位置2>.ns.cloudflare.com
  • 修改本地的 sudo vi /etc/hosts 可以直接作 ip 和 domain name 的對應,不需要外部 DNS。這在測試 web server 的時候很好用,不需要等 DNS 生效。不過記得測試完最好砍掉,以免之後忘記,發生改 DNS 後怎麼連線都連錯 ip 的慘劇。

安裝網站伺服器 (Ubuntu 16.04)

租到一台虛擬機之後,你應該可以使用SSH登入。以下則是在Ubuntu 16.04上安裝系統和Ruby的指令。以下操作有 (本機) 開頭的指令表示在本地端執行,其他則是指在遠端伺服器上。

1. 更新和安裝系統套件

apt-get是 Ubuntu 和 Debian 內建的套件管理工具,類似於 Mac 上的 homebrew。以下的指令會更新和升級已經安裝的套件:

sudo apt-get update
sudo apt-get upgrade -y
sudo dpkg-reconfigure tzdata

進入選單選你的Time zone=>Asia=>Taipei

接著我們安裝新的套件們,這些是 Ruby on Rails 所需要的東西。請輸入以下一行指令:

sudo apt-get install -y build-essential git-core bison openssl libreadline6-dev curl zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3  autoconf libc6-dev libpcre3-dev curl libcurl4-nss-dev libxml2-dev libxslt-dev imagemagick nodejs libffi-dev

2-1. 安裝 Ruby 快方法:用套件安裝

使用 https://www.brightbox.com/docs/ruby/ubuntu/ 已經編譯好的 Ruby 套件

sudo apt-get install software-properties-common
sudo apt-add-repository ppa:brightbox/ruby-ng
sudo apt-get update
sudo apt-get install ruby2.3 ruby2.3-dev

安裝好之後,輸入

ruby -v

應該就會看到 ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu] 就是成功了。

接著安裝 Bundler gem

sudo gem install bundler

2-2. 安裝 Ruby 慢方法:自行編譯原始碼

或是我們可以下載 Ruby 的原始碼,自行編譯。相較於方法一會花比較久的時間,不過如果有新版 Ruby 發行時,上述 brighbox 不一定會即時包好,或是 brighbox 不維護的話,這時想用新版需要自己編譯了:

wget http://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
tar xvfz ruby-2.3.1.tar.gz
cd ruby-2.3.1
./configure
make
sudo make install

請將2.3.1換成最新的Ruby版本

上述指令 wget 用來下載檔案、> tar 是 Linux 上解壓縮的工具

./configure 如果在centos下安裝失敗時,改用 ./configure –prefix=/usr 指定路徑

編譯耗時,請耐心等候。如果你租的虛擬主機 CPU 太爛,建議不要自行編譯,例如 Amazon EC2 最低的 Micro 等級。

3-1. 安裝MySQL資料庫

MySQL 是一個非常受歡迎的關聯式資料庫,可以說是大多數網路公司的首選。以下是安裝MySQL的指令,過程中會提示你設定資料庫的root密碼(請記下來,等會設定 Rails 會用到)。

sudo apt-get install mysql-common mysql-client libmysqlclient-dev mysql-server

接著我們進入 mysql console 建立新的資料庫:

mysql -u root -p

進入 mysql console 後,輸入:

CREATE DATABASE your_database_name CHARACTER SET utf8mb4;

手動建立一個資料庫(注意,資料庫名稱不能包括橫線-),等會你的Rails就用這個。執行完,輸入 exit 離開 mysql console,然後繼續以下步驟。

若是之後用相同主機建立一個新專案,要再重複這個步驟新增資料庫。

3-2. 或是安裝PostgreSQL資料庫

要用 MySQL 的話,就不需要裝 PostgreSQL 了,二選一。MySQL 是網路公司的最愛,分散式擴充和商業支持的生態系非常豐富。PostgreSQL 則是對進階的 SQL 語法支援比較多,以及支援更多的儲存格式,例如 PostGIS

你也可以選擇安裝PostgreSQL

sudo apt-get install postgresql libpq-dev postgresql-contrib

修改帳號 postgres 的密碼

sudo -u postgres psql 然後打 \password

建資料庫

 sudo -u postgres createdb your_database_name

4-1. 安裝 Nginx + Passenger 快方法:用套件安裝

Passenger是目前佈署Ruby on Rails最好用、設定最簡單的方式,它是一套ApacheNginx的擴充模組,可以直接支援Rails或任何Rack應用程式。

Passenger不支援Windows平台

以下我們選擇使用Nginx是目前最流行的網站伺服器之一,相較於Apache雖然功能較少,但運作效率非常優秀。要讓Nginx裝上Passgener不需要先裝Nginx,只需要執行以下指令:

以下參考 Installing Passenger + Nginx 的步驟:

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7

sudo apt-get install -y apt-transport-https ca-certificates

# Add our APT repository
sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger xenial main > /etc/apt/sources.list.d/passenger.list'

sudo apt-get update

# Install Passenger + Nginx
sudo apt-get install -y nginx-extras passenger

打開你的瀏覽器,輸入 Server IP 位置,應該就可以看到預設的 Nginx 網頁了:Welcome to nginx on Ubuntu!

Nginx啟動和重開用法:

sudo service nginx start
sudo service nginx stop
sudo service nginx restart

4-2. 安裝 Nginx + Passenger 慢方法:自行編譯

或是也可以用自行編譯的方式,好處是可以 customize Nginx 的版本:

$ sudo gem install bundler passenger --no-ri --no-rdoc
$ sudo passenger-install-nginx-module

過程中:

  • 選 ruby
  • 接著會要你選1. download 或 2. customize=> 請選 1
  • 用預設目錄 /opt/nginx

這是因為Passenger必須與Nginx一起編譯的關係,所以Passenger的安裝指令就包括了安裝Nginx。接著我們設定 Nginx 啟動腳本:

wget -O init-deb.sh http://www.linode.com/docs/assets/1139-init-deb.sh
sudo mv init-deb.sh /etc/init.d/nginx
sudo chmod +x /etc/init.d/nginx
sudo /usr/sbin/update-rc.d -f nginx defaults

自行編譯的話,Nginx 的設定檔位於 /opt/nginx/conf/ 下。

5. 新增 deploy 使用者

將下來我們想要找地方放我們 Ruby on Rails 專案的程式。因為 root 帳號權限很大,習慣上我們會在伺服器上另開一個專門的帳號,用來放你的Rails專案程式碼。這裡我們另開一個 deploy 帳號來使用:

sudo adduser --disabled-password deploy
sudo su deploy
cd ~
ssh-keygen -t rsa

--disabled-password deploy 參數會讓deploy無法用密碼登入,因為我們打算用 SSH Key 來登入更安全。 su指令是切換使用者

接著複製本機的 ~/.ssh/id_rsa.pub 到 /home/deploy/.ssh/authorized_keys:

  • 在本機電腦輸入 cat ~/.ssh/id_rsa.pub,會出現一串文字,複製下來
  • 在 server 上輸入vi /home/deploy/.ssh/authorized_keys,進入vi去編輯該檔,把上一個步驟的視窗內的文字copy貼上到vi內,然後 :wq 離開

    chmod 644 /home/deploy/.ssh/authorized_keys
    chown deploy:deploy /home/deploy/.ssh/authorized_keys
    

這樣本機就可以直接 ssh deploy@<主機IP位置>,登入無須密碼。

6. 設定你的 Rails 專案 (如果做自動化部署的話,這一節就不需要做了)

假設我們的 Rails 專案是放在 Github 上,那麼從 GitHub pull 下來即可。接下來請在遠端主機上操作:

cd ~
git clone https://github.com/ihower/rails-exercise-ac9.git
cd your_project_name

如果用 SSH 協定,你可以進去 Github 的 Repo Setting 新增 Deploy Key。

接著設定使用 MySQL 資料庫。

如果你在本機開發還沒改用 MySQL 資料庫的話,建議改用 MySQL,可用 brew install mysql 安裝,執行 brew services start mysql 就會常駐在你本機的電腦。在本機的 MySQL 密碼預設是空白。

修改 Gemfile 加上 gem "mysql2" 並執行 bundle,然後 commit 並 push 程式碼。

編輯 config/database.yml ,設定使用 MySQL:

production:
  adapter: mysql2
  pool: 25
  encoding: utf8mb4
  database: your_database_name
  host: localhost
  username: root
  password: your_database_password

如果是用 PostgreSQL 的話:

production:
  adapter: postgresql
  pool: 25
  database: your_database_name
  host: localhost
  username: root
  password: your_database_password

接著編輯 config/secrets.yml (在本機用 rake secret 可以隨機產生一個新的 key):

production:
  secret_key_base: xxxxxxx........

執行

bundle install --deployment --without test development

執行

RAILS_ENV=production bundle exec rake db:migrate
RAILS_ENV=production bundle exec rake assets:precompile

之後如果要更新 rails 專案的程式碼:

git pull
touch tmp/restart.txt

7. 設定 Nginx (如果做自動化部署的話,這一節等做好自動化部署之後再做)

輸入 exit 回到 root 帳號

編輯 /etc/nginx/nginx.conf,打開以下一行:

include /etc/nginx/passenger.conf;

/etc/nginx/nginx.conf最上方新增一行:

env PATH;

少這一行的話,等會 Rails 會找不到 nodejs 的路徑,在 nginx error log 中會有 Message from application: There was an error while trying to load the gem ‘uglifier’. Gem Load Error is: Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes. 的錯誤。

新增 /etc/nginx/sites-enabled/your_project_name.conf

server {
  listen 80;
  server_name your_domain.com; # 還沒 domain 的話,先填 IP 位置

  root /home/deploy/your_project_name/public;
  # 如果是自動化部署,位置在 root /home/deploy/your_project_name/current/public;

  passenger_enabled on;

  passenger_min_instances 1;

  location ~ ^/assets/ {
    expires 1y;
    add_header Cache-Control public;
    add_header ETag "";
    break;
   }
}

以上設定包括設定Assets靜態檔案成為永不過期(Rails的Assets Pipeline會加上版本號,所以不需要擔心)、設定Passenger至少開一個Process。其中server_name your_domain.com請會換成你的domain。如果Domain name還沒註冊好,可以先用伺服器IP地址。但是如果你的伺服器上有多個Rails專案或網站,就必須用不同domain來區分。

如果有多個domain連到同一個伺服器,可以用空白區隔,例如:

server_name dureading.calvinchu.cc dureading.com www.dureading.com;

這樣三個 domain 都會連到同一個 Rails 了。

最後執行sudo service nginx restart便會啟用Nginx設定。如果之後你的Rails有任何修改要重新載入,但是並不想把Nginx整個重開,請在你的Rails應用程式目錄下執行touch tmp/restart.txt即可,這樣Passenger就會知道要重新載入Rails,而不需要重開Nginx

自動化佈署

決定應用程式伺服器之後,接下來我們來討論你要如何把程式佈署上去?最常見的作法,不就是開個FTP或用SFTP上傳上去不就好了?再不然SSH進去,從版本控制系統更新下來也可以。但是你有沒有想過這佈署的過程,其實是每次都重複一再執行的步驟(除非你佈署完之後,就不需要再繼續開發和升級),隨者時間的演進,這個過程常常會有各種客製的指令需要要執行,例如安裝設定檔、更新啟動某個Daemon、清除快取等等。因此,好的實務作法是自動化佈署這個動作,只要執行一個指令,就自動更新上去並重新啟動伺服器。這樣也可以大大避免漏做了什麼佈署步驟的可能性。

設定佈署腳本

CapistranoRails社群中最常使用的佈署工具。

首先,我們在本地端Gemfile中加上:

gem 'capistrano-rails', :group => :development
gem 'capistrano-passenger', :group => :development

資料庫用 MySQL 的話,記得再加上 gem "mysql2"

接著輸入bundle install

在你的Rails專案目錄下執行:

cap install

Enhance Capistrano with awesome collaboration and automation features? 請輸入 no

這樣就會產生幾個檔案,首先編輯Capfile在中段加入:

require 'capistrano/rails'
require 'capistrano/passenger'
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git

編輯config/deploy.rb,請替換以下的application名稱、git repo網址和deploy_to路徑

`ssh-add` # 注意這是鍵盤左上角的「 `」不是單引號「 '」
set :application, 'rails-exercise'

set :repo_url, 'git@github.com:ihower/rails-exercise.git'
set :deploy_to, '/home/deploy/rails-exercise'
set :keep_releases, 5

append :linked_files, 'config/database.yml', 'config/secrets.yml'
# 如果有 facebook.yml 或 email.yml 想要連結的話,也要加進來

append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system'

set :passenger_restart_with_touch, true
# ....

其中的ssh-add可以參考SSH agent forwarding 的應用的說明。

編輯config/deploy/production.rbexample.com換成伺服器的IP或網域,例如:

server '139.162.21.176', user: 'deploy', roles: %w{app db web}, my_property: :my_value

本機執行cap production deploy:check,就會自動登入遠端的伺服器,在登入的帳號下新建releasesshared這兩個目錄,releases是每次佈署的檔案目錄,shared目錄則是不同佈署目錄之間會共用的檔案。

有了shared目錄之後,我們還需要把設定檔放上去,請編輯:

  • (遠端) 編輯 shared/config/database.yml
  • (遠端) 編輯 shared/config/secrets.yml

這是因為我們不希望將資料庫的帳號密碼和cookie secret key也放進版本控制系統,所以會將存有正確帳號密碼的database.ymlsecrets.yml檔案預先放在伺服器的shared/config目錄下,自動佈署時會覆蓋過去。

執行rake secret產生的 key 放到遠端伺服器的shared/config/secrets.yml,範例如下(小心YAML格式的縮排規則,用兩個空格):

production:
  secret_key_base: xxxxxxx........

遠端伺服器設定好shared/config/database.yml,範例如下:

production:
  adapter: mysql2
  encoding: utf8mb4
  database: your_database_name
  host: localhost
  username: root
  password: your_database_password

如果是用PostgreSQL範例如下:

production:
  adapter: postgresql
  encoding: unicode
  database: your_database_name
  host: localhost
  pool: 25
  username: postgres
  password: your_database_password

到此終於可以部署了,執行cap production deploy就可以了。這會建立current這個目錄用symbolic link指向releases目錄下最新的版本。

修改 Nginx 設定

因為 Rails 專案目錄的位置跟上一章不一樣,所以請修改上一章的 /etc/nginx/site-enabled/your_project_name.conf

root /home/deploy/your_project_name/current/public;

重開 Nginx:

$ sudo service nginx restart

常見錯誤

錯誤: mysql2 not installed 的錯誤:

解法: server 上的 Gemfile 需要有 gem ‘mylsq2’,你需要 git push 你改好的 Gemfile 上去。 不過,修改 config/deploy.rb 和執行 cap production deploy 不需要先 commit&push,因為 server 上面其實不需要 capistrano 的 code,所以你可以改好 config/deploy.rb 後再 commit&push 即可。

錯誤: ActiveRecord::NoDatabaseError: Unknown database ‘rails-exercise’

解法: server 上的 shared/config/database.yml 設定錯了,這個資料庫名字要跟 mysql -u root -p 建立新資料庫時一樣。

錯誤: 更新Capistrano後,佈署時出現

cap aborted! Capfile locked at 3.3.3, but 3.4.0 is loaded 解法: 怎麼解?因為Capistrano鎖版本 把config/deploy.rb lock ‘3.3.3’ 改成 lock ‘>=3.3.3’

常用指令整理

要 deploy code,都是先 git push 到github上,再 cap production deploy

如何 SSH 登入?

  • (本機) ssh root@your_server_ip
    • su deploy 指令可以切換身分到 deploy
    • 如果修改 nginx 設定必須要 root 身分、操作你的 rails 專案則用 deploy 身分
  • (本機) 直接用 deploy 登入 ssh deploy@your_server_ip

當錯誤發生時,如何看遠端 log ?

  • 用 deploy 身分登入
  • cd ~/your_project/current
  • tail -n 500 log/production.log 這樣會顯示最後500行
  • 或是 tail -f log/production.log 這樣會一直掛著顯示

除了 Rails 的錯誤訊息,錯誤也有可能發生在 Nginx,這時候可以找 nginx log,位置在 /var/log/nginx//opt/nginx/logs 下,要用 root 身分才能看

在遠端如何進 rails console?

  • 用 deploy 身分登入,例如 ssh deploy@your_server_ip 或用 root 登入再切換身分 sudo su deploy
  • cd ~/your_project/current
  • bin/rails c production 或 bundle exec rails c production

如何在遠端跑 rake?

  • RAILS_ENV=production bin/rake db:seed

如何重開遠端 rails 而不重開 nginx ?

  • 在遠端 ~/your_project/current 下執行 touch tmp/restart.txt
  • 完全重開 nginx 的話 sudo service nginx restart

Passenger 監控指令

  • sudo passenger-status
  • sudo passenger-memory-stats

Nginx 設定最佳化

讓我們進一步最佳化 Nginx 設定,包括:

  • 設定檔案上傳可以到100mb,預設只有1Mb超小氣的,上傳一張圖片就爆了
  • 關閉 Passenger 和 Nginx 的版本資訊,減少資訊洩漏,增加駭客攻擊的難度
  • 自動調整Nginx使用多少process(跟主機有多少CPU核有關)
  • 最佳化gzip壓縮(可以大大減少網頁下載時間,瀏覽器都支持自動解壓縮),默認沒有壓縮 HTML,這裡設定要壓縮更多不同類型的檔案

編輯 /etc/nginx/nginx.conf

worker_processes  auto;
events {
 worker_connections  4096;
 use epoll;
}

http {

    passenger_show_version_in_header off;
    server_tokens       off;

    client_max_body_size 100m;

    gzip                on;
    gzip_disable        "msie6";
    gzip_comp_level    5;
    gzip_min_length    256;
    gzip_proxied       any;
    gzip_vary          on;
    gzip_types
      application/atom+xml
      application/javascript
      application/x-javascript
      application/json
      application/rss+xml
      application/vnd.ms-fontobject
      application/x-font-ttf
      application/x-web-app-manifest+json
      application/xhtml+xml
      application/xml
      font/opentype
      image/svg+xml
      image/x-icon
      text/css
      text/xml
      text/plain
      text/javascript
      text/x-component;

  # .... 其他不用改
}

修改完記得重開 Nginx。

安裝第三方服務

例外錯誤監控

雖然我們努力避免,但總是程式總有出錯的時候,一個上Production的專業 Rails app 絕不會痴痴地等待使用者告訴你網站炸了,而是要能夠主動通知及紀錄下這個錯誤例外(exception),好讓我可以 trace error、fixed bug 甚至在發生錯誤沒多久就可以通知苦主發生了什麼事情。

最基本我們可以安裝Exception Notifier,這個套件會在發生例外時寄 email 通知你(們)。

或是使用第三方服務,例如:

這些第三方服務可以在網站發生例外錯誤的時候自動將錯誤訊息收集起來,並且提供了還蠻不錯的後台可以瀏覽,還可以統計及追蹤例外處理的情況。免費的方案對於小網站就很夠用,非常推薦使用。

網站 Uptime 監控

以下這些第三方服務,可以每隔幾分鐘檢查你指定的 URL 是否正常回應(不需要額外安裝Gem),如果連不上可以透過 E-mail 通知你。免費的方案就夠用了,如果需要簡訊通知或增加檢查頻率,則需要付費。

網站效能監控

以下這些第三方服務,會紀錄監控網站程式的效能,例如網站的回應速度,協助你分析哪些部分需要做最佳化改善:

Log 收集器

當你有有多台伺服器時,會希望有個地方能夠集中所有的 Log,這樣要查時才方便:

Linux 主機安全性加強

一些基本防護措施:

  • 另開一個使用者有 sudo 權限,然後關閉 root 遠端可以登入
    • sudo adduser your_personal_account 新增自己個人帳號
    • sudo visudo 加上 your_personal_account ALL=(ALL:ALL) ALL 給予你自己有 sudo 權限
    • 把自己的 public key 放進 ~/.ssh/authorized_keys
  • 設定 root 帳號不可以 SSH 登入
    • 編輯 sudo vi /etc/ssh/sshd_config 設定 PermitRootLogin no
    • (optional) 可以不允許密碼登入,只能用 public key 登入: PubKeyAuthentication yesPasswordAuthentication no
  • 設定防火牆 iptable 只允許 80, 443, 22 port。直接操作底層的 iptable 步驟比較複雜,可以用 ufw 這個工具:
    • sudo apt-get install ufw
    • sudo ufw default deny
    • sudo ufw allow 22
    • sudo ufw allow 80
    • sudo ufw allow 443
    • sudo ufw enable
    • sudo ufw status
    • sudo ufw insert 1 deny from 要ban掉的IP
    • sudo ufw reload
  • 安裝 fail2ban,自動 ban 掉亂試密碼的 ip
    • https://www.digitalocean.com/community/tutorials/how-to-protect-ssh-with-fail2ban-on-ubuntu-12-04
  • 關閉 Nginx 不可以用 IP address 瀏覽,一定需要用 domain name。這樣可以避免無差別掃 IP 試探攻擊,如果你有使用 cloudflare 的話,也可以確保流量一定經過 cloudflare 防火牆:

    server {
     # ....
     server_name www.your_domain.com;
      if ($host != $server_name) {
          return       444;
      }
     # ....
    

整理 Log 檔案

網站持續運作,log目錄下的production.log可是會越長越肥,因此需要定期整理備份,這裡有幾種方法,一種是修改config/environments/production.rb的設定:

config.logger = Logger.new(config.paths["log"].first, 'daily') # 或 weekly,monthly

或是

config.logger = Logger.new(config.paths["log"].first, 10, 10*1024*1024) # 10 megabytes

另一種是用Linux內建的logrotate工具,請參考 使用 logrotate 定期整理 Rails Log 檔案

例如新增 /etc/logrotate.d/rails 檔案,內容如下:

/home/deploy/dojo/shared/log/*.log {
  monthly
  dateext
  missingok
  rotate 65535
  notifempty
  copytruncate
}

/var/log/nginx/*.log {
  monthly
  dateext
  missingok
  rotate 65535
  notifempty
  copytruncate
}

Recipe: 安裝 SSL 憑證和 HTTP/2

HTTP/2 對於網站效能 Page load time 有顯著的幫助,前因後果詳見更快更安全: 每個網站都應該升級到 HTTP/2一文。

安裝 SSL 憑證有幾種方式:

方法一:購買憑證

網路上有很多家廠商在賣 SSL 憑證,例如 https://www.namecheap.com

  • 產生 CSR request 憑證簽章要求
  • openssl req -new -newkey rsa:2048 -nodes -keyout staging.key -out staging.csr
    • 其中 Common Name 必須是你的 domain name,例如 exercise.ihower.tw。如果是 wildcard certificate 則用 *.ihower.tw
    • 最後的 password 可以不填,填的話之後每次 server 啟動需要打密碼
    • 這會產生兩個檔案 staging.csr 和 staging.key,後者是你的私鑰
  • 將 .csr 申請憑證檔案丟給憑證機構做申請,會拿到 .crt key
  • 過程會需要認證你真的擁有這個 domain,通常會要求你設定一個特別的 CNAME
  • 也可以自己簽發,只是瀏覽器會警告
    • openssl x509 -in staging.csr -out staging.crt -req -signkey staging.key -days 365
  • 買 COMODO cert 的話,請參考 https://support.comodo.com/index.php?/Default/Knowledgebase/Article/View/789/0/certificate-installation-nginx
    • 會需要認證你擁有此網址,通常方法是 1. 設定某個 CNAME 或 2. 寄信給 admin@your_domain,認證成功後,就可以下載憑證 .crt 檔案
    • cat your_domain_name.crt COMODORSADomainValidationSecureServerCA.crt COMODORSAAddTrustCA.crt > ssl-bundle.crt
    • 或 cat your_domain_name.crt your_domain_name.ca-bundle > ssl-bundle.crt
  • 將憑證 .crt 和私鑰 .key 放到 /opt/nginx/ 下,新加 vhost 支援 SSL:
  server {
       listen       443 ssl;
       ssl on;
       ssl_certificate       /opt/nginx/staging.crt;
       ssl_certificate_key    /opt/nginx/staging.key;
       server_name exercise.ihower.tw;

       root /home/deploy/rails-exercise/current//public;
       passenger_enabled on;

       passenger_min_instances 1;
       server_tokens       off;

       location ~ ^/assets/ {
         expires 1y;
         add_header Cache-Control public;
         add_header ETag "";
         break;
       }
  }

如果只支援 SSL 連線,可以將所有 HTTP 連線重導到 HTTPS

  server {
    listen 80;
    server_name exercise.ihower.tw;
    server_tokens off;
    location / {
      return 301 https://$host$request_uri;
    }
  }

方法二:使用 Let’s Encrypt 免費憑證

Nginx 設定使用 HTTP/2

在 Nginx 上設定好 SSL 後,就可以啟用 http2 了。請編輯 nginx vhost 設定檔案,修改成 listen 443 ssl http2;

方法三:使用 CloudFlare 做 Reverse Proxy

CloudFlare 有提供免費憑證,透過 Flexible SSL 模式,可以讓終端使用者到 CloudFlare 是加密連線,而你的伺服器不需要安裝。

Recipe: 設定 Staging 伺服器

除了 Prodcution 正式網站之外,通常我們還會另外部署一個叫做 Staging 的網站,用途是拿來做人工測試。Rails 的行為在本地開發和跑在伺服器上,常常有很多不一樣的地方,例如 Asset Pipeline 在開發時不會合併在一起,實際部署後才會編譯合併,因此有可能本地看起來正常,部署後卻壞掉的情況。這時候有一台 Staging 進行最後的人工測試,就非常有幫助,降低直接就部署正式網站的風險。

Staging 最好是用不同台伺服器,資料庫也是完全分開的,跟 Production 網站互不影響。

安裝的步驟如下:

  • 新增 config/environments/staging.rb 環境設定,內容同 config/environments/production.rb
  • 新增 config/deploy/staging.rb,內容同 config/deploy/production.rb,佈署的 Server IP 如果不同台伺服器就改掉
  • Server 上新增 /etc/nginx/sites-enabled/staging.conf 設定檔,內容跟 production 用的 nginx 設定檔一樣,除了 需要加一行 rack_env staging; 因為預設是 rack_env prodcution;
  • 如果 staging 和 production 共用同一台 server 的話,網址(domain name)和佈署目錄得要不一樣:
    • 上述的 nginx vhost 設定裡面 1. server_name 的網址 和 2. root 目錄位置要改
    • 將專案本來 config/deploy.rb 裡面的 set :deploy_to, '/home/deploy/shopping' 這一行設定搬到 config/deploy/staging.rbconfig/deploy/production.rb 並且改成不同目錄
  • 把上述變更 Push 到 Github
  • 執行 cap staging deploy:check
  • 到 server 上新增 shared/config/database.ymlsecrets.yml 設定檔案,注意 yml 內第一層 Key 要改成 staging。如果有其他 yml 設定也需要一併設定,例如 facebook.ymlemail.yml
  • 在 server 上新增 staging 用的資料庫
    • mysql -u root -p
    • CREATE DATABASE shopping_exercise_staging CHARACTER SET utf8;
  • 執行 cap staging deploy
  • 在 Server 上重開 Nginx sudo service nginx restart

Recipe: 為 staging 加上 HTTP Basic Authentication

用途: 簡單保護 staging 伺服器防止外人看到,特別是 Google 很厲害的會爬你的網頁!

編輯 Nginx 的 vhost 設定,加上

auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;

編輯 /etc/nginx/.htpasswd,內容如下

your_account:{PLAIN}your_password

其中your_accountyour_password換成你想要的帳號密碼即可。

Recipe: 為 staging 加上 E-Mail 攔截機制

在網站營運一段時間之後,我們很可能會不定期複製 production 的資料到本機 development 或 staging 上,這樣可以有更逼真的開發環境和測試環境。

但是這時候就要非常小心一些操作,因為資料是真的,所以不能真的做出通知使用者的行為,例如 E-Mail 寄送。

一個最保險的解決方案是,如果在非 production 的環境,一律檢查收件人的 email,如果發現不是我們測試的用戶,就在寄出時攔截轉寄。這樣就可以避免杯具發生。

Rails ActionMailer 內建支援這樣的機制,透過 register_interceptor 方法,我們可以不需要修改每支寄信的程式,只需要註冊攔截器即可:

新增 lib/email_interceptor.rb 檔案,內容參考 https://gist.github.com/ihower/145f96a0f2f0e56653cc506f13c863c5 可自訂條件,本例中是 email 收件人只要沒有 alphacamp 字串,就轉給 engineering@alphacamp.co

新增 config/initializers/email.rb 在 Rails 啟動時,如果是非 production mode 就載入:

unless Rails.env.production?
  puts "Enable email interceptor"
  require 'email_interceptor'
  ActionMailer::Base.register_interceptor(EmailInterceptor)
end

Recipe: 如何匯入匯出資料庫

MySQL 從本機匯出,在伺服器上匯入

  • 在本機匯出資料庫:
    • mysqldump -u root your_db_name > your_db_name.sql
  • 壓縮這個檔案:
    • gzip your_db_name.sql
  • 上傳到遠端伺服器 deploy 帳號的家目錄下:
    • scp your_db_name.sql.gz deploy@your_server_ip:~/
  • 登入遠端伺服器:
    • ssh deploy@your_server_ip
  • 解壓縮:
    • gunzip your_db_name.sql.gz
  • 砍掉現有的資料庫,新增一個空的資料庫 (或是不砍舊的資料庫,新增一個不一樣名字的空資料庫,修改 database.yml 換個資料庫名字,匯入完最後再重開 rails)
    • mysql -u root -p
    • DROP DATABASE your_db_name;
    • CREATE DATABASE your_db_name CHARACTER SET utf8;
  • 匯入資料庫:
    • mysql -u root your_db_name < your_db_name.sql

MySQL 從伺服器上匯出備份,在本機匯入

  • 登入遠端伺服器:
    • ssh deploy@your_server_ip
  • 在伺服器端匯出資料庫:
    • mysqldump -u root your_db_name -p > your_db_name.sql
  • 壓縮這個檔案:
    • gzip your_db_name.sql
  • 下載回本機
    • scp deploy@your_server_ip:~/your_db_name.sql.gz ./
  • 砍掉現有的資料庫,新增一個空的資料庫 (或是不砍舊的資料庫,新增一個不一樣名字的空資料庫,修改 database.yml 換個資料庫名字,匯入完最後再重開 rails)
    • mysql -u root -p
    • DROP DATABASE your_db_name;
    • CREATE DATABASE your_db_name CHARACTER SET utf8;
  • 匯入資料庫:
    • mysql -u root your_db_name -p < your_db_name.sql

PostgreSQL

請參考 https://ihower.tw/blog/archives/8152

其他補充

上下傳檔案,除了用 scp 指令,也可以用 FTP 軟體,例如 Cyberduck,通訊協定選 SFTP 即可。

Recipe: 調整本機 SSH 設定

SSH 登入快捷設定

本機編輯 ~/.ssh/config,內容如下

ControlMaster auto
ControlPath /tmp/ssh_mux_%h_%p_%r

Host your_project_name
HostName 192.168.1.115
User deploy

這樣執行 ssh your_project_name就會登入了,而且開多個視窗登入時,會沿用之前的連線,加快登入速度。

Recipe: 主機自動化備份

使用 https://github.com/backup/backup 這個 Ruby 工具可以幫助我們設定好自動備份。

  • Example config: https://gist.github.com/ihower/5a28624f9420fb9a7c49 這會備份整個 mysql 資料庫,打包壓縮整個 /srv 目錄,上傳到指定的 S3 bucket,最後寄送 email 通知完成!
  • 在 Server 上執行 crontab -e 可以編輯例行性工作排程(crontab),以下是一個每日凌晨 4:30 自動執行的範例:

     30 4 * * * /bin/bash -l -c '/usr/local/bin/backup perform -t my_backup -c /home/ihower/Backup/config.rb'
    

》回到頁首