炸蝦碎碎念。

[Notes, GNU/Linux, Open Source, Ruby on Rails, Computer Science, Archlinux]

[Note] 網站佈署環境小筆記

因為常常在要佈署網站時,有一些相似的流程,想說紀錄一下。這是個簡單的筆記,也許有遺漏的地方,請多多指出。

Linux Distribution

對於 Production Server ,我比較喜歡用穩定一點的 CentOS ,但是很多別人的伺服器都是 Ubuntu LTS ,所以基本上這個 Guide 算是通用。一般我的網站都是放在 Linode 新加坡的機器上。

使用的 Capistrano 相關套件

其實除了這段比較針對 Rails 之外,其他的部份不只是用 Ruby on Rails ,我也是用這個環境跟 Capistrano 佈署手上的 django Project 。

1
2
3
4
5
6
7
8
group :development do
  gem 'capistrano'
  gem 'capistrano-rails'
  gem 'capistrano-bundler'
  gem 'capistrano-rvm'
  gem 'capistrano3-puma', github: "seuros/capistrano-puma"
  gem 'capistrano-rake'
end

我用的 Solution 是:

  1. Web Server: nginx
  2. Rack Server: puma
  3. Ruby Version: RVM
  4. Database: PosrgreSQL

這篇的舉例是用 Ruby on Rails 為主,之前有碰到一點點 Python ,會另外介紹。不過大部份應該是共通的。

設定 Deploy User 跟 SSH Keys

1
2
3
4
5
$ sudo useradd -m -s /bin/bash deploy
$ sudo su -l deploy # 切換到 deploy 使用者
$ mkdir .ssh
$ echo "[填入你要用來登入這個帳號的 public key(s)]" > .ssh/authorized_keys
$ ssh-keygen # 生成用來 pull git repository 的 deploy key ,一直按 enter 就好。

記得照這篇的說明把剛剛生成的 deploy key 的 .ssh/id_rsa.pub 的內容放上去。

這個 User 並沒有設定密碼,預設的 /etc/ssh/sshd_configPermitEmptyPasswords 應該是 no 保險起見可以檢查一下。這樣就只能透過 public key 驗證。通常我會有另外一個常用的可以 sudo 的 user 在機器上。

我喜歡把 App 都放在 /home/deploy 裡面,每個網站就直接拿網址當作資料夾名稱,若同網址有不同的 stage 用底線在後面加註。如: some-good-app.example.org, some-good-app.example.org_staging 。

關閉 SELinux

SELinux 是一個進階的安全模組,但是因為設定複雜、限制不少,一般的 Linux distribution 都是預設關閉的。但是 RedHat 系的 CentOS 及 Fedora 預設是把 SELinux 開啟的,可以參考這個把他關掉。45.2.7. Enable or Disable SELinux。慶幸的是 linode 的一開始就是關掉的。

Nginx 設定

網站設定檔路徑

設定的部份,一般來說會把機器上的各個 App 的設定檔放在 /etc/nginx/sites-available ,然後要 enable 的 App 呢,就利用指到到 sites-available 裡面的設定檔的 Symbol link ,然後放在 /etc/nginx/sites-enabled 。我們要做的是在 /etc/nginx/nginx.conf 裡 include sites-enabled 這個資料夾。

