1 部署思路与宿主机部署流程

1.1 部署思路

  • 后端部署思路

    1. 上传Dockerfile
  1. 上传源代码

  2. 用Dockerfile构建运行环境

  3. 在运行环境里运行源代码

  4. 使用Nginx做转发,优势如下

    1. 负载均衡:可以用多个应用去服务用户
  5. 0宕机时间:新旧应用负载均衡,最终新应用完全替代旧应用,实现应用的不停机更新

  • 前端部署思路

    1. 将代码中的路径替换成CDN路径

    2. 上传打包好的CSS和JS代码到CDN

    3. 将html文件上传到后端服务器

    4. 使用Nginx处理html文件请求

1.2 本地部署实践

  • 思路
    • 本地采用docker部署,首先在linux中打包代码(Rails源代码及Dockerfile)并拷贝到宿主机环境,之后在宿主机中运行docker命令,构建ruby运行容器,最终在宿主机的docker启动项目即可

1.2.0 添加首页

  • 添加跟路由,替换rails自带的欢迎页

    config/routes.rb

    1
    2
    3
    4
    5
    6
    
    Rails.application.routes.draw do
    	get '/', to 'home#index'
      namespace :api do
        ...
      end
    end
    
    • 生成对应的controller:bin/rails g controller home index

      app/controllers/home_controller.rb

    1
    2
    3
    4
    5
    
    class HomeController < ApplicationController
      def index
        render json: { message: 'Welcome' }
      end
    end
    

1.2.1 打包

  • 将docker开发环境中的rails代码选择性地拷贝到公共目录,供宿主机访问

  • bin目录下创建pack_for_host.sh文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
dir=oh-my-env
# 以时间作为版本 注意空格
time=$(date +"%Y%m%d-%H%M%S")
dist=tmp/mangosteen-$time.tar.gz
current_dir=$(dirname $0)
# 打包的目标路径
deploy_dir=/workspaces/$dir/mangosteen_deploy

# 删除之前存在的
yes | rm tmp/mangosteen-*.tar.gz;
yes | rm $deploy_dir/mangosteen-*.tar.gz;

# 打包 除了tmp/cache中的文件 打包到dist目录 *表示所有不以点开头的文件
tar --exclude="tmp/cache/*" -czv -f $dist *
mkdir -p $deploy_dir
# 将Dockerfile与docker命令文件复制过去
cp $current_dir/../config/host.Dockerfile $deploy_dir/Dockerfile
cp $current_dir/setup_host.sh $deploy_dir/
# 将dist文件(tar包移动出来)
mv $dist $deploy_dir
echo $time > $deploy_dir/version
echo 'DONE'
  • 为此文件添加可执行权限:chmod +x bin/pack_for_host.sh

1.2.2 Dockerfile

  • 准备ruby运行容器的dockerfile,供宿主机构建容器用

    config/host.Dockerfile

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    # 基于ruby3.0.0镜像构建ruby运行环境的容器
    FROM ruby:3.0.0
    
    # 设置rails环境变量为production
    ENV RAILS_ENV production
    RUN mkdir /mangosteen
    # 配置bundle源
    RUN bundle config mirror.https://rubygems.org https://gems.ruby-china.com
    WORKDIR /mangosteen
    # 将源代码放入当前工作目录中(ADD会自动解压缩tar包)
    ADD mangosteen-*.tar.gz ./
    # 安装依赖(先配置 安装时排除开发和测试环境的依赖)
    RUN bundle config set --local without 'development test'
    RUN bundle install
    # bundle exec rails server 是专门用在开发环境的
    # 生产环境用puma
    # 只在docker exec或start时自动执行,build时不执行
    ENTRYPOINT bundle exec puma
    

1.2.3 宿主机项目构建与运行

  • 构建部署容器的命令

    bin/setup_host.sh

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    DB_PASSWORD=123456
    container_name=mangosteen-prod-1
    version=$(cat mangosteen_deploy/version)
    
    echo 'docker build ...'
    docker build mangosteen_deploy -t mangosteen:$version
    echo 'docker run ...'
    docker run -d -p 3000:3000 --network=network1 -e DB_PASSWORD=$DB_PASSWORD --name=$container_name mangosteen:$version
    echo 'docker exec ...'
    # 创建数据库 同步数据表
    docker exec -it $container_name bin/rails db:create db:migrate
    echo 'DONE'
    
  • 为此文件添加可执行权限:chmod +x bin/setup_host.sh

1.2.4 部署流程

  • 在容器中执行: bin/pack_for_host.sh

  • 将宿主机的oh-my-env目录下,可以看到mangosteen_deploy文件

