网站布署

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。以下的指令会更新和升级已经安装的套件:

  1. sudo apt-get update
  2. sudo apt-get upgrade -y
  3. sudo dpkg-reconfigure tzdata

进入选单选你的Time zone=>Asia=>Taipei

接着我们安装新的套件们,这些是 Ruby on Rails 所需要的东西。请输入以下一行指令:

  1. 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 套件

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

安装好之后,输入

  1. ruby -v

应该就会看到 ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu] 就是成功了。

接着安装 Bundler gem

  1. sudo gem install bundler

2-2. 安装 Ruby 慢方法:自行编译原始码

或是我们可以下载 Ruby 的原始码,自行编译。相较于方法一会花比较久的时间,不过如果有新版 Ruby 发行时,上述 brighbox 不一定会即时包好,或是 brighbox 不维护的话,这时想用新版需要自己编译了:

  1. wget http://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
  2. tar xvfz ruby-2.3.1.tar.gz
  3. cd ruby-2.3.1
  4. ./configure
  5. make
  6. 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 会用到)。

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

接着我们进入 mysql console 建立新的数据库:

  1. mysql -u root -p

进入 mysql console 后,输入:

  1. CREATE DATABASE your_database_name CHARACTER SET utf8mb4;

手动建立一个数据库(注意,数据库名称不能包括横线-),等会你的Rails就用这个。执行完,输入 exit 离开 mysql console,然后继续以下步骤。

若是之后用相同主机建立一个新专案,要再重复这个步骤新增数据库。

3-2. 或是安装PostgreSQL数据库

要用 MySQL 的话,就不需要装 PostgreSQL 了,二选一。MySQL 是网络公司的最爱,分布式扩充和商业支持的生态系非常丰富。PostgreSQL 则是对进阶的 SQL 语法支援比较多,以及支援更多的储存格式,例如 PostGIS

你也可以选择安装PostgreSQL

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

修改帐号 postgres 的密码

  1. sudo -u postgres psql 然后打 \password

建数据库

  1. 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 的步骤:

  1. sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
  2. sudo apt-get install -y apt-transport-https ca-certificates
  3. # Add our APT repository
  4. sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger xenial main > /etc/apt/sources.list.d/passenger.list'
  5. sudo apt-get update
  6. # Install Passenger + Nginx
  7. sudo apt-get install -y nginx-extras passenger

打开你的浏览器,输入 Server IP 位置,应该就可以看到默认的 Nginx 网页了:Welcome to nginx on Ubuntu!

Nginx启动和重开用法:

  1. sudo service nginx start
  2. sudo service nginx stop
  3. sudo service nginx restart

4-2. 安装 Nginx + Passenger 慢方法:自行编译

或是也可以用自行编译的方式,好处是可以 customize Nginx 的版本:

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

过程中:

  • 选 ruby
  • 接着会要你选1. download 或 2. customize=> 请选 1
  • 用默认目录 /opt/nginx

这是因为Passenger必须与Nginx一起编译的关系,所以Passenger的安装指令就包括了安装Nginx。接着我们设定 Nginx 启动脚本:

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

自行编译的话,Nginx 的设定档位于 /opt/nginx/conf/ 下。

5. 新增 deploy 使用者

将下来我们想要找地方放我们 Ruby on Rails 专案的程式。因为 root 帐号权限很大,习惯上我们会在服务器上另开一个专门的帐号,用来放你的Rails专案程式码。这里我们另开一个 deploy 帐号来使用:

  1. sudo adduser --disabled-password deploy
  2. sudo su deploy
  3. cd ~
  4. 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 离开
  1. chmod 644 /home/deploy/.ssh/authorized_keys
  2. chown deploy:deploy /home/deploy/.ssh/authorized_keys

这样本机就可以直接 ssh deploy@<主机IP位置>,登入无须密码。

6. 设定你的 Rails 专案 (如果做自动化部署的话,这一节就不需要做了)

