CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
部署与运维
这一课你将能做到
用 Puma + systemd 或 Kamal 把 Rails 应用推上生产服务器
配置生产环境的三件套:资产预编译、数据库迁移、密钥管理
说清楚 Rails 7/8 的 Solid 生态(Cache/Queue/Cache)如何省掉 Redis
为什么要学
能写的代码不算本事,能稳定跑在用户面前的代码才是。部署和运维是开发到上线的最后一公里,也是事故最高发的阶段:环境变量漏配、数据库迁移没跑、资产 404、连接池耗尽——任意一条都能让精心写的代码在用户面前变成白屏。Rails 8 的 Kamal 把部署简化到「一条命令上云」,中小团队终于不必深陷 Ansible/Capistrano 的泥潭。
对比本站:Spring Boot + Next.js 的部署通常拆成「Java 容器 + Nginx + Node 进程」,配置两份;Rails 全栈应用是单一进程(Puma),配好反向代理即可,部署脚本短一半。
先建立图像
用户浏览器
│
▼
Nginx / CDN(SSL、静态文件、缓存)
│
▼
Puma(Rails 进程)
│
├─ Workers(2~4)× Threads(5)
│
▼
数据库(SQLite / MySQL / PostgreSQL)
│
▼
后台 Worker(Solid Queue,可选独立进程)
部署流水线:
代码推送 → CI 跑测试 → 镜像构建(Docker)→ 服务器拉镜像
→ 资产预编译 → 数据库 migrate → 切新容器 → 旧容器退役
跟着做
步骤 1:生产环境配置检查清单
config/environments/production.rb:
Rails.application.configure do
config.cache_classes = true
config.eager_load = true
config.consider_all_requests_local = false # 不暴露完整错误栈
config.force_ssl = true # 强制 HTTPS
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
config.active_storage.variant_processor = :mini_magick
config.log_level = :info
config.logger = ActiveSupport::Logger.new(STDOUT)
end
上线前必做三件事 (顺序不可乱):
# 1. 预编译资产(CSS/JS 指纹化)
RAILS_ENV=production bin/rails assets:precompile
# 2. 跑数据库迁移(新增/改表)
RAILS_ENV=production bin/rails db:migrate
# 3. 重启 Puma 进程加载新代码
步骤 2:Puma 配置
config/puma.rb:
workers ENV.fetch("WEB_CONCURRENCY", 2)
threads_count = ENV.fetch("RAILS_MAX_THREADS", 5)
threads threads_count, threads_count
preload_app!
on_worker_boot do
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end
workers :进程数,按 CPU 核心数设(通常是核心数或核心数+1)
threads :每个 worker 的线程数,影响并发连接数
preload_app! :启动时预加载应用,节省内存(Copy-on-Write)
环境变量:
export WEB_CONCURRENCY=2 # 2 个 worker 进程
export RAILS_MAX_THREADS=5 # 每个 5 线程
export RAILS_ENV=production
export RAILS_MASTER_KEY=... # 解密 credentials 的密钥
步骤 3:用 Kamal 一键部署(Rails 8 推荐) Kamal 是 Rails 8 官方出的部署工具,基于 Docker,一条命令完成构建、推送、迁移、切换:
# 1. 安装并初始化
gem install kamal
kamal init
# 生成 config/deploy.yml
# servers: 你的 VPS IP
# registry: Docker Hub 或阿里云镜像仓库
# config/deploy.yml
service: blog
image: yourname/blog
servers:
web:
- 1.2.3.4
job:
hosts:
- 1.2.3.4
cmd: bin/jobs
registry:
username: yourname
password:
- KAMAL_REGISTRY_PASSWORD
env:
secret:
- RAILS_MASTER_KEY
kamal setup # 首次:装 Docker、建目录、配 traefik 反向代理
kamal deploy # 后续:构建镜像、推仓库、迁移、切流量
kamal logs # 看实时日志
kamal app exec "bin/rails console" # 远程进 console
Kamal 底层用 Docker 容器运行,自带 Traefik 做反向代理和 SSL(Let's Encrypt 自动续期)。对中小项目来说,这就是「Heroku 体验的私有化替代」。
步骤 4:Solid 生态——省掉 Redis Rails 7.1+ 推了三件套,目标是用数据库取代 Redis 担任缓存和队列:
组件 角色 替代对象 Solid Cache 片段缓存/动作缓存 Redis Cache Solid Queue 后台任务队列 Sidekiq + Redis Solid Cable WebSocket(Action Cable)广播 Redis Pub/Sub
# config/environments/production.rb
config.cache_store = :solid_cache_store
bin/rails solid_cache:install
bin/rails db:migrate
缓存写入数据库表,天然持久化、事务一致、不需要额外运维。QPS 几千以内的内容站完全够用;超高并发才需切回 Redis。
步骤 5:监控与健康检查 class HealthController < ApplicationController
skip_before_action :require_login, raise: false
def index
ActiveRecord::Base.connection.execute("SELECT 1")
render plain: "OK", status: :ok
end
end
# routes.rb
get "/up", to: "health#index"
负载均衡器 / Kamal 用它判断实例是否健康, unhealthy 时自动摘除。配合日志轮转(lograge gem 输出结构化日志)和 APM(New Relic / Sentry)形成监控闭环。
讲清楚要点
部署顺序铁律:代码→资产→迁移→重启 :资产要基于最新代码编译,迁移要在服务切换前完成,重启最后。顺序错了就是白屏或数据不一致事故。
环境变量是生产配置的唯一入口 :密码、密钥、第三方 token 全进 ENV,代码库零秘密。Kamal 的 env.secret 会自动注入。
Puma workers × threads 的算式 :总并发 = workers × threads,数据库连接池 ≥ 这个值。单机内存小就减 workers,IO 密集就加 threads。
Kamal 不是唯一方案 :Heroku/Fly.io 适合不想管服务器的人;Capistrano 适合老项目;Docker Swarm/K8s 适合团队已有基础设施。Kamal 是「我想自己控制服务器但不想写 500 行 Ansible」的最佳解。
Solid 生态降低运维负担 :一个数据库同时扛业务数据 + 缓存 + 队列,部署文档短一页。等流量真的爆了再引入 Redis,不要过度架构。
你可能会懵的地方
SQLite 怎么上生产?
SQLite 是单文件,多 worker 共享一个文件在并发下可能锁竞争。用 solid_cache 做缓存缓解,或把 SQLite 放 fast NVMe 盘。真想高并发就上 PostgreSQL/MySQL,但不要为了「觉得生产该用 RDB」而提前引入运维成本。
assets:precompile 为什么慢?
首次编译所有 JS/CSS 指纹。开发时不需要,只有 CI/CD 或生产部署跑。加 RAILS_ENV=production 时跳过 jsbundling 可提速(纯 Importmap 项目已很快)。
Kamal 和 Docker Compose 区别?
Docker Compose 适合本地/单机编排;Kamal 是专为 Web 部署设计的 CLI,封装了镜像构建、多机分发、零停机切换(blue/green 部署)、SSL 终止、日志聚集。
零停机部署怎么做到的?
Kamal 先启动新容器跑健康检查,通过后切流量,再停旧容器。期间请求由 Traefik 平滑转发,用户无感知。这叫 rolling update。
日志打到哪里?
Docker 容器日志输出到 STDOUT,Kamal 用 kamal logs 聚合。生产配 lograge gem 输出 JSON,对接日志平台(ELK / Datadog)。
错法对照 错法 问题 正解 生产 eager_load = false 第一次请求时才加载,慢且易超时 production.rb 必须 eager_load = true 部署顺序:重启→迁移 新代码跑在老结构上,报错 先迁移,再重启 master.key 提交到仓库 生产密钥泄露 加入 .gitignore,服务器 ENV 注入 把 secrets 写死在代码里 改密码要发版 全走环境变量或 credentials 没有健康检查端点 实例挂了负载均衡不知道 /up 检查 DB 连通性
卡点急救
上线后静态文件 404 :生产环境 public_file_server 未启用且 Nginx 没配 assets 目录;或预编译没跑。检查 public/assets/ 下有无带指纹文件,Nginx alias 是否指向正确。
ActiveRecord::ConnectionTimeoutError :连接池 < 总线程数。检查 pool(database.yml)和 RAILS_MAX_THREADS(Puma 配置),确保 pool ≥ threads,还要加 Sidekiq 的并发余量。
Kamal 部署后服务起不来 :SSH 进服务器 kamal app logs 看报错;常见是数据库没建、迁移未跑、master key 不对。Kamal 的 app exec "bin/rails db:migrate" 可远程执行补救。
面试常问
Rails 生产环境部署的核心步骤和顺序?
答:①代码拉取/镜像构建 → ②资产预编译 → ③数据库迁移 → ④零停机切换容器/进程 → ⑤旧实例退役。顺序不能乱:迁移必须在服务切换前完成,否则新代码读老结构;资产必须在重启前编译,否则指纹对不上。
Puma workers 和 threads 怎么配?
答:workers 对应进程数,CPU 核心数或稍多(内存允许下);threads 对应单进程内的并发数,IO 密集型可设 10~16,计算密集型 5 左右。总并发 = workers × threads,数据库连接池 ≥ 该值。内存不够时优先减 workers(线程共享进程内存)。
Kamal 解决了什么问题?
答:把 Rails 部署从「写几百行 Ansible/Capistrano 脚本 + 配 Nginx + 手动 SSL + 各自为政的日志」压缩成一条 kamal deploy 命令。基于 Docker 保证环境一致,自带 Traefik 做反向代理和自动证书,默认零停机切换。是 Rails 8 对「部署体验」的官方回答。
自测三问
assets:precompile 和 db:migrate 为什么必须在服务重启前执行?顺序错了会发生什么?
Solid Cache 相比 Redis 省掉了什么?在什么量级下可能成为瓶颈?
Kamal 的零停机切换大概是怎么实现的?旧容器什么时候停止?
小结一句话 部署纪律:资产先编译、迁移再切流量、密钥走环境变量、监控接健康检查——Kamal 一条命令把 Rails 推上线。
延伸