Deploy Rails app to AWS EC2

前言

這篇生了很久終於生出來了,在踩了不少雷邊做邊學Linux指令下終於完成了,一開始是在growthschool的EC即戰力班的附件教學裡有教怎麼deploy在linode,但後來跟著一些步驟在aws EC2上面做發現deploy在aws EC2跟linode還真的差的蠻多的,像是在linode所安裝的環境不管是哪個使用者都找得到,但到了aws EC2就不行了,因為aws EC2是根據使用者來安裝的,也就是A使用者已經安裝好的環境B使用者不能用,還有linode的user可以不用有admin權限一樣可以deploy但aws不行,另外aws對於權限的要求也是非常嚴謹的,在踩了不少雷終於成功Deploy之後就在讀書會上分享了怎麼Deploy的教學,但還是覺得要把這些東西記錄下來,漸少後人踩雷的機會,希望這篇文章會對一些想要deploy rails app在aws上有些幫助。
主要是用aws EC2 ubuntu server + Passenger + Nginx + capristrano 來做

step1.申請aws ec2 ubuntu server

1-1

選擇server主機位置,這邊我是選擇日本因為機房離台灣比較近速度也會比較快,但日本位置的主機好像貴了一點點就是,然後在service的地方選擇EC2。

1-2

Lancch a new instance 建一個新的主機。

1-3

選擇Ubantu Server。

1-4

選擇免費方案。

1-5

Configure Instance 使用預設設定。

1-6

Add Storage 使用預設設定。

1-7

幫主機設一個名字,只是辨別用設什麼名字都可以。

1-8

Configure Security Group 設定防火牆,之前沒申請過的話就可以建立一個新的防火牆設定並且給他一個名稱,有的話選已經設定好的防火牆也可以。


幫這個防火牆加入一個允許HTTP的設定。

1-9

如果要使用免費的方案的話,就檢查一下設定看有沒有出現需要付費的選項,沒有的話就按launch到下一步。

1-10

建立登入金鑰並且下載到本機,這邊我建議是下載到本機使用者的跟目錄下,這樣終端機打開就找得到金鑰不用再去找路徑。

1-11

查看一下主機資訊


確認Instance states亮綠燈顯示runnibg就代表主機設定完成,現在已經有一台虛擬主機可以用了。

step2 使用金鑰登入主機

2-1 設定金鑰權限

在step1-11的第二張圖片點選剛剛建立好的主機後再選上面的connect按鈕就可以看到登入資訊。


打開iTerm或是終端機,然後輸入下面的指令更改金鑰權限,SSH_KEY_NAME就自己更換成在step1-10下載的金鑰名稱

chmod 400 SSH_KEY_NAME.pem

2-2登入主機

在connect裡可以看到Example的地方他有教你怎麼登入你的instance,但這個指令實在太長了所以你可以改用以下方式登入。
一樣SSH_KEY_NAME請換成自己金鑰的名稱,@的前面是使用者名稱,預設就是ubuntu,然後@後面的PUBLIC_IP就換成在step1-11第二張圖看到的public ip,然後不要傻傻地照著圖片上的ip打嘿,請輸入你自己主機的public ip。

ssh -i "SSH_KEY_NAME.pem" ubuntu@PUBLIC_IP
//Example:ssh -i "aws-key.pem" ubuntu@11.111.111.11

第一次登入時會顯示下面的訊息這時候打yes就好了。


登入之後會看到一些系統的訊息,然後在iTerm的地方也會顯示為ubuntu@11-111-111-11,這樣就代表登入成功了

step3 安裝deploy rails所需要的os及packages

step3-1 ubuntu系統更新

sudo apt-get update
sudo apt-get upgrade
sudo apt-get autoremove

step3-2 設定時區

sudo dpkg-reconfigure tzdata



step3-3 安裝 utf-8 的語系

sudo locale-gen zh_TW zh_TW.UTF-8 zh_CN.UTF-8 en_US.UTF-8

step3-4 安裝 MySQL

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

mysql的預設使用者就是root,然後這邊需要設置mysql的密碼,在deploy的時候會需要用到,所以密碼要記起來。



安裝完成後可以輸入下面的指令,然後打密碼登入剛剛安裝好的sql,有出現下面的畫面就代表安裝並且登入成功,然後再輸入exit跳出就好了。

mysql -u root -p

3-5 安裝需要的套件 ( 含 git )

sudo apt-get install build-essential git-core curl libssl-dev libreadline5 libreadline-gplv2-dev zlib1g zlib1g-dev libmysqlclient-dev libcurl4-openssl-dev libxslt-dev libxml2-dev libffi-dev git

