使用 grunt-ssh 将构建产物上传到蓝绿服务器并切换负载均衡

解读

这道题考察的是“把 Grunt 从纯构建工具升级为持续交付入口”的能力。国内一线、二线公司普遍采用蓝绿发布来降低停机风险,而 grunt-ssh 是早期 Node 栈团队最常用的低成本部署插件。面试官想知道:

  1. 你是否理解“蓝绿”在**国内云厂商(阿里云 SLB、腾讯云 CLB、华为云 ELB)**上的真实含义;
  2. 能否用 Grunt 生态把“上传 → 健康检查 → 切换流量”三步串成自动化脚本;
  3. 是否具备回滚、并发、幂等、密钥分级存储等工程化意识。

答得太浅(只传文件)会被认为“只能跑任务,不会交付”;答得太深(直接撸 SDK 重写插件)又容易被质疑“过度设计”。因此要把 grunt-ssh 用到极限,同时把**不可控部分(切换 SLB)**交给官方 CLI 或 OpenAPI,让面试官看到你“边界清晰、风险可控”。

知识点

  1. grunt-ssh 双任务模型sftp 上传文件、sshexec 远程执行命令,二者共用 grunt-ssh 一个 npm 包。
  2. 蓝绿角色划分
    • 绿组:当前生产流量所在;
    • 蓝组:待上线新版本,上传完毕后做健康探活,通过后再把 SLB 权重切到蓝组。
  3. 国内 SLB 切换的三种姿势
    • 权重置零:把旧组权重改 0,新组权重改 100,零 downtime;
    • 解绑/绑定后端服务器:适合经典网络 ECS;
    • 切换虚拟服务器组:适合 VPC 场景,API 为 ModifyVServerGroupBackendServers
  4. Grunt 并发控制grunt-concurrentthis.async() 保证“上传并行、命令串行、切换串行”。
  5. 密钥安全
    • 本地使用 grunt-sshagent 选项指向 ssh-agent,避免私钥落盘;
    • 生产环境通过阿里云 OSS 临时 STS 令牌腾讯云 SSM 下发密钥,拒绝把 password 写死在 Gruntfile。
  6. 健康检查与回滚
    • 上传后触发 curl -f http://蓝组IP:端口/health
    • 三次重试失败立即执行 grunt.registerTask('rollback', ['sshexec:rollback']),把 SLB 权重改回原组。
  7. 日志与通知:使用 grunt-log-adapter 把输出同步到钉钉群机器人飞书 webhook,满足国内“发布必通知”的合规要求。

答案

  1. 安装依赖
npm i -D grunt-ssh grunt-concurrent grunt-contrib-watch
  1. Gruntfile.js 中定义蓝绿两组服务器SLB 配置
const aliyunSLB = require('@alicloud/slb20140515'); // 官方 SDK
const accessKey = process.env.ALY_KEY;             // 通过 CI 变量注入
const accessSecret = process.env.ALY_SECRET;

const blue = { host: '172.16.0.4', sshUser: 'work', sshPort: 22, webRoot: '/data/web' };
const green = { host: '172.16.0.5', sshUser: 'work', sshPort: 22, webRoot: '/data/web' };
const slbId = 'lb-2ze3j4ue8k9';                    // 负载均衡实例 ID
const current = 'green';                           // 当前生产组,由 CMDB 提供
const standby = current === 'green' ? 'blue' : 'green';
  1. 封装上传任务
grunt.initConfig({
  sftp: {
    deploy: {
      files: [{
        cwd: 'dist/',
        src: '**',
        dest: `/${standby === 'blue' ? blue.webRoot : green.webRoot}/`,
        filter: 'isFile'
      }],
      options: {
        host: standby === 'blue' ? blue.host : green.host,
        username: standby === 'blue' ? blue.sshUser : green.sshUser,
        port: 22,
        agent: process.env.SSH_AUTH_SOCK, // 使用本地 ssh-agent
        createDirectories: true,
        showProgress: true
      }
    }
  }
});
  1. 封装健康检查与流量切换
sshexec: {
  health: {
    command: 'curl -sf http://localhost/health || exit 1',
    options: {
      host: standby === 'blue' ? blue.host : green.host,
      username: standby === 'blue' ? blue.sshUser : green.sshUser,
      agent: process.env.SSH_AUTH_SOCK
    }
  },
  rollback: {
    command: 'echo "rollback placeholder"', // 实际调用 SLB SDK 把权重改回原组
    options: { /*同上*/ }
  }
}
  1. 注册串行任务链
grunt.registerTask('upload', ['sftp:deploy']);
grunt.registerTask('check', ['sshexec:health']);
grunt.registerTask('switch', function() {
  const done = this.async();
  const client = new aliyunSLB.default({ accessKeyId: accessKey, accessKeySecret: accessSecret });
  // 把 standby 组权重改 100,原组改 0
  client.modifyVServerGroupBackendServers({
    VServerGroupId: 'vsp-xxx',
    BackendServers: JSON.stringify([
      { ServerId: standby === 'blue' ? 'i-blue' : 'i-green', Weight: 100 },
      { ServerId: standby === 'blue' ? 'i-green' : 'i-blue', Weight: 0 }
    ])
  }).then(() => { grunt.log.ok('SLB 切换完成'); done(); })
    .catch(e => { grunt.fail.fatal('SLB 切换失败: ' + e); done(false); });
});

grunt.registerTask('deploy', function() {
  grunt.task.run(['upload', 'check', 'switch']);
});
  1. 在 CI(如阿里云云效、GitLab-Runner)里执行
grunt deploy --verbose

若健康检查失败,CI 自动捕获非 0 退出码并触发 grunt rollback,实现一键回滚

拓展思考

  1. 灰度与分批:蓝绿发布虽然零停机,但流量一次性全切风险高;可结合 SLB 的“权重灰度”能力,把任务拆成 switch:10%switch:50%switch:100%,用 Grunt 的 grunt-task-helper 动态传参,实现“蓝绿 + 金丝雀”混合模式。
  2. 多地域复制:国内 App 需要华北-华东-华南三地域同时发布,可在 sftp 配置里使用 grunt-sshmultiple 选项,一次性并发上传 6 台服务器,再通过**阿里云全球流量管理(GTM)**统一切换,避免“单地域成功、多地域失败”的孤岛问题。
  3. 安全合规:金融、政企项目要求“密钥不落盘、操作可审计”,可把 grunt-sshprivateKey 字段换成阿里云 KMS 托管密钥,通过 kms:GetSecretValue 实时拉取,实现“谁审批、谁使用、谁负责”的链路闭环。