岁岁年,碎碎念

记一次 Ruby on Rails 部署记录

2022.04.19     1863

写在前面

学习 Rails 时,一直都是部署到 Heroku上,方便的确很方便,但总缺点意思。一直想部署到云服务上,所以实操一下,记录一下简单 Rails 项目部署过程,重在部署。

云服务器

采用的是腾讯云,系统Ubuntu Server 16.04.1 LTS 64位

项目示例

采用 blog demo,来自 Rails 官网文档中 Getting Started with Rails。

安装服器软件

采用 Ubuntu 内建的套件管理工具 apt-get

  1. 更新软件列表
sudo apt-get update
  1. 安装 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
  1. 安装 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
  1. 替换 gem source,安装 Bundler gem
gem source
gem source -r https://rubygems.org/
gem source -a https://gems.ruby-china.com

sudo gem install bundler
  1. 安装 Rails
gem install rails -v 5.1.7
  1. 安装数据库,采用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
  1. 安装 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
  1. 直接访问 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位置> 免密登录

自动化部署

  1. 安装 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]
  1. 本地配置 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 }
  1. 修改 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
  1. 修改 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"
#   }
  1. 本地项目目录下执行 cap production deploy:check,这会自动在自动登录远程服务器,并建立一些 Capistrano 目录和文件
cap production deploy:check

# 会有缺少 database.yml 等文件 Error,继续看下一步
  1. 服务器设置 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 了
  1. 本机 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

  1. 使用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;

    # 下略
  1. 远程使用 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;
   }
}
  1. 重启 Nginx
sudo service nginx restart
  1. 打开 IP 地址,大功告成,开心 图片

参考文档