Capistrano部署入门:可审计、可回滚的Ruby自动化流水线 1. 项目概述Capistrano不是“一键部署”而是可审计、可回滚、可协作的部署流水线Capistrano 是 Ruby 生态里最成熟、最被低估的自动化部署工具之一。它不像 Docker Compose 那样强调环境隔离也不像 GitHub Actions 那样主打云原生编排它的核心价值非常具体让每一次线上发布变成一次可重复、可追溯、可协作的工程动作。我从 2013 年第一次在 Rails 项目中用 Capistrano 替换手工 rsync 手动重启 unicorn 的操作起至今在 7 个不同技术栈的生产环境中持续使用它——包括纯 Ruby/Sinatra 微服务、混合 PHPRuby 的遗留系统、甚至用它驱动 Python Flask 应用的蓝绿切换脚本。它不解决“怎么写代码”但彻底终结了“上线前心跳加速、上线后不敢关电脑”的运维焦虑。标题里那个 “Getting Started” 并非谦辞而是真实门槛提示Capistrano 的学习曲线不在语法而在对部署本质的理解重构。它强制你把“部署”拆解为明确阶段check → deploy → finish、明确角色local machine 发起remote server 执行、明确状态current → releases → shared。这种结构化思维恰恰是多数人用 shell 脚本或 Jenkins 自由风格任务时缺失的底层逻辑。关键词里反复出现的recipes食谱就是这个逻辑的具象化——不是写“执行什么命令”而是定义“在什么时机、以什么权限、对哪些服务器、按什么顺序、带什么上下文去执行”。而automation在这里从来不是“全自动无人值守”而是“所有人工干预点都显式声明、所有失败路径都预设应对”。你不需要是 Ruby 专家但必须接受一个事实Capistrano 的 DSL领域特定语言是 Ruby 写的这意味着它的配置文件本身就是可执行的 Ruby 代码。这既是优势能复用 Ruby 生态、做条件判断、调用外部 API也是新手第一道坎看到task :restart do on roles(:app) do ... end end会本能想查“on 是哪个库的方法”。别急着查文档——先理解它为什么这样设计因为部署不是线性流程而是带分支、带依赖、带容错的有向图。比如“数据库迁移”必须在“新代码上线前”完成但“静态资源预编译”可以并行“备份旧版本”应在“软链接切换前”执行而“清理过期 release”则安排在“整个流程成功后”。Capistrano 的 recipe 结构天然支持这种表达。适合谁学三类人收益最大一是维护老旧 Rails 应用的工程师Capistrano 仍是官方推荐部署方案二是需要在受限环境如客户内网、无 Kubernetes 的私有云实现标准化发布的 DevOps 工程师三是想深入理解“自动化”本质的初中级开发者——它比 Ansible 更聚焦部署场景比 Shell 脚本更强调工程规范。至于热搜词里混入的 “Delphi deployment”、“WinCC Runtime”、“Automation Studio”它们和 Capistrano 属于完全不同的技术宇宙前者是工业控制软件的专用部署机制后者是西门子 TIA Portal 的工程集成环境而 Capistrano 解决的是通用 Web 应用的 Linux 服务器交付问题。强行类比只会混淆认知边界。我们接下来要做的就是亲手搭起这条最小可行的部署流水线从零开始验证为什么一个 2009 年诞生的工具在 2024 年依然值得投入时间。2. 核心设计逻辑为什么 Capistrano 不用 YAML 配置而坚持 Ruby DSL2.1 部署不是静态配置而是动态决策过程很多新手接触 Capistrano 后的第一个困惑是“为什么不能像 Ansible 那样用 YAML 写 playbook” 这个问题直指 Capistrano 的哲学内核。YAML 擅长描述静态状态比如 “目标服务器应安装 nginx 1.22”但部署的核心挑战在于处理动态条件。举个真实案例某次上线需同时更新 API 服务和管理后台但后台依赖一个新数据库字段而 API 服务必须先启动才能触发数据同步任务。这时你需要检查数据库迁移是否已执行rails db:migrate:status | grep up若未执行则运行迁移并捕获错误rails db:migrate || exit 1若执行失败立即终止后续步骤并发送告警调用 Slack webhook若成功再并行启动两个服务systemctl start api systemctl start adminYAML 无法原生支持这种 if-else-try-catch 流程。而 Capistrano 的 Ruby DSL 天然支持task :migrate_and_check do on roles(:db) do within current_path do # 检查迁移状态 result capture(bundle exec rails db:migrate:status | grep -c up) if result.to_i 0 # 执行迁移 execute bundle exec rails db:migrate # 验证结果 verify_result capture(bundle exec rails db:migrate:status | grep -c up) if verify_result.to_i 0 raise Database migration failed! end end end end end这段代码里capture和execute不是简单命令执行而是 Capistrano 封装的带上下文的远程操作自动处理 SSH 连接复用、路径切换、错误传播、日志记录。更重要的是raise会触发 Capistrano 的全局错误处理机制——自动回滚到上一个 release并发送通知。这种“操作即逻辑”的能力是任何声明式配置语言难以替代的。2.2 Recipes 的本质是“可组合的部署原子单元”热搜词里高频出现的recipes常被误解为“预设脚本集合”。实际上Capistrano 的 recipe 是可复用、可覆盖、可依赖的部署行为模块。它遵循 Unix 哲学“做一件事并做好”。一个标准 recipe 文件如lib/capistrano/tasks/nginx.rake通常只做三件事声明任务task :nginx_config定义一个命名操作设置依赖before deploy:published, nginx:config声明在何时执行编写逻辑on roles(:web) { ... }定义具体行为这种设计带来两个关键优势第一是职责分离。你可以把 Nginx 配置、SSL 证书更新、日志轮转分别写成独立 recipe再根据项目需求组合。某金融客户要求每次部署必须生成审计日志我们只需新增audit_log.rake并在deploy.rb中添加after deploy:finished, audit:log完全不影响原有流程。第二是安全可控。Capistrano 默认禁止执行任意 shell 命令execute rm -rf /会被拦截所有操作必须通过on roles()显式指定作用域。当你看到on roles(:app)就知道这段代码只会在标记为app角色的服务器上运行且自动继承该角色的用户、端口、SSH 密钥等配置。这种“最小权限原则”的贯彻远超手动写 bash 脚本时的ssh userhost cd /var/www git pull。2.3 Capistrano 的目录结构是部署可靠性的物理保障Capistrano 强制采用的目录结构releases/,shared/,current/不是历史包袱而是经过十年生产验证的可靠性模式。我们来拆解它如何解决实际痛点目录作用解决什么问题实操案例releases/存放每次部署的完整快照按时间戳命名避免“正在覆盖代码时服务中断”某次部署因网络中断卡在 80%releases/20240520120000目录不完整但current仍指向健康的20240519100000用户无感知shared/存放跨版本共享的数据logs, system, config解决“配置文件随代码更新被覆盖”shared/config/database.yml包含生产环境密码每次部署自动软链接到current/config/database.yml无需在 Git 中存敏感信息current/指向当前生效 release 的符号链接实现秒级回滚与无缝切换执行cap production deploy:rollback仅需 0.3 秒即可将current指向releases/20240519100000比重启进程还快这个结构的价值在灰度发布中尤为突出。我们曾为电商大促设计双轨部署current_stable指向主版本current_canary指向灰度版本。通过 Nginx upstream 动态调整流量比例全程无需停机。而这一切的基础正是 Capistrano 对目录结构的强约束——它强迫你把“代码”、“配置”、“状态”物理隔离让复杂操作变得简单可预测。3. 实操搭建从零初始化一个可运行的 Capistrano 部署流水线3.1 环境准备Ruby 版本与系统依赖的务实选择标题中提到的 “failed to install homebrew portable ruby (and your system version is too old)” 是真实痛点但解决方案并非升级系统而是明确 Ruby 的使用边界。Capistrano 本身只需要 Ruby 2.7 运行在本地机器你的开发笔记本它不关心服务器上的 Ruby 版本。服务器上真正执行部署逻辑的是 SSH 和 shellCapistrano 只是“聪明的 SSH 客户端”。因此环境准备分两层本地开发机必须Ruby 2.7.8 或 3.0.6避免最新版因某些 gem 尚未兼容Bundler 2.3gem install bundler -v ~ 2.3SSH 公钥已添加到目标服务器ssh-copy-id userserver提示Mac 用户遇到 “mac failed to upgrade homebrew portable ruby!” 时不要强行升级 Homebrew Ruby。直接用 rbenv 安装指定版本rbenv install 3.0.6 rbenv global 3.0.6。Homebrew 的 portable ruby 是为 Homebrew 自身服务的与 Capistrano 无关。远程服务器最低要求OpenSSH 7.2支持-o ConnectTimeout10等参数Bash 4.0支持数组和关联数组Git 2.10支持git archive快速打包注意Capistrano 不依赖服务器上的 Ruby即使服务器只有 Python 2.7 和 Perl它依然能工作。它只是通过 SSH 执行 shell 命令。那些要求服务器装 Ruby 的教程是在教你怎么用 Capistrano 部署 Ruby 应用而非 Capistrano 本身的运行条件。3.2 初始化项目5 分钟创建可运行的部署骨架进入你的应用根目录如my-rails-app/执行以下命令# 1. 初始化 Gemfile如果还没有 echo source https://rubygems.org Gemfile echo gem capistrano, ~ 3.17 Gemfile echo gem capistrano-rails, ~ 1.6 Gemfile echo gem capistrano-bundler, ~ 1.6 Gemfile echo gem capistrano-rbenv, ~ 2.2 Gemfile # 2. 安装依赖 bundle install # 3. 生成 Capistrano 骨架 bundle exec cap install STAGESproduction这会生成Capfile入口文件加载核心模块config/deploy.rb全局配置部署路径、仓库地址等config/deploy/production.rb生产环境专属配置服务器地址、用户等现在编辑config/deploy.rb填入关键参数# config/deploy.rb # 应用名称用于生成目录名 set :application, my-rails-app # Git 仓库地址支持 HTTPS 或 SSH set :repo_url, gitgithub.com:yourname/my-rails-app.git # 部署到服务器的路径绝对路径 set :deploy_to, /var/www/my-rails-app # 默认分支建议用 main 或 release/* set :branch, main # 是否启用 SCM 缓存加快 git archive set :scm, :git set :format, :pretty set :log_level, :debug set :keep_releases, 5 # 保留最近 5 个 release # 加载 Rails 相关任务数据库迁移、资产编译等 require capistrano/rails require capistrano/bundler require capistrano/rbenv # 设置 rbenv 路径如果服务器用 rbenv 管理 Ruby set :rbenv_type, :user set :rbenv_ruby, 3.0.6接着配置生产服务器config/deploy/production.rb# config/deploy/production.rb # 服务器列表支持多台用数组 server 192.168.1.100, user: deploy, roles: %w{web app db}, primary: true # 如果 SSH 端口不是 22需指定 # server 192.168.1.100, user: deploy, port: 2222, roles: %w{web app db} # 设置 rbenv 路径与服务器实际路径一致 set :rbenv_path, /home/deploy/.rbenv # 设置 bundle 路径避免权限问题 set :bundle_path, - { #{fetch(:shared_path)}/bundle } # 设置 bundle bin 路径 set :bundle_bin, - { #{fetch(:bundle_path)}/bin/bundle } # 关键禁用本地 Git 检查避免本地未提交导致部署失败 set :git_strategy, Capistrano::Git::CopyStrategy实操心得Capistrano::Git::CopyStrategy是新手救命设置。默认策略会尝试在服务器上执行git clone但若服务器没配 Git 凭据或网络不通部署必败。CopyStrategy改为在本地git archive打包再scp上传100% 可靠。代价是上传体积略大但换来的是稳定性。3.3 首次部署执行、观察、验证三步法执行首次部署前确保服务器已创建部署用户并配置好权限# 在服务器上执行以 root 身份 useradd -m -s /bin/bash deploy mkdir -p /var/www/my-rails-app chown -R deploy:deploy /var/www/my-rails-app # 安装必要工具Ubuntu/Debian apt update apt install -y git curl nginx然后回到本地执行# 1. 测试连接检查 SSH 和基础路径 bundle exec cap production deploy:check # 2. 执行完整部署会自动执行 check → deploy → finish bundle exec cap production deploy # 3. 查看详细日志定位问题 bundle exec cap production deploy --tracedeploy:check会依次验证SSH 连接是否可达ssh deploy192.168.1.100 echo test部署目录是否存在且可写ls -ld /var/www/my-rails-appshared/目录是否已创建ls -la /var/www/my-rails-app/sharedGit 是否可用git --version如果全部通过deploy会按顺序执行git archive打包本地代码 →scp上传到服务器/tmp解压到releases/20240520120000/创建shared/下的符号链接config/database.yml→shared/config/database.yml运行bundle install --deployment安装到shared/bundle运行rake assets:precompile编译前端资源运行rake db:migrate执行数据库迁移创建current符号链接指向新 release重启应用服务需自定义deploy:restart任务注意第 6 步db:migrate是危险操作Capistrano 默认不自动执行。你必须在Capfile中显式加载require capistrano/rails/migrations并在config/deploy.rb中添加set :migration_role, :db。这是故意为之的安全设计——数据库变更必须由人确认。3.4 自定义关键任务让部署真正贴合你的架构Capistrano 的威力在自定义任务。以下是三个生产环境必备的 recipe 示例1. 安全的数据库迁移任务带人工确认在lib/capistrano/tasks/db.rake中namespace :deploy do desc Run database migrations with confirmation task :migrate do on roles(:db) do |host| # 询问确认仅在本地终端显示 if ENV[AUTO_MIGRATE] ! true puts \n⚠️ WARNING: This will run rake db:migrate on #{host.hostname} print Type YES to continue, or CtrlC to abort: answer STDIN.gets.chomp abort Migration cancelled. unless answer YES end within current_path do with rails_env: fetch(:rails_env, production) do execute bundle exec rake db:migrate end end end end end # 让 migrate 成为 deploy 的前置任务 before deploy:symlink:shared, deploy:migrate使用时cap production deploy会暂停等待输入cap production deploy AUTO_MIGRATEtrue则跳过确认用于 CI。2. Nginx 配置热重载在lib/capistrano/tasks/nginx.rake中namespace :nginx do desc Upload and reload Nginx config task :reload do on roles(:web) do # 上传本地 config 到服务器 upload! config/nginx.conf, /etc/nginx/sites-available/my-rails-app # 创建软链接 execute ln -sf /etc/nginx/sites-available/my-rails-app /etc/nginx/sites-enabled/my-rails-app # 测试配置并重载 execute nginx -t systemctl reload nginx end end end # 部署完成后自动重载 after deploy:published, nginx:reload3. 健康检查与自动回滚在lib/capistrano/tasks/health.rake中namespace :deploy do desc Check application health after deploy task :health_check do on roles(:web) do # 检查进程是否存活 result capture(systemctl is-active --quiet my-rails-app echo ok || echo failed) if result.strip ! ok raise Application process not running! end # 检查 HTTP 响应码 http_result capture(curl -s -o /dev/null -w %{http_code} http://localhost:3000/health) if http_result.strip ! 200 raise Health check failed: HTTP #{http_result} end end end end # 部署成功后执行健康检查 after deploy:published, deploy:health_check # 如果健康检查失败自动回滚需在 Capfile 中 require capistrano/deploy after deploy:health_check, deploy:rollback这些任务不是“锦上添花”而是生产环境的生存必需品。它们把 Capistrano 从“代码搬运工”升级为“部署守门员”。4. 常见问题排查从报错信息反推根本原因的实战方法论4.1 连接类错误SSH 权限与网络策略的精准诊断典型报错Net::SSH::AuthenticationFailed: Authentication failed for user deploy192.168.1.100Capistrano::CommandError: bundle exit status: 127 (SSH command failed!)这不是 Capistrano 的 bug而是 SSH 层的权限断层。排查必须按 OSI 模型自下而上物理层确认服务器 IP 和端口可达ping 192.168.1.100→telnet 192.168.1.100 22若 telnet 不通检查防火墙ufw status或iptables -L认证层验证 SSH 密钥是否正确加载ssh -T deploy192.168.1.100应显示 Welcome to Ubuntu...若失败检查本地~/.ssh/id_rsa.pub是否已用ssh-copy-id上传服务器~deploy/.ssh/authorized_keys是否包含该公钥服务器/etc/ssh/sshd_config中PubkeyAuthentication yes和PasswordAuthentication no是否启用会话层确认用户权限和 Shellssh deploy192.168.1.100 whoami; echo $SHELL; ls -la ~/关键点deploy用户必须有/bin/bash不能是/usr/sbin/nologin~deploy/目录权限必须是700chmod 700 /home/deploy~deploy/.ssh/权限必须是700authorized_keys必须是600实操心得90% 的连接失败源于authorized_keys权限错误。Capistrano 的 SSH 会话比普通ssh更严格它要求密钥文件权限必须是600否则静默拒绝。用ssh -vvv deployserver查看详细日志搜索Offering public key和Authentication succeeded即可定位。4.2 权限类错误Linux 文件系统权限的深度解析典型报错Capistrano::CommandError: mkdir exit status: 1 (SSH command failed!)Errno::EACCES: Permission denied dir_s_mkdir - /var/www/my-rails-app/releases这是 Linux 文件系统权限的经典陷阱。Capistrano 的部署用户deploy必须对deploy_to目录拥有完全控制权但很多人只给了chown deploy:deploy却忽略了父目录的sticky bit。正确做法# 在服务器上执行 mkdir -p /var/www/my-rails-app chown deploy:deploy /var/www/my-rails-app # 关键设置 setgid 位确保新创建目录继承组权限 chmod gs /var/www/my-rails-app # 设置 umask确保新文件组可写 echo umask 002 /home/deploy/.bashrc更深层的问题是shared/目录的权限。Capistrano 会在shared/下创建log/、tmp/等目录这些目录必须被应用进程如 Puma读写。常见错误配置错误配置后果正确方案shared/log/权限755Puma 无法写日志chmod 775 shared/log/ chgrp www-data shared/log/shared/tmp/所有者root应用无法创建 sessionchown deploy:www-data shared/tmp/ chmod 775 shared/tmp/shared/config/包含敏感文件Git 泄露数据库密码shared/config/database.yml必须由运维手动创建Capistrano 只负责软链接提示用getfacl /var/www/my-rails-app查看详细 ACL 权限。Capistrano 的:linked_files和:linked_dirs配置会自动处理软链接但前提是源目录存在且权限正确。4.3 Ruby 环境类错误服务器端 Ruby 管理的黄金法则典型报错bash: bundle: command not foundYour Ruby version is 2.5.1, but your Gemfile specified 3.0.6这是新手最易踩的坑混淆了“Capistrano 运行环境”和“应用运行环境”。Capistrano 本身不依赖服务器 Ruby但你的应用如 Rails需要。解决方案不是在服务器装 RVM而是用capistrano-rbenv统一管理在服务器上安装 rbenvsu - deploy curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/install.sh | bash echo export PATH$HOME/.rbenv/bin:$PATH ~/.bashrc echo eval $(rbenv init - bash) ~/.bashrc source ~/.bashrc安装指定 Ruby 版本rbenv install 3.0.6 rbenv global 3.0.6 gem install bundler -v ~ 2.3在config/deploy.rb中确认配置set :rbenv_type, :user set :rbenv_ruby, 3.0.6 set :rbenv_prefix, - { RBENV_ROOT#{fetch(:rbenv_path)} RBENV_VERSION#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec } set :rbenv_map_bins, %w{rake gem bundle ruby rails}关键点rbenv_prefix确保每个 Capistrano 任务都显式指定 Ruby 环境避免which ruby返回系统默认版本。4.4 部署失败后的回滚与状态修复当cap production deploy中途失败服务器会处于“半部署”状态。此时cap production deploy:rollback是首选但它只回滚current链接不清理失败的 release 目录。手动清理步骤# 在服务器上执行 cd /var/www/my-rails-app # 查看所有 releases ls -lt releases/ # 删除失败的 release如 20240520120000 rm -rf releases/20240520120000 # 强制重置 current如果 rollback 未生效 rm current ln -s releases/20240519100000 current # 清理临时文件 rm -f /tmp/capistrano*更彻底的状态修复脚本保存为fix-deploy-state.sh#!/bin/bash DEPLOY_PATH/var/www/my-rails-app CURRENT$(readlink -f $DEPLOY_PATH/current) LATEST_RELEASE$(ls -t $DEPLOY_PATH/releases/ | head -1) LATEST_PATH$DEPLOY_PATH/releases/$LATEST_RELEASE echo Current points to: $CURRENT echo Latest release: $LATEST_PATH if [ $CURRENT ! $LATEST_PATH ]; then echo Fixing current symlink... rm $DEPLOY_PATH/current ln -s $LATEST_PATH $DEPLOY_PATH/current fi # 清理空 release 目录 find $DEPLOY_PATH/releases/ -maxdepth 1 -type d -empty -delete echo State fixed.实操心得我曾在支付系统上线时遭遇bundle install因网络超时中断导致shared/bundle目录不完整。Capistrano 的deploy:cleanup任务无法识别这种“部分成功”状态。最终方案是在deploy:updated后添加自定义任务校验shared/bundle下关键 gem 是否存在test -f shared/bundle/ruby/3.0.0/gems/rails-7.0.8/lib/rails.rb否则中止部署。这种防御性编程是生产环境的标配。5. 进阶实践超越基础部署的工程化能力扩展5.1 多环境协同Staging、Production、Canary 的配置隔离策略Capistrano 的STAGES机制天生支持多环境但关键是如何避免配置污染。我们采用“三层配置法”第一层全局基础配置config/deploy.rb定义所有环境共用的参数application、repo_url、scm、keep_releases。这里不写任何环境相关路径或凭据。第二层环境专属配置config/deploy/staging.rb,config/deploy/production.rb只写差异项staging.rbserver staging.example.com, roles: %w{web app db}set :rails_env, stagingproduction.rbserver prod1.example.com, roles: %w{web},server prod2.example.com, roles: %w{app db}set :rails_env, production第三层敏感凭据隔离config/deploy/production-secrets.rb此文件绝不提交 Git由运维单独维护# config/deploy/production-secrets.rb set :database_password, prod-db-pass-2024 set :aws_access_key_id, AKIA... set :aws_secret_access_key, ... # 加载此文件在 production.rb 末尾 load ./config/deploy/production-secrets.rb if File.exist?(./config/deploy/production-secrets.rb)执行时cap production deploy会自动加载production-secrets.rb如果存在而cap staging deploy则不会加载彻底隔离风险。5.2 与 CI/CD 工具链集成GitHub Actions 的极简实现Capistrano 与 GitHub Actions 是天作之合——Actions 负责构建验证Capistrano 负责安全交付。.github/workflows/deploy.yml示例name: Deploy to Production on: push: branches: [main] paths: [**/*.rb, **/*.erb, **/config/**/*] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Set up Ruby uses: ruby/setup-rubyv1 with: ruby-version: 3.0.6 - name: Install dependencies run: bundle install - name: Run tests run: bundle exec rspec - name: Deploy to production # 使用 ssh-agent 管理密钥 uses: webfactory/ssh-agentv0.8.0 with: ssh-private-key: ${{ secrets.PROD_SSH_KEY }} - name: Execute Capistrano # 关键传递环境变量控制行为 run: bundle exec cap production deploy AUTO_MIGRATEtrue env: SSH_USER: ${{ secrets.PROD_SSH_USER }} SSH_HOST: ${{ secrets.PROD_SSH_HOST }}这里的关键设计AUTO_MIGRATEtrue绕过人工确认CI 中必须自动ssh-agent安全注入私钥避免硬编码paths过滤器确保只在代码变更时触发减少误部署5.3 部署可观测性从日志到指标的闭环监控Capistrano 本身不提供监控但它的钩子hooks是埋点的绝佳位置。我们在lib/capistrano/tasks/monitoring.rake中namespace :deploy do before deploy:starting, monitoring:alert_start after deploy:finished, monitoring:alert_success after deploy:failed, monitoring:alert_failure task :alert_start do on roles(:all) do # 发送部署开始通知Slack cmd curl -X POST -H Content-type: application/json --data {\text\:\ Deploy started for #{fetch(:application)} on #{Time.now.strftime(%H:%M)}\} https://hooks.slack.com/services/XXX execute cmd end end task :alert_success do on roles(:all) do # 发送成功通知 新旧版本对比 old_release capture(readlink -f /var/www/#{fetch(:application)}/current).strip.split(/).last new_release capture(ls -t /var/www/#{fetch(:application)}/releases/ | head -1).strip cmd curl -X POST -H Content-type: application/json --data {\text\:\✅ Deploy success! From #{old_release} to #{new_release}. https://grafana.example.com/d/abc/deployment|View metrics\} https://hooks.slack.com/services/XXX execute cmd # 推送 Prometheus 指标需服务器装 node_exporter execute echo capistrano_deploy_success{env\production\} 1 | nc -w 1 prometheus:9091 end end end配合 Grafana 看板我们能实时看到每次部署耗时从 deploy