假设我们的 Rails 专案是放在 Github 上,那么从 GitHub pull 下来即可。接下来请在远端主机上操作:

  1. cd ~
  2. git clone https://github.com/ihower/rails-exercise-ac9.git
  3. 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:

  1. production:
  2. adapter: mysql2
  3. pool: 25
  4. encoding: utf8mb4
  5. database: your_database_name
  6. host: localhost
  7. username: root
  8. password: your_database_password

如果是用 PostgreSQL 的话:

  1. production:
  2. adapter: postgresql
  3. pool: 25
  4. database: your_database_name
  5. host: localhost
  6. username: root
  7. password: your_database_password

接着编辑 config/secrets.yml (在本机用 rake secret 可以随机产生一个新的 key):

  1. production:
  2. secret_key_base: xxxxxxx........

执行

  1. bundle install --deployment --without test development

执行

  1. RAILS_ENV=production bundle exec rake db:migrate
  2. RAILS_ENV=production bundle exec rake assets:precompile

之后如果要更新 rails 专案的程式码:

  1. git pull
  2. touch tmp/restart.txt

7. 设定 Nginx (如果做自动化部署的话,这一节等做好自动化部署之后再做)

输入 exit 回到 root 帐号

编辑 /etc/nginx/nginx.conf,打开以下一行:

  1. include /etc/nginx/passenger.conf;

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

  1. 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

  1. server {
  2. listen 80;
  3. server_name your_domain.com; # 还没 domain 的话,先填 IP 位置
  4. root /home/deploy/your_project_name/public;
  5. # 如果是自动化部署,位置在 root /home/deploy/your_project_name/current/public;
  6. passenger_enabled on;
  7. passenger_min_instances 1;
  8. location ~ ^/assets/ {
  9. expires 1y;
  10. add_header Cache-Control public;
  11. add_header ETag "";
  12. break;
  13. }
  14. }

以上设定包括设定Assets静态档案成为永不过期(Rails的Assets Pipeline会加上版本号,所以不需要担心)、设定Passenger至少开一个Process。其中servername your_domain.com请会换成你的_domain。如果Domain name还没注册好,可以先用服务器IP地址。但是如果你的服务器上有多个Rails专案或网站,就必须用不同domain来区分。

如果有多个domain连到同一个服务器,可以用空白区隔,例如:

  1. 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中加上:

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

数据库用 MySQL 的话,记得再加上 gem "mysql2"

接着输入bundle install

在你的Rails专案目录下执行:

  1. cap install

Enhance Capistrano with awesome collaboration and automation features? 请输入 no

这样就会产生几个档案,首先编辑Capfile在中段加入:

  1. require 'capistrano/rails'
  2. require 'capistrano/passenger'
  3. require "capistrano/scm/git"
  4. install_plugin Capistrano::SCM::Git

编辑config/deploy.rb,请替换以下的application名称、git repo网址和deploy_to路径

  1. `ssh-add` # 注意这是键盘左上角的「 `」不是单引号「 '」
  2. set :application, 'rails-exercise'
  3. set :repo_url, 'git@github.com:ihower/rails-exercise.git'
  4. set :deploy_to, '/home/deploy/rails-exercise'
  5. set :keep_releases, 5
  6. append :linked_files, 'config/database.yml', 'config/secrets.yml'
  7. # 如果有 facebook.yml 或 email.yml 想要连结的话,也要加进来
  8. append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system'
  9. set :passenger_restart_with_touch, true
  10. # ....

其中的ssh-add可以参考SSH agent forwarding 的应用的说明。

编辑config/deploy/production.rbexample.com换成服务器的IP或网域,例如:

  1. 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格式的缩排规则,用两个空格):

  1. production:
  2. secret_key_base: xxxxxxx........

远端服务器设定好shared/config/database.yml,范例如下:

  1. production:
  2. adapter: mysql2
  3. encoding: utf8mb4
  4. database: your_database_name
  5. host: localhost
  6. username: root
  7. password: your_database_password

如果是用PostgreSQL范例如下:

  1. production:
  2. adapter: postgresql
  3. encoding: unicode
  4. database: your_database_name
  5. host: localhost
  6. pool: 25
  7. username: postgres
  8. password: your_database_password