1
2
3
4
5
6
7
http {
    .....
    .....
    include /etc/nginx/conf.d/*.conf;
    # 加入這行:
    include /etc/nginx/sites-enabled/*;
}

設定 nginx 執行的 user

一般來說 nginx 是用 www-data 這個 user 來執行,我們要把他改成 deploy 。

就在 nginx.conf 的最前面,把 user 改成 deploy 。

1
user deploy;

Nginx 網站設定檔

再來就是針對我們要 deploy 的 App 的設定檔。這裡給一個有 https 的設定,跟 http 的設定範例。這個設定檔包含一些常見的設定。我還想弄把 static 跟 動態網域分開的,但是我目前還沒玩過,用過之後再來補上設定。

這些設定都是要放在 /etc/nginx/sites-available 底下,通常名字就一樣取網址+底線+ stage 。

要 enable 的站就像這樣做符號連結:

1
$ ln -s /etc/nginx/sites-available/some-good-app.example.org_staging /etc/nginx/sites-enabled/some-good-app.example.org_staging

https 的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 設定 upstream 路徑
upstream some-good-app {
        server unix:///home/deploy/some-good-app/shared/tmp/sockets/puma.sock;
}

# 強迫使用 https (如用 http 會自動重新導向至 https)
server {
        listen 80;
        server_name some-good-app.example.org
        return 301 https://$server_name$request_uri;
}

server {
        listen 443 ssl;
        server_name some-good-app.example.org
        root /home/deploy/some-good-app/current/public/;

        # 連到你 ssl certificate 的路徑,通常提供商都會教你怎麼弄這兩個檔案(可以下 keyword 像 comodo postive ssl nginx config)
        ssl_certificate /etc/ssl/nginx/some-good-app.example.org.crt;
        ssl_certificate_key /etc/ssl/nginx/some-good-app.example.org.key;

        # 強迫使用較新版本的加密方式
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
        ssl_prefer_server_ciphers on;

        location / {
                proxy_pass http://some-good-app;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header client_ip $remote_addr;
                proxy_set_header remote_ip $remote_addr;
                sendfile on;
        }

        # 檔案壓縮傳輸 
        gzip on;
        gzip_disable "msie6";
        gzip_http_version 1.1;
        gzip_proxied no-cache no-store private expired auth;
        gzip_min_length 500;

        # Log 位置
        access_log /var/log/nginx/some-good-app-access.log;
        error_log /var/log/nginx/some-good-app-error.log;

        # 讓 nginx 負責 static assets 
        location ^~ /assets/ {
                tcp_nodelay on;
                tcp_nopush on;
                sendfile on;
                sendfile_max_chunk 200m;
                expires 1w;
                gzip_static on;
                add_header Cache-Control public;
        }
}

http 的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
upstream some-good-app {
        server unix:///home/deploy/some-good-app/shared/tmp/sockets/puma.sock;
}

server {
        listen 80;
        server_name some-good-app.example.org
        root /home/deploy/some-good-app/current/public/;

        location / {
                proxy_pass http://some-good-app;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header client_ip $remote_addr;
                proxy_set_header remote_ip $remote_addr;
                sendfile on;
        }

        gzip on;
        gzip_disable "msie6";
        gzip_http_version 1.1;
        gzip_proxied no-cache no-store private expired auth;
        gzip_min_length 500;

        access_log /var/log/nginx/some-good-app-access.log;
        error_log /var/log/nginx/some-good-app-error.log;

        location ^~ /assets/ {
                tcp_nodelay on;
                tcp_nopush on;
                sendfile on;
                sendfile_max_chunk 200m;
                expires 1w;
                gzip_static on;
                add_header Cache-Control public;
        }

}

管理 Nginx 服務

1
$ systemctl start/stop/status/restart/reload nginx

如果設定檔有錯誤,在 status 裡會提到。

PostgreSQL 與 database.yml

安裝完後,可以直接啟動 PostgreSQL 的服務。

1
2
$ systemctl start postgresql
$ systemctl enable postgresql

Postgres 方便的是可以直接用 shell 來管理,要 supostgres 使用者。這裡先建立我們要用到的 user 。而且本機使用也可以不設定 db 密碼。

1
2
$ sudo su -l postgres
postgres@example $ createuser --createdb -s deploy

這樣一來, database.yml 就能方便的這樣設定,因為如果沒有設定 user 就會找跟 deploy user 一樣的 user ,在這裡就是 deploy 這個 user 。 在你的開發機上也許有其他相應的 user 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5

development:
  <<: *default
  database: good-app_development

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: good-app_test

staging:
  <<: *default
  database: good-app_staging

production:
  <<: *default
  database: good-app_production

Capistrano

安裝

1
2
bundle exec cap install
# 最後會有一個問你要不要用他服務的 選不要就好 XDD

他會幫你生成下列檔案(取自官方 Github 的 README):

1
2
3
4
5
6
7
8
9
├── Capfile
├── config
│   ├── deploy
│   │   ├── production.rb
│   │   └── staging.rb
│   └── deploy.rb
└── lib
    └── capistrano
            └── tasks

設定

Capfile

Capfile 是設定要用哪些 capistrano 相關的 package 。記得,這些 package 都要在 Gemfile 被 include 到。

他已經預設有一些範例了,這是我通常用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Load DSL and set up stages
require "capistrano/setup"

# Include default deployment tasks
require "capistrano/deploy"

# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
#   https://github.com/capistrano/rvm
#   https://github.com/capistrano/rbenv
#   https://github.com/capistrano/chruby
#   https://github.com/capistrano/bundler
#   https://github.com/capistrano/rails
#   https://github.com/capistrano/passenger
#
require 'capistrano/rvm'
# require 'capistrano/rbenv'
# require 'capistrano/chruby'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano/rake'
require 'capistrano/puma'
require 'capistrano/puma/workers' # if you want to control the workers (in cluster mode)
# require 'capistrano/passenger'

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

Deploy.rb

config/deploy.rb 是主要的設定檔。其實蠻容易理解的,把該設定的東西填進去就好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# config valid only for current version of Capistrano
lock '3.6.0'

set :application, 'some-good-app'
set :repo_url, 'git@github.com:joshua5201/some-good-app.git'

# Default branch is :master
# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

# Default deploy_to directory is /var/www/my_app_name
# set :deploy_to, '/var/www/my_app_name'

# Default value for :scm is :git
# set :scm, :git

# Default value for :format is :airbrussh.
# set :format, :airbrussh

# You can configure the Airbrussh format using :format_options.
# These are the defaults.
# set :format_options, command_output: true, log_file: 'log/capistrano.log', color: :auto, truncate: :auto

# Default value for :pty is false
# set :pty, true

# Default value for :linked_files is []
# append :linked_files, 'config/database.yml', 'config/secrets.yml'

# Default value for linked_dirs is []
# append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system'
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system', 'public/uploads')
set :puma_user, 'deploy'

# Default value for default\_env is {}
# set :default\_env, { path: "/opt/ruby/bin:$PATH" }

# Default value for keep\_releases is 5
# set :keep\_releases, 5

Stages

預設的 Rails 是有 development, test, production 等三個 stage ,但是一般我們都會再加一個 staging 的 stage 來在上 production 前測試, Capistrano 預設的也是這樣。所以我們要在 Rails 裡也加上這個 stage 。

1
cp config/environments/production.rb config/environments/staging.rb

剛剛我們設定的 database.yml 已經包含 staging 這個 stage 了。

每個 stage 在 config/deploy/ 裡都有對應的 .rb 的設定檔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# server-based syntax
# ======================
# Defines a single server with a list of roles and multiple properties.
# You can define all roles on a single server, or split them:

server 'some-good-app.example.org', user: 'deploy', roles: %w{app db web}
# server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value
# server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value
# server 'db.example.com', user: 'deploy', roles: %w{db}
set :deploy_to, "/home/deploy/some-good-app.example.org_production"



# role-based syntax
# ==================

# Defines a role with one or multiple servers. The primary server in each
# group is considered to be the first unless any  hosts have the primary
# property set. Specify the username and a domain or IP for the server.
# Don't use `:all`, it's a meta role.

# role :app, %w{deploy@example.com}, my_property: :my_value
# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
# role :db,  %w{deploy@example.com}



# Configuration
# =============
# You can set any configuration variable like in config/deploy.rb
# These variables are then only loaded and set in this stage.
# For available Capistrano configuration variables see the documentation page.
# http://capistranorb.com/documentation/getting-started/configuration/
# Feel free to add new variables to customise your setup.



# Custom SSH Options
# ==================
# You may pass any option but keep in mind that net/ssh understands a
# limited set of options, consult the Net::SSH documentation.
# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
#
# Global options
# --------------
#  set :ssh_options, {
#    keys: %w(/home/rlisowski/.ssh/id_rsa),
#    forward_agent: false,
#    auth_methods: %w(password)
#  }
#
# The server-based syntax can be used to override options:
# ------------------------------------
# server 'example.com',
#   user: 'user_name',
#   roles: %w{web app},
#   ssh_options: {
#     user: 'user_name', # overrides user setting above
#     keys: %w(/home/user_name/.ssh/id_rsa),
#     forward_agent: false,
#     auth_methods: %w(publickey password)
#     # password: 'please use keys'
#   }

Deploy!

做好所有準備之後,我們就可以來 deploy 了。他就會很神奇的把東西從你的 git 拉下來,然後建好檔案,安裝完相關 gem ,跑完 migration ,編完 assets ,還會啟動/重啟 puma server 。

1
$ cap staging/production deploy # 視你要 deploy 的 stage 而定,一般都會先在 staging 上測試。

這樣可以看有什麼 task 可以做:

1
$ cap staging/production -T

Custom Task

你可以在 lib/capistrano/tasks 裡自己寫 rake task 。下一次介紹 python 的 capistrano deployment 會介紹到。

伺服器狀態監控: ssmtp, logwatch

詳見 [整理] Linux上常用的server Log監視工具:logwatch

Comments