3-6 安裝rvm來管理ruby & rails

輸入下面指令安裝,安裝完成之後輸入exit離開,然後再登入一次ubuntu主機。

\curl -sSL https://get.rvm.io | bash

3-7 用 rvm 安裝 ruby 2.2.2

rvm install 2.2.2
安裝好後輸入rvm lsitruby -v確認安裝好的ruby。

3-8 安裝 ImageMagick

sudo apt-get install imagemagick

3-9 安裝 Passenger

gem install passenger

3-10 用 Passenger 安裝 Nginx

3-10-1 輸入下面指令安裝Nginx

rvmsudo passenger-install-nginx-module

3-10-2 按Enter繼續

3-10-3 選 Ruby 按 enter 繼續

3-10-4 這邊應該會出現記憶體不足1G的警告,不過我們也只有1G可以用所以就按Enter繼續安裝。

3-10-5 輸入 1. Yes: download

3-10-6 選擇: 安裝到 /opt/nginx (直接按 enter),這邊會花一點時間

3-10-7 按 enter 完成 passenger-nginx-module 的安裝

3-10-8 看到這畫面就代表安裝完成

3-11 安裝 Nginx init script

git clone git://github.com/jnstq/rails-nginx-passenger-ubuntu.git
sudo mv rails-nginx-passenger-ubuntu/nginx/nginx /etc/init.d/nginx
sudo chown root:root /etc/init.d/nginx

3-12 設定 Nginx conf

輸入sudo vim /opt/nginx/conf/nginx.conf
這邊是用vim來編輯設定檔,沒有用過vim的人可能會有點不習慣啊,我也是第一次用還邊查linux指令邊修改,這邊跟大家說幾個會用到的指另。
i進入編輯模式。
Esc離開編輯模式。
在不是編輯模式下輸入:wq指的是寫入並且儲存然後離開。
在這邊要做的修改是
在server的block裡加入root /home/USER_NAME/PROJECT_NAME/public;以及passenger_enabled on;
設定root這行為了要指定首頁去哪個資料夾找
root /home/USER_NAME/PROJECT_NAME/public;。
USER_NAME是指要deploy時登入的使用者名稱,這邊可以先設定ubuntu。
PROJECT_NAME 要deploy的專案名稱。
小提醒:不要忘記/public一定要加,這樣才找得到網頁,然後也不要忘記結尾的分號!
然後再用#註解掉下面location block。

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;
                root /home/buntu/PROJECT_NAME/public; #(加這一行)
          passenger_enabled on; #(加這一行)
        #charset koi8-r;

        #access_log  logs/host.access.log  main;
                
        (用#註解掉下面location block)
       # location / {
           # root   html;
            #index  index.html index.htm;
        #}

        #error_page  404              /404.html;

3-13 檢查 Nginx 設定有無異常

輸入

sudo service nginx configtest

有出現下面的訊息代表設定成功。

3-14 將 Nginx server 重開機

sudo service nginx restart

3-15 測試 Nginx 的機制 ( 可跳過 )

3-15-1 建立public資料夾&index.html

sudo mkdir public
sudo vi public/index.html
index.html
<html><head><title>nginx test</title></head>
<body bgcolor="white">
<center><h1>hello world</h1></center>
<hr><center>for testing ubuntu setting</center>
</body></html>

3-15-2 修改nginx.conf並且重新啟動Nginx

修改nginx.conf並儲存。
sudo vim /opt/nginx/conf/nginx.conf

server {
        listen       80;
        server_name  localhost;
    -    #root /home/ubuntu/PROJECT_NAME/public;
    +   root /home/ubuntu/public;
        (以下略。。)

檢查有無錯誤
sudo service nginx configtest
重新啟動Nginx
sudo service nginx restart
在瀏覽器輸入public ip應該會看到hello world的畫面

3-16 安裝必要的gme

gem install bundler
gem install execjs
sudo apt-get install nodejs

step4 備份主機(可跳過,但建議做)

在AWS EC2可以製作AMIs Image作為備份,這樣如果主機被搞爛的時候就還有個備份可以做回復,現在做到這步驟該裝的東西也差不多都裝好了就可以來備份一下instance了。下面的影片是教說如果金鑰遺失要怎麼用備份好的image重新建立一個新的instance,然後再產生一個新的金鑰,因為裡面有教怎麼做備份,所以就直接看影片吧。
SSH into Amazon EC2 Instance Without Your PEM File

step5 用本機ssh key做登入

這邊要說是如何用本機所產生的ssh key登入aws ec2 ubuntu server。

## 5-1 建立新user 這邊建立一個叫做deploy的user。 `sudo adduser deploy` ![step5-1.png](http://user-image.logdown.io/user/14290/blog/13503/post/701033/8vjtDphxQEK8a3GmGuSj_step5-1.png) ##5-2 切換至新user 前面的使用者名稱會從ubuntu變成deploy。 `sudo su deploy ` 切換使用者後還會停留在ubuntu的目錄下所以要輸入`cd`回到deploy的目錄下。 之後再輸入`pwd`確定現在位置,如果顯示的是/home/deploy就是對的位置。 小提醒:這邊很重要小心不要弄錯位置,不然會無法用新的使用者登入的。 ![step5-2.png](http://user-image.logdown.io/user/14290/blog/13503/post/701033/2pW16oHwQGKCaxPbfbHO_step5-2.png)

5-1 建立.ssh資料夾及authorized_keys檔案

如果要用本機的ssh key登入的話,在aws ec2他有規定要把ssh key放在.ssh/authorized_keys這個位置下,所以我們要建立這個檔案,並且給權限。

mkdir .ssh
chmod 700 .ssh
touch .ssh/authorized_keys
chmod 600 .ssh/authorized_keys

5-2 在本地建立ssh key

他的運作原理是這樣的,在本機建立的ssh key會有id_rsa跟id_rsa.pub這兩個檔案,然後我們把id_rsa.pub這個檔案的ssh key內容複製到server上的authorized_keys檔案裡,這樣登入的時候就會各自拿公鑰跟私鑰來比對。
先開一個新的iTerm視窗,並且輸入more ~/.ssh/id_rsa.pub,看有沒有之前已經建立好的ssh key,有的話把這串複製起來,記得從sh-rsa開始複製到最後。


如果沒有值的話就改輸入ssh-keygen -t rsa建立新的key。

5-3 把本地的ssh key內容複製到server上的authorized_keys

先回到原本的ubuntu@ip的視窗,然後輸入sudo vi .ssh/authorized_keys,應該會看到裡面原本就有值,上面的那一個是用aws所給的金鑰登入時會比對的public key所以那一段不能刪除,刪掉的話以後就不能用他的金鑰登入了,這邊我們把剛剛在本機複製的ssh key貼在原本金鑰的下面並且儲存離開,可以輸入cat .ssh/authorized_keys看有沒有值有的話就是對的。

5-4登出重新用剛剛設定好的ssh key登入

輸入exit登出回到本機上,並用下面指令重新登入,PUBLIC_IP自行換掉

ssh ubuntu@PUBLIC_IP

5-5 修改本地.ssh/config

經過上面的步驟我們已經不需要用aws給我的key登入而是用自己本地端的ssh key做登入,省了還要輸入key的動作,但在登入的時候我們還是要輸入後面的public ip,這個步驟就是要說怎樣連後面的ip都不用輸入就可以登入。
先在本地的iTerm輸入sudo vi ~/.ssh/config,然後照下面修改config檔
USER_NAME改成要登入instance的使用者名稱,這邊就是ubuntu
PUBLIC_IP改為instance的public ip

Host USER_NAME
    user         USER_NAME
    HostName     PUBLIC_IP
    Port         22
    
 #--------example----------------
 Host ubuntu
    user         ubuntu
    HostName     11.111.111.11
    Port         22
 

修改完後試著用ssh USER_NAME(ssh ubuntu)登入,如果可以登入的話就成功囉,這樣是不是省事許多。

5-6 用aws金鑰簡單登入的方法

如果我不想這麼麻煩用本地的ssh key登入,想直接用aws給我的金鑰登入,但我又想省事不用輸入金鑰名稱跟public ip可以嗎?
答案是可以的!你可以再加入一個屬性欄位叫做IdentityFile,然後這個地方要輸入你aws金鑰的位置,這樣就可以做到用aws金鑰登入又省事的方法~

Host USER_NAME
    user         USER_NAME
    HostName     PUBLIC_IP
    Port         22
    IdentityFile AWS_KEY.pem
    
 #--------example----------------
 Host ubuntu
    user         ubuntu
    HostName     11.111.111.11
    Port         22
    IdentityFile awskey.pem
 

應該會有人有疑問說為什麼不一開始就這樣做?這樣就不用做step5-1~5-5的步驟了
應該是說這邊有兩個方法,看你要用什麼方式登入都可以,用本地的key登入可以避免萬一金鑰真的不見無法登入的情況。

step6 在ubuntu instance建一個 ssh-key

在用capristrano做自動化deploy時,我們需要從 ubuntu instance 上可以 git clone github 上的 source code,專案下來然後自動化的deplyo到server上,( 如果是用 https 的 git 路徑會被要求輸入帳號密碼 ),所以需要在 deploy 這個帳號裡設定一組 ssh-key 給 github, 可以不用詢問帳號密碼就能夠 clone。
先登入ubuntu server,然後輸入ssh-keygen產生key,之後再輸入more ~/.ssh/id_rsa.pub然後複製key。

step6-1 將 ssh-key 放到 github 上

https://github.com/settings/keys去設定


設定好之後在iTerm輸入ssh -T git@github.com看有沒有連線成功。

step7 修改rails專案,並且push到github上

7-1 修改rails專案

我們不希望機密的資料上傳至github上所以要先把有敏感資料的檔案放在.gitignore檔案裡,這樣commit的時候才不會一起push上去。

 /config/database.yml
 /config/secrets.yml

如果已經不小心push上去的話可以打git rm --cached FILE_NAME,這樣在做push之後就會把指定的檔案給移除了。
修改gemfile

- gem 'sqlite3'
+ gem 'sqlite3', group: :development

- # gem 'therubyracer', platforms: :ruby
+ gem 'therubyracer', platforms: :ruby

+ group :production do
+   gem "mysql2"
+ end

然後bundle install,之後再commit上github

step8 clone your project form github to your ubuntu instance

step8-1 登入主機並從github clone專案下來

先登入instance

git clone GITHUB_PROJECT_URL

#GITHUB_PROJECT_URL自行至換掉github的專案網址

之後輸入ls看一下有沒有看到專案的資料夾。

8-2 bundle install

cd進到專案資料夾裡然後輸入bundle install --path vendor/bundle安裝所需要的gem

Bundle complete! 23 Gemfile dependencies, 88 gems now installed. Bundled gems are installed into ./vendor/bundle.

8-3 從本機複製databease.yml & secrets.yml到server並修改

在本機先cd到專案資料夾底下然後用scp指令把本地檔案傳到server上,這邊要記得要做本地的ssh config設定才能照這樣輸入
詳細的scp用法可以看這兩個網站
鳥哥的 Linux 私房菜
凍仁的筆記-scp - 藉由 ssh 的遠端檔案傳輸指令

scp ubuntu config/database.yml ubuntu:~/PROJECT_NAME/config
scp ubuntu config/secrets.yml ubuntu:~/PROJECT_NAME/config

之後登入主機並且進入專案資料夾去修改這兩個檔案
修改databease.yml

sudo vi config/databease.yml
config/databease.yml
production:

  adapter: mysql2

  encoding: utf8

  database: PROJECT_NAME

  pool: 5

  username: root      # 在前面章節安裝 mysql 的時候所設定的名稱



  password: "123456"  # 在前面章節安裝 mysql 的時候所設定的密碼



  socket: /var/run/mysqld/mysqld.sock

修改secrets.yml

sudo vi config/secrets.yml
config/secrets.yml
development:

  secret_key_base: (一大串密碼)


test:

  secret_key_base: (一大串密碼)


# Do not keep production secrets in the repository,

# instead read values from the environment.

production:

  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

  # 將production的secret_key_base改成同檔案裡面的其他密碼即可

8-4 執行 production 的資料庫設定

RAILS_ENV=production rake db:create # 建立 production database
RAILS_ENV=production rake db:migrate # 建立 production database 欄位

執行 rake assets:precompile

RAILS_ENV=production rake assets:precompile # 建立 production assets

如果在做rake有遇到下面的訊息的話,更新rake
Gem::LoadError: You have already activated rake 10.4.2, but your Gemfile requires rake 10.5.0. Prepending bundle exec to your command may solve this.

gem update rake #在專案資料夾底下

8-5 檢查是否 deploy 成功

確認nginx.conf有加root的路徑,詳細可以回去看step3-12 設定 Nginx conf

sudo vi /opt/nginx/conf/nginx.conf
nginx.conf
server {
        listen       80;
        server_name  localhost;
        root /home/ubuntu/PROJECT_NAME/public;
        (略)

之後

sudo service nginx configtest 檢查設定檔有沒有錯誤

sudo service nginx restart 重開 nginx, 讓設定檔生效

最後直接開瀏覽器輸入你的public ip應該就會看得到你的網站了。

step9 用 capristrano 自動化 deploy

9-1 在 ubuntu 的專案建立 shared/config 資料夾

這邊建立shared資料夾的原因是我們不希望把機密檔案傳到git上面去,所以clone下來的專案當然也沒有secrets跟database的檔案,所以我們要建立一個shared資料夾先把機密的檔案從本機上傳到server,然後在capristrano自動化deploy的時候他會從shared資料夾copy這些檔案到專案資料夾做deploy。

mkdir shared
mkdir shared/config

因為剛剛已經在step8的時候已經由從本機裡把檔案傳到server上的專案裡,所以就可以直接從config資料夾裡copy到shared/config裡

cd PROJECT_NAME
cp config/database.yml shared/config/
cp config/secrets.yml shared/config/

9-2 為本地端專案安裝capristrano 3.4

回到本機上並且打開專案的 Gemfile

Gemfile
 group :development do
   gem "capistrano", "~> 3.4"
   gem "capistrano-rvm"
   gem "capistrano-rails"
 end

然後bundle installcap install
cao install會產生下面的檔案
Capfire
config/deploy.rb
config/deploy/production.rb
config/deploy/staging.rb
接下來要修改這些檔案。

9-3 設定 config/deploy.rb

config/deploy.rb
# config valid only for current version of Capistrano

 lock '3.4.0'
 
# 你的 application name


set :application, 'PROJECT_NAME'

# 你的 git url

set :repo_url, 'https://github.com/USER_NAME/PROJECT_NAME.git'#github專案網址


# deploy 的資料夾位置 (prodution)

set :deploy_to, "/home/ubuntu/PROJECT_NAME"
 # Default value for :linked_files is []

# git clone 完成後會從 shared 資料夾 copy 過去的檔案

set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')
 
 # Default value for linked_dirs is []

# git clone 完成後會從 shared 資料夾 copy 過去的資料夾

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

9-3 設定 Capfile

把下面的這幾個require的註解打開

Capfile
...()

require 'capistrano/rvm'
require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'

...()

9-4 設定 config/deploy/production.rb

config/deploy/production.rb
role :app, %w{ubuntu@PUBLIC_IP}
role :web, %w{ubuntu@PUBLIC_IP
role :db,  %w{ubuntu@PUBLIC_IP}

9-5 執行 deploy:check

cap production deploy:check
如果有出現failed的error message的話就看一下是什麼問題debug一下。

9-6 執行 deploy 程序

cap production deploy

9-7 修改 nginx.conf

用capristrano deploy的話,capristrano會再server的專案資料夾下再開一個current資料夾,並且把專案deploy到這個資料夾裡,所以要去修改 nginx config 的 root路徑
先登入到ubuntu server 然後輸入

sudo vim /opt/nginx/conf/nginx.conf

修改成

/opt/nginx/conf/nginx.conf
    server {
        listen       80;
        server_name  localhost;
        root /home/apps/PROJECT_NAME/current/public;#多加一個current的路徑
        passenger_enabled on;
    }

然後儲存重開nginx

sudo /etc/init.d/nginx restart

之後一樣開瀏覽器輸入public ip看網站吧。

以後專案有變動的話,只要push到 github(記得要push到master主線),然後在本地的專案直接輸入 cap production deploy 即可自動跑完 deploy

萬一新版本的專案炸掉,還可以輸入 cap production deploy:rollback 回溯到上個版本

production 端的專案資料夾根目錄會有三個資料夾
current <== 現在實際上線運作的版本
releases <== 你過去 deploy 的記錄 ( 預設存五個 , 最新版的 = current )
shared <== 你不想放在 git 上給人看到的檔案(例如 database.yml) / logs / 圖片 ... etc 的放置處,新版本 deploy 完後 capristrano 會自動 copy 一份過去到 current 上
現在根目錄除了這三個資料以外的檔案都沒有用到了,所以除了這三個資料夾的檔案外其他都可以刪除掉了。
覺得要一個個刪掉很麻煩的話也可以直接砍掉整個資料夾,然後再建立一個空的專案資料夾(記得在裡面要在建立shared資料夾),然後再從本地直接deploy上去就好。
另外這三個檔案其實也不用上到git可以把它們放到gitignore裡

.gitignore
/config/deploy/production.rb
/config/deploy/staging.rb
/config/deploy.rb

參考資料:
Ruby on Rails 實戰聖經-網站部署
[AWS] 連結Amazon EC2 Server與ssh設定
遠端部署最佳實現
kutj,s blog-Chef with AWS EC2 - 1
Rails 佈署虛擬機使用 Ubuntu 14.04、Nginx、Passenger
Deploy Rails Application to AWS EC2(Ubuntu 14.04)
growthschool-(Mini Course) Deploy Rails Project to Linux Server
How to set up a new user on your Amazon AWS server
https://www.rails365.net/articles/shi-yong-capistrano-bu-shu-rails-ying-yong

comments powered by Disqus