到此终于可以部署了,执行cap production deploy就可以了。这会建立current这个目录用symbolic link指向releases目录下最新的版本。

修改 Nginx 设定

因为 Rails 专案目录的位置跟上一章不一样,所以请修改上一章的 /etc/nginx/site-enabled/your_project_name.conf

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

重开 Nginx:

  1. $ 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.rblock ‘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

  1. worker_processes auto;
  2. events {
  3. worker_connections 4096;
  4. use epoll;
  5. }
  6. http {
  7. passenger_show_version_in_header off;
  8. server_tokens off;
  9. client_max_body_size 100m;
  10. gzip on;
  11. gzip_disable "msie6";
  12. gzip_comp_level 5;
  13. gzip_min_length 256;
  14. gzip_proxied any;
  15. gzip_vary on;
  16. gzip_types
  17. application/atom+xml
  18. application/javascript
  19. application/x-javascript
  20. application/json
  21. application/rss+xml
  22. application/vnd.ms-fontobject
  23. application/x-font-ttf
  24. application/x-web-app-manifest+json
  25. application/xhtml+xml
  26. application/xml
  27. font/opentype
  28. image/svg+xml
  29. image/x-icon
  30. text/css
  31. text/xml
  32. text/plain
  33. text/javascript
  34. text/x-component;
  35. # .... 其他不用改
  36. }

修改完记得重开 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
  • 关闭 Nginx 不可以用 IP address 浏览,一定需要用 domain name。这样可以避免无差别扫 IP 试探攻击,如果你有使用 cloudflare 的话,也可以确保流量一定经过 cloudflare 防火墙:
  1. server {
  2. # ....
  3. server_name www.your_domain.com;
  4. if ($host != $server_name) {
  5. return 444;
  6. }
  7. # ....

整理 Log 档案

网站持续运作,log目录下的production.log可是会越长越肥,因此需要定期整理备份,这里有几种方法,一种是修改config/environments/production.rb的设定:

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

或是

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

另一种是用Linux内建的logrotate工具,请参考 使用 logrotate 定期整理 Rails Log 档案

例如新增 /etc/logrotate.d/rails 档案,内容如下:

  1. /home/deploy/dojo/shared/log/*.log {
  2. monthly
  3. dateext
  4. missingok
  5. rotate 65535
  6. notifempty
  7. copytruncate
  8. }
  9. /var/log/nginx/*.log {
  10. monthly
  11. dateext
  12. missingok
  13. rotate 65535
  14. notifempty
  15. copytruncate
  16. }

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:
  1. server {
  2. listen 443 ssl;
  3. ssl on;
  4. ssl_certificate /opt/nginx/staging.crt;
  5. ssl_certificate_key /opt/nginx/staging.key;
  6. server_name exercise.ihower.tw;
  7. root /home/deploy/rails-exercise/current//public;
  8. passenger_enabled on;
  9. passenger_min_instances 1;
  10. server_tokens off;
  11. location ~ ^/assets/ {
  12. expires 1y;
  13. add_header Cache-Control public;
  14. add_header ETag "";
  15. break;
  16. }
  17. }

如果只支援 SSL 连线,可以将所有 HTTP 连线重导到 HTTPS

  1. server {
  2. listen 80;
  3. server_name exercise.ihower.tw;
  4. server_tokens off;
  5. location / {
  6. return 301 https://$host$request_uri;
  7. }
  8. }

方法二:使用 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 设定,加上

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

编辑 /etc/nginx/.htpasswd,内容如下

  1. 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 就加载:

  1. unless Rails.env.production?
  2. puts "Enable email interceptor"
  3. require 'email_interceptor'
  4. ActionMailer::Base.register_interceptor(EmailInterceptor)
  5. 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,内容如下

  1. ControlMaster auto
  2. ControlPath /tmp/ssh_mux_%h_%p_%r
  3. Host your_project_name
  4. HostName 192.168.1.115
  5. 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 自动执行的范例:
  1. 30 4 * * * /bin/bash -l -c '/usr/local/bin/backup perform -t my_backup -c /home/ihower/Backup/config.rb'