1
2
3
4
5
.
├── Dockerfile 
├── mangosteen-.tar.gz
├── setup_host.sh 
└── version # 版本号(时间戳)
  • 在此目录下执行(用zsh或bash):mangosteen_deploy/setup_host.sh

  • 构建完成后在docker desktop会发现多了一个容器,但是启动失败了,失败原因:docker logs containerID

  • 如果是rails报错missing ‘secret_base_key’,说明我们部署的过程是没有问题的

  • 不适用于M1芯片的系统,会遇到了glibc版本不对的问题

2 密钥管理

2.1 基本思路

  • web应用中经常会做一些对称加密,例如JWT和Session ID的加密解密

  • 这些keys不可以存在github中,也不要存在自己电脑中传来传去,都不是安全的

  • 所以rails为我们提供了master.key,master.key相当于加解密的钥匙,rails会对密钥们基于master.key二次加密

  • 我们需要将密钥们写入临时文件,经master.key加密后生成credentials.yml.enc(encrypted),随即销毁临时文件,只有这两个文件同时存在时,rails才会将真正的密钥们解密出来

1
2
master.key + keys => encrypted
encrypted + master.key => keys
  • 可以将加密后的.enc文件放到git中,注意一定要将master.key排除出git(rails帮忙了)

2.2 生产环境master.key

  • rails生成或编辑keys:

    • bin/rails credentials:edit,默认使用vim编辑

    • EDITOR="code --wait" bin/rails credentials:edit,指定使用vscode编辑

  • 执行后,在config下生成master.keycredentials.yml.enc,同时会打开一个临时文件,存储着所有的密钥,关闭即销毁

1
2
3
4
5
6
7
# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: 56dea1725f6fb01a30ce9e93c35aae44dab3a59a89e69a2300ef2973edc78948fa9847a6e399e8bfae304e8cf47843e9f23d3ce8ebf1543ecdc853f5d291d8b6
demo: gsqzs666
  • 关闭后,credentials.yml.enc文件会自动更新

  • rails读keys:

    • 打开rails控制台:bin/rails c

    • 输入代码:Rails.application.credentials.secret_key_base或者Rails.application.credentials.config

1
2
3.0.0 :002 > Rails.application.credentials.config
 => {:secret_key_base=>"a6fe29e67764ee2add4bbf7714478a2bf709b9bf5434cf1859ba84b2f9d2efa18cbbb07c99488823b0bf4299811600b6e972d8f1bdde019a4790b520c369d8a1", :demo=>"gsemir"}

2.3 多环境密钥

  • 开发环境的master.key可以随意,只要保证生产环境的production.key只有负责部署的最高权限那个人能访问就可以避免安全问题

  • rails生成并编辑生产环境的keys,注意默认并没有生成secret_key_base,需要手动添加

    • EDITOR="code --wait" rails credentials:edit --environment production
  • 执行后,在config/credentials下创建了production.key和对应的production.yml.enc

  • rails读生产环境的keys:

    • 打开rails生产环境控制台:RAILS_ENV=production bin/rails c

    • 输入代码:Rails.application.credentials.secret_key_base或者Rails.application.credentials.config

2.4 总结

  • 开发环境

    • 使用master.key和credentials.yml.enc

    • master.key一定ignore

    • 如果.enc被ignore,那就多人公用master.key

    • 如果.enc不被ignore,那就把密钥直接拷贝给每个人,每个人创建自己的master.key

  • 生产环境

    • 使用production.key和production.yml.enc

    • .env不要ignore

    • production.key一定ignore,内容写到部署机的环境变量中

2.5 解决Missing secret_key_base的报错

  • 添加生产环境的production.key(步骤见上文)

  • 更改setup_host,在执行docker run的时候添加环境变量占位符

1
2
3
...
docker run -d -e RAILS_MASTER_KEY=$RAILS_MASTER_KEY ...
...
  • 每次在宿主机执行构建脚本前,加上production.key
1
$ RAILS_MASTER_KEY=xxxxxx mangosteen_deploy/setup_host.sh

3 配置生产环境数据库

config/database.yml

1
2
3
4
5
6
7
8
...
production:
  <<: *default
  database: mangosteen_production
  username: mangosteen
  # 需要通过环境变量传入
  password: <%= ENV["DB_PASSWORD"] %>
  host: <%= ENV["DB_HOST"] %>
  • 再次更改setup_host,在执行docker run的时候添加环境变量占位符
1
2
3
...
docker run -d -e DB_PASSWORD=$DB_PASSWORD -e DB_HOST=$DB_HOST...
...
  • 创建生产环境数据库
1
2
3
4
# exec containerId/name 表示让一个容器执行一个命令
$ docker exec -it mangosteen-prod-1 bin/rails db:create db:migrate
# 手动进入容器创建也行
$ docker exec -it mangosteen-prod-1 bash
  • 每次在宿主机执行构建脚本前,加上production.key、DB_HOST和DB_PASSWORD值
1
$ RAILS_MASTER_KEY=xxxxxx DB_HOST=xxx DB_PASSWORD=xxx mangosteen_deploy/setup_host.sh