Skip to content

第 25 章 · 部署与运维 · 高频面试题

部署题虽然不是每场必问,但问到一道就是劝退题——能答上来直接显得你"会用"而不止"会写"。


1. 静态部署平台对比?怎么选?

平台优势劣势适合
VercelNext.js 最佳、Edge Function国内慢、商业付费全球 SaaS
NetlifyForm / Identity 一站式国内慢小型站
Cloudflare Pages全球最快、免费 / 无限带宽UI 一般个人项目
GitHub Pages免费 / 集成 GitHub仅静态、CN 极慢文档站
腾讯/阿里 OSS+CDN国内速度好要自配 CDN/HTTPS国内商业项目

选型思路

  • 用户在国外 → Vercel / Cloudflare Pages
  • 用户在国内 → 腾讯 EdgeOne / 阿里 OSS+CDN / Cloudflare(全球都快)
  • 个人简历项目 → Cloudflare Pages(免费、全球可访问)

2. SPA 部署到 Nginx,刷新页面 404 怎么解决?

原因history 模式的 URL(如 /about)在服务器上没有对应文件,刷新会真实请求服务器找不到。

解决:所有不存在的路径 fallback 到 index.html,让前端路由接管。

nginx
server {
  root /var/www/dist;
  location / {
    try_files $uri $uri/ /index.html;
  }
}

try_files 的语义:

  1. 先找 $uri(如 /about/var/www/dist/about
  2. 再找 $uri/(目录)
  3. 都没有 → 返回 /index.html(200,不是 404)

Apache.htaccess

apache
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

Cloudflare Pages / Vercel / Netlify:天然支持,不用配。


3. CDN 的工作原理?发布后如何确保用户拿到新版本?

原理

用户 → DNS 解析到最近边缘节点

        ├── 命中缓存 → 直接返回

        └── 未命中 → 回源(去源站拉)→ 缓存 → 返回

确保用户拿到新版本的两种方式

方式 1(推荐):hash 文件名 + 长缓存

app.a3f9c4.js  ← 内容变了,hash 变了,URL 变了
              ← CDN 视为新文件,自动从源站拉

文件名变 = 新文件,根本不存在"刷新缓存"问题。HTML 里引用新名字即可。

方式 2:主动刷新(Purge)

调 CDN API 把旧资源从所有节点驱逐。代价:刷新后第一次访问要回源,慢。

部署顺序:先发静态资源(CDN 同步好),再发 HTML(源站)。否则用户拿到新 HTML 引用了 CDN 还没有的 JS → 404。


4. Docker 多阶段构建是什么?为什么用?

问题:单阶段构建会把 node_modules、构建工具、源码都打进最终镜像,几百 MB。

多阶段构建:分多个 FROM,前面阶段只做构建,后面阶段只 COPY --from=... 取产物。

dockerfile
# 阶段 1:构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 阶段 2:仅 nginx + dist 产物
FROM nginx:1.25-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

收益

维度单阶段多阶段
镜像体积800MB30MB
启动速度
安全性含构建工具极简
拉取速度

5. 写一个 GitHub Actions 自动部署的 workflow。

yaml
name: CI/CD

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm run test

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
        env:
          VITE_API_URL: ${{ secrets.API_URL }}

      - name: Upload to Cloudflare Pages
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}
          projectName: my-app
          directory: dist

要点

  • test 和 deploy 分开,PR 只跑 test
  • deploy 依赖 testneeds: test
  • secrets 存敏感配置
  • cache: 'npm' 加速依赖安装

6. 缓存策略怎么配?为什么 HTML 要 no-cache?

资源类型推荐策略原因
index.htmlCache-Control: no-cache必须实时拿到最新版(指向新 hash)
app.[hash].js/cssCache-Control: max-age=31536000, immutable文件名变 = 新文件
图片Cache-Control: max-age=259200030 天,平衡更新和性能
动态 APICache-Control: no-store永不缓存

为什么 HTML 必须 no-cache

如果 HTML 强缓存了 1 小时,发布新版后老用户:

  1. 浏览器用 1 小时前的 HTML
  2. HTML 引用旧的 app.abc.js
  3. 旧的 JS 可能调用新 API,参数不对 → 报错

no-cache 不是"不缓存",而是"必须协商缓存"——发请求带 If-None-Match,服务端返回 304 也算命中。


7. 灰度发布有哪些方式?

方式实现适合
按比例Nginx split_clients / 网关按 hash 分流通用
按 Cookie给特定用户植入 Cookie,路由到新版本内部测试
按地域CDN 边缘节点按 IP 切流大型公司
按用户 IDuser_id % 100 < 5 命中灰度业务层灰度
蓝绿部署两套环境(蓝/绿),LB 整体切换极致快速回滚

Nginx 按 IP 灰度示例:

nginx
split_clients "${remote_addr}${http_user_agent}" $version {
    5%   "v2";       # 5% 用户走 v2
    *    "v1";
}
upstream v1 { server 10.0.0.1; }
upstream v2 { server 10.0.0.2; }
server {
    location / { proxy_pass http://$version; }
}

8. Nginx 反向代理 vs 正向代理?

正向代理:
   客户端 → [代理] → 目标服务器
   客户端知道有代理;服务端不知道
   场景:科学上网、隐藏客户端 IP

反向代理:
   客户端 → [代理] → 后端服务器
   客户端不知道;服务端知道(其实代理就是它的"门卫")
   场景:负载均衡、SSL 终止、缓存、隐藏内部架构

Nginx 主要做反向代理:把外部请求转发到内部多台后端服务器。


9. 怎么实现"一键回滚"?

核心思路:每次发布带版本号,保留若干历史版本,回滚 = 把指针指回去。

实战方案 1:CDN 路径 + 软链

/cdn/dist/v20251018-1023/  ← 当前
/cdn/dist/v20251015-0912/  ← 上一版本(保留 7 天)
/cdn/dist/current  ── 软链 ──► v20251018-1023/

回滚 = ln -sfn v20251015-0912 current,CDN 缓存几分钟自动失效。

实战方案 2:K8s 镜像版本回滚

bash
# 当前版本
kubectl set image deploy/app web=registry/app:v20251018-1023

# 一行回滚
kubectl rollout undo deploy/app

实战方案 3:Nginx upstream 切换

nginx
upstream backend {
    server 10.0.0.1;  # v2,注释掉就回滚
    # server 10.0.0.2;  # v1
}

好的回滚预案

  1. 一键脚本 / 一个按钮
  2. 5 分钟内完成
  3. 每月演练
  4. 数据库变更单独管理(DB schema 不能简单回滚)

10. 怎么做前端错误监控?

捕获时机

js
// 1. JS 同步错误
window.addEventListener('error', e => {
  report({
    type: 'js',
    msg: e.message,
    stack: e.error?.stack,
    url: e.filename,
    line: e.lineno,
    col: e.colno,
  });
});

// 2. Promise 未捕获 rejection
window.addEventListener('unhandledrejection', e => {
  report({ type: 'promise', msg: e.reason?.message, stack: e.reason?.stack });
});

// 3. 资源加载失败(image/script/link)
window.addEventListener('error', e => {
  if (e.target !== window) {
    report({ type: 'resource', tag: e.target.tagName, src: e.target.src || e.target.href });
  }
}, true); // 注意:用捕获阶段,资源错误不冒泡

// 4. 接口失败:在 axios/fetch 拦截器里上报

上报方式:用 navigator.sendBeacon —— 即使页面关了也能发出去。

还要做

  • Source Map 解析:把混淆代码栈还原成源码(私有上传到 Sentry)
  • 去重 + 聚合:同样的错误不要重复上报
  • 采样率:错误太多时按比例采样
  • 业务标签:上报时带 user_id、route、version,方便排查

工具:Sentry(业界标杆)、阿里 ARMS、字节火山引擎、自建。


11. (进阶)说说蓝绿部署 vs 滚动更新 vs 灰度发布的区别。

策略做法优势劣势
蓝绿部署准备两套环境(蓝、绿),LB 整体切换回滚快(秒级)资源 ×2,成本高
滚动更新一台台机器替换(K8s 默认策略)资源占用低中间状态会有两个版本共存
灰度发布按比例/用户/地域慢慢放量风险可控周期长,要观察期
金丝雀部署灰度发布的细化(先 1%,观察后 5%,再 20% ...)极度安全操作复杂

实战建议

  • 小团队:滚动更新 + 监控告警
  • 中等规模:蓝绿 + 一键切换
  • 大厂:金丝雀 + 全链路灰度

12. (进阶)什么是 Edge Function / Serverless?跟传统部署有啥区别?

维度传统服务器ServerlessEdge Function(Workers/Edge)
运行位置中心机房中心机房全球边缘节点(200+ 城市)
启动延迟常驻运行冷启动 100ms~几秒冷启动 < 5ms
计费按时间按请求按请求
限制任意单次执行 < 几分钟通常 < 50ms CPU
适用持久任务API、定时任务鉴权、A/B 测试、个性化首页

前端典型用途

  • Edge Function 重写 HTML:根据用户地理位置动态注入语言、广告
  • Edge 鉴权:在最近的节点拦截未登录请求
  • Edge SSR:Vercel / Cloudflare Workers + Next.js / SvelteKit 在边缘渲染

一句话总结

部署面试套路:怎么部署(Vercel/Nginx/Docker)→ 怎么加速(CDN)→ 怎么自动化(CI/CD)→ 怎么不翻车(灰度+回滚)→ 怎么发现问题(监控)——能讲完这条链,就证明你不只是"写代码的",是能把项目"跑起来"的工程师。