写在前面
学习 Rails 时,一直都是部署到 Heroku上,方便的确很方便,但总缺点意思。一直想部署到云服务上,所以实操一下,记录一下简单 Rails 项目部署过程,重在部署。
云服务器
采用的是腾讯云,系统Ubuntu Server 16.04.1 LTS 64位
项目示例
采用 blog demo,来自 Rails 官网文档中 Getting Started with Rails。
安装服器软件
采用 Ubuntu 内建的套件管理工具 apt-get
- 更新软件列表
sudo apt-get update
- 安装 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 libcurl4-nss-dev libxml2-dev libxslt-dev imagemagick nodejs libffi-dev
- 安装 Ruby,使用 Brighbox 已经编译好的 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.6 ruby2.6-dev
- 替换 gem source,安装 Bundler gem
gem source
gem source -r https://rubygems.org/
gem source -a https://gems.ruby-china.com
sudo gem install bundler
- 安装 Rails
gem install rails -v 5.1.7
- 安装数据库,采用PostgreSQL,当然也可以选择 MySQL
sudo apt-get install postgresql libpq-dev postgresql-contrib
# 修改账号 postgres 的密码
sudo -u postgres psql
postgres=#\password # 记住密码,后续 database.yml 中需要
# 创建数据库 blog
sudo -u postgres createdb blog
- 安装 Nginx + Passenger Web 服务器
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
sudo apt-get install -y apt-transport-https ca-certificates
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
sudo apt-get install -y nginx-extras passenger
- 直接访问 IP
确认是否为 Nginx 静态页面,Welcome to nginx on Ubuntu!
新建部署用户
新增 deploy 用户
sudo adduser --disabled-password deploy
sudo su deploy
mkdir ~/.ssh
touch ~/.ssh/authorized_keys
# 本地电脑执行,复制公钥到剪贴板
cat ~/.ssh/id_rsa.pub
# 服务器执行,粘贴公钥
vi ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 644 ~/.ssh/authorized_keys
# 退出后,本地电脑可以 ssh deploy@<主机IP位置> 免密登录
自动化部署
- 安装 Capistrano 部署工具
需要用到 blog 项目
修改 blog 项目 Gemfile,关注处标记 #!!!,修改后 bundle install
source 'https://gems.ruby-china.com/'
git_source(:github) do |repo_name|
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
"https://github.com/#{repo_name}.git"
end
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.1.7'
# Use sqlite3 as the database for Active Record
# Use Puma as the app server
gem 'puma', '~> 3.7'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby
# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
#!!!
group :production do
gem 'pg'
end
#!!!
group :development, :test do
gem 'sqlite3'
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
# Adds support for Capybara system testing and selenium driver
gem 'capybara', '>= 2.15'
gem 'selenium-webdriver'
gem 'rspec-rails'
#!!!
gem 'capistrano-rails'
gem 'capistrano-passenger'
end
group :development do
# Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
- 本地配置 capistrano,项目目录下执行
cap install
编辑 Capfile 文件,关注处标记 #!!!
# Load DSL and set up stages
require "capistrano/setup"
# Include default deployment tasks
require "capistrano/deploy"
# Load the SCM plugin appropriate to your project:
#
# require "capistrano/scm/hg"
# install_plugin Capistrano::SCM::Hg
# or
# require "capistrano/scm/svn"
# install_plugin Capistrano::SCM::Svn
# or
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git
#!!!
require 'capistrano/rails'
require 'capistrano/passenger'
# 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/passenger"
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
- 修改 config/deploy.rb,关注处标记 #!!!
# config valid for current version and patch releases of Capistrano
#!!!
sh "ssh-add"
lock "~> 3.16.0"
#!!!
set :application, "blog"
#!!! 将你的项目提到远程 github 仓库,包含你的之前和后续的一些修改
set :repo_url, "git@github.com:xxx/blog.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"
#!!!
set :deploy_to, "/home/deploy/blog"
# 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"
#!!!
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"
#!!!
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
#!!!
set :passenger_restart_with_touch, true
# Default value for default_env is {}
# set :default_env, { path: "/opt/ruby/bin:$PATH" }
# Default value for local_user is ENV['USER']
# set :local_user, -> { `git config user.name`.chomp }
# Default value for keep_releases is 5
# set :keep_releases, 5
#!!!
set :keep_releases, 5
# Uncomment the following to require manually verifying the host key before first deploy.
# set :ssh_options, verify_host_key: :secure
- 修改 config/deploy/production.rb,关注处标记 #!!!
#!!!
set :branch, "master"
# 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 "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}
#!!! 你的服务 IP 地址
server "15x.13x.12x.18", user: "deploy", roles: %w{app db web}, my_property: :my_value
# 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/user_name/.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"
# }
- 本地项目目录下执行 cap production deploy:check,这会自动在自动登录远程服务器,并建立一些 Capistrano 目录和文件
cap production deploy:check
# 会有缺少 database.yml 等文件 Error,继续看下一步
- 服务器设置 database.yml 和 secrets.yml
# 登录或切换到 deploy 用户,添加 /home/deploy/blog/shared/config/database.yml
production:
adapter: postgresql
pool: 25
database: blog
host: localhost
username: postgres
password: xxxxxx
# 本地项目下执行 rake secret,生成随机数备用
efab0aae7352a424fae818574934baf1107f96785dbdf6ee5f8e7054399a4bbbb561dfc1183e1f20f2c247d9cd437fa0908d2fcb7517b0d7c34e61b459d5969d
# 登录或切换到 deploy 用户,添加 /home/deploy/blog/shared/config/secrets.yml
production:
secret_key_base: 把刚刚的随机数粘贴过来,这地方有个空格,不然报 parse 错
# 再次本地执行 cap production deploy:check
# 应该没有第 14 步的 error 了
- 本机 blog 目录下执行 cap production deploy 进行部署 执行登录远程服务器,从 GitHub 仓库拉取代码,安装 Gem,迁移数据库,编译 asset 等等
ssh-add
Identity added: /Users/gekang/.ssh/id_rsa (/Users/gekang/.ssh/id_rsa)
00:00 git:wrapper
01 mkdir -p /tmp
✔ 01 deploy@15x.x3x.xxx.13 0.351s
Uploading /tmp/git-ssh-0633e09da1de2a9e42f4.sh 100.0%
02 chmod 700 /tmp/git-ssh-0633e09da1de2a9e42f4.sh
✔ 02 deploy@15x.x3x.xxx.13 0.077s
00:00 git:check
01 git ls-remote git@github.com:grackanil/blog.git HEAD
01 ac469184b28aec29e409430ab7bdc0eb6af76e5b HEAD
✔ 01 deploy@15x.x3x.xxx.13 6.136s
00:06 deploy:check:directories
01 mkdir -p /home/deploy/blog/shared /home/deploy/blog/releases
✔ 01 deploy@15x.x3x.xxx.13 0.035s
00:06 deploy:check:linked_dirs
01 mkdir -p /home/deploy/blog/shared/log /home/deploy/blog/shared/tmp/pids /home/…
✔ 01 deploy@15x.x3x.xxx.13 0.464s
00:07 deploy:check:make_linked_dirs
01 mkdir -p /home/deploy/blog/shared/config
✔ 01 deploy@15x.x3x.xxx.13 0.031s
00:08 git:clone
The repository mirror is at /home/deploy/blog/repo
00:08 git:update
01 git remote set-url origin git@github.com:grackanil/blog.git
✔ 01 deploy@15x.x3x.xxx.13 0.077s
02 git remote update --prune
02 Fetching origin
✔ 02 deploy@15x.x3x.xxx.13 5.568s
00:14 git:create_release
01 mkdir -p /home/deploy/blog/releases/20210622112320
✔ 01 deploy@15x.x3x.xxx.13 0.075s
02 git archive master | /usr/bin/env tar -x -f - -C /home/deploy/blog/releases/20…
✔ 02 deploy@15x.x3x.xxx.13 0.092s
00:14 deploy:set_current_revision
01 echo "ac469184b28aec29e409430ab7bdc0eb6af76e5b" > REVISION
✔ 01 deploy@15x.x3x.xxx.13 0.079s
00:14 deploy:symlink:linked_files
01 mkdir -p /home/deploy/blog/releases/20210622112320/config
✔ 01 deploy@15x.x3x.xxx.13 0.070s
02 rm /home/deploy/blog/releases/20210622112320/config/database.yml
✔ 02 deploy@15x.x3x.xxx.13 0.074s
03 ln -s /home/deploy/blog/shared/config/database.yml /home/deploy/blog/releases/…
✔ 03 deploy@15x.x3x.xxx.13 0.071s
04 rm /home/deploy/blog/releases/20210622112320/config/secrets.yml
✔ 04 deploy@15x.x3x.xxx.13 0.071s
05 ln -s /home/deploy/blog/shared/config/secrets.yml /home/deploy/blog/releases/2…
✔ 05 deploy@15x.x3x.xxx.13 0.071s
00:15 deploy:symlink:linked_dirs
01 mkdir -p /home/deploy/blog/releases/20210622112320 /home/deploy/blog/releases/…
✔ 01 deploy@15x.x3x.xxx.13 0.070s
02 rm -rf /home/deploy/blog/releases/20210622112320/log
✔ 02 deploy@15x.x3x.xxx.13 0.070s
03 ln -s /home/deploy/blog/shared/log /home/deploy/blog/releases/20210622112320/l…
✔ 03 deploy@15x.x3x.xxx.13 0.073s
04 ln -s /home/deploy/blog/shared/tmp/pids /home/deploy/blog/releases/20210622112…
✔ 04 deploy@15x.x3x.xxx.13 0.071s
05 ln -s /home/deploy/blog/shared/tmp/cache /home/deploy/blog/releases/2021062211…
✔ 05 deploy@15x.x3x.xxx.13 0.078s
06 ln -s /home/deploy/blog/shared/tmp/sockets /home/deploy/blog/releases/20210622…
✔ 06 deploy@15x.x3x.xxx.13 0.077s
07 ln -s /home/deploy/blog/shared/public/system /home/deploy/blog/releases/202106…
✔ 07 deploy@15x.x3x.xxx.13 0.069s
08 rm -rf /home/deploy/blog/releases/20210622112320/public/assets
✔ 08 deploy@15x.x3x.xxx.13 0.078s
09 ln -s /home/deploy/blog/shared/public/assets /home/deploy/blog/releases/202106…
✔ 09 deploy@15x.x3x.xxx.13 0.072s
00:16 bundler:config
01 bundle config --local deployment true
01 You are replacing the current local value of deployment, which is currently nil
✔ 01 deploy@15x.x3x.xxx.13 0.241s
02 bundle config --local path /home/deploy/blog/shared/bundle
02 You are replacing the current local value of path, which is currently nil
✔ 02 deploy@15x.x3x.xxx.13 0.317s
03 bundle config --local without development:test
03 You are replacing the current local value of without, which is currently nil
✔ 03 deploy@15x.x3x.xxx.13 0.308s
00:18 bundler:install
The Gemfile's dependencies are satisfied, skipping installation
00:18 deploy:assets:precompile
01 bundle exec rake assets:precompile
01 Yarn executable was not detected in the system.
01 Download Yarn at https://yarnpkg.com/en/docs/install
✔ 01 deploy@15x.x3x.xxx.13 1.870s
00:20 deploy:assets:backup_manifest
01 mkdir -p /home/deploy/blog/releases/20210622112320/assets_manifest_backup
✔ 01 deploy@15x.x3x.xxx.13 0.073s
02 cp /home/deploy/blog/releases/20210622112320/public/assets/.sprockets-manifest…
✔ 02 deploy@15x.x3x.xxx.13 0.071s
00:20 deploy:migrate
[deploy:migrate] Run `rake db:migrate`
00:20 deploy:migrating
01 bundle exec rake db:migrate
✔ 01 deploy@15x.x3x.xxx.13 1.584s
00:21 deploy:symlink:release
01 ln -s /home/deploy/blog/releases/20210622112320 /home/deploy/blog/releases/cur…
✔ 01 deploy@15x.x3x.xxx.13 0.075s
02 mv /home/deploy/blog/releases/current /home/deploy/blog
✔ 02 deploy@15x.x3x.xxx.13 0.072s
00:22 passenger:restart
01 mkdir -p /home/deploy/blog/releases/20210622112320/tmp
✔ 01 deploy@15x.x3x.xxx.13 0.072s
02 touch /home/deploy/blog/releases/20210622112320/tmp/restart.txt
✔ 02 deploy@15x.x3x.xxx.13 0.072s
00:22 deploy:cleanup
Keeping 5 of 6 deployed releases on 15x.x3x.xxx.13
01 rm -rf /home/deploy/blog/releases/20210622073535
✔ 01 deploy@15x.x3x.xxx.13 0.080s
00:22 deploy:log_revision
01 echo "Branch master (at ac469184b28aec29e409430ab7bdc0eb6af76e5b) deployed as …
✔ 01 deploy@15x.x3x.xxx.13 0.073s
后续只需要修改代码,执行部署就可以了
git commit
cap production deploy
注意此处会生成 current 目录,后续会需要
设置 Nginx
- 使用root 权限,设置 /etc/nginx/nginx.conf,使用 #!!!
#!!!
env PATH;
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 768;
# multi_accept on;
}
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;
#!!!
include /etc/nginx/passenger.conf;
# 下略
- 远程使用 root 权限,设置 /etc/nginx/sites-enabled/blog.conf,blog 命名随意
server {
listen 80;
server_name 15x.x3x.xxx.13; # 用你自己的服务器 IP 地址
root /home/deploy/blog/current/public; # 第 16 步骤最后说的 current 目录
passenger_enabled on;
passenger_min_instances 1;
location ~ ^/assets/ {
expires 1y;
add_header Cache-Control public;
add_header ETag "";
break;
}
}
- 重启 Nginx
sudo service nginx restart
- 打开 IP 地址,大功告成,开心