Vue 3 项目经过 Vite 或 webpack 构建之后,产物通常是纯静态资源,团队在规模较小时往往直接扔到一台 Nginx 机器上就算部署完成。但当前端应用数量增多、发布频率变高,手工维护服务器的方式就会暴露出无法水平扩展、发布回滚慢、故障恢复靠人肉等问题。把 Vue 3 的构建产物打包成 Docker 镜像,再交给 Mesos 加 Marathon 这套经典组合去做调度和编排,可以让前端发布也享受容器生态带来的弹性能力。本文将完整讲解这套方案的落地过程。

一、Vue 3 项目构建与镜像化
容器化的第一步是把 Vue 3 项目的构建产物固化到镜像里。推荐使用多阶段构建:第一阶段用 Node 镜像执行依赖安装和构建,第二阶段把 dist 目录复制进 Nginx 镜像,最终镜像里不含 node_modules,体积可以控制在几十 MB 以内。
# 构建镜像 docker build -t registry.ippipp.com/my-vue-app:1.4.0 . # 本地验证运行 docker run -d -p 8080:80 registry.ippipp.com/my-vue-app:1.4.0 # 推送到私有仓库 docker push registry.ippipp.com/my-vue-app:1.4.0
对应的 Dockerfile 与 Nginx 配置如下。注意 Vue 3 使用 HTML5 路由时必须配置 try_files 回退到 index.html,否则刷新页面会出现 404:
FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80
# nginx.conf 核心片段
server {
listen 80;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html;
}
}
镜像打好后务必推送到 Mesos 集群各节点都能访问的镜像仓库。如果是自签证书的私有仓库,需要在所有 Agent 警节点的 Docker daemon 配置中加入 insecure-registries,否则调度到某些节点时会因为拉取镜像失败而一直处于 staging 状态。
二、编写 Marathon 应用定义
Marathon 通过一份 JSON 描述应用的全部运行参数。下面是一份适用于 Vue 3 静态站点的定义,重点在于容器配置、端口映射和健康检查三部分:
{
"id": "/frontend/vue-app",
"container": {
"type": "DOCKER",
"docker": {
"image": "registry.ippipp.com/my-vue-app:1.4.0",
"network": "BRIDGE",
"portMappings": [
{ "containerPort": 80, "hostPort": 0, "protocol": "tcp" }
]
}
},
"cpus": 0.2,
"mem": 128,
"instances": 3,
"healthChecks": [
{
"protocol": "HTTP",
"path": "/healthz",
"portIndex": 0,
"gracePeriodSeconds": 30,
"intervalSeconds": 10,
"timeoutSeconds": 5
}
],
"upgradeStrategy": {
"minimumHealthCapacity": 0.5,
"maximumOverCapacity": 0.5
}
}
几个字段值得展开说明。hostPort 设为 0 表示由 Marathon 动态分配宿主机端口,配合外部负载均衡按服务发现地址转发即可;如果前端需要被网关按固定端口访问,也可以指定具体端口,但要确保不与节点上其他任务冲突。cpus 和 mem 对纯静态服务来说给得很小就够,Nginx 处理静态文件的资源消耗远低于 Node 应用。
健康检查路径 /healthz 需要在 Nginx 中额外配置一个返回 200 的空 location,例如 location = /healthz { return 200; }。没有健康检查的反馈,Marathon 就无法判断新实例是否真正就绪,滚动更新时可能出现短暂的全量不可用。提交定义的方式很简单:
curl -X POST http://marathon-host:8080/v2/apps \ -H "Content-Type: application/json" \ -d @vue-app.json
三、滚动更新、回滚与弹性伸缩
发布新版本时只需更新镜像版本号再 PUT 同一个应用 ID,Marathon 会根据 upgradeStrategy 的容量策略逐步替换旧实例。比如 minimumHealthCapacity 为 0.5 时,更新过程中始终保持至少一半实例在线,新实例健康检查通过后才杀掉对应的旧实例,前端用户几乎感知不到发布动作。
回滚同样轻量,把镜像 tag 改回旧版本提交即可。如果搭配 CI 流水线,可以在 Git tag 上记录每次发布的版本,出现线上问题时一条命令完成回退。相比直接在虚拟机上覆盖文件的发布方式,这种以镜像为单位的版本管理粒度清晰得多。
伸缩也是 Marathon 的强项。遇到大促或流量高峰,把 instances 从 3 改到 10,Mesos 会自动在有空余资源的节点上拉起新容器;流量回落后再改回去释放资源。这套机制让前端静态服务第一次具备了和后端服务相同的弹性能力,而不再需要提前按峰值采购机器。
四、常见问题排查
部署过程中最常见的问题有三类。第一类是任务一直卡在 staging,多半是镜像拉取失败,可以到对应 Agent 节点手动执行 docker pull 验证网络和证书配置;第二类是健康检查持续失败导致实例被反复重启,要确认容器内端口、Nginx 监听端口与 portMappings 三者一致,以及 gracePeriodSeconds 是否给得太短。
第三类是路由刷新 404 或资源路径错误,这属于 Vue 3 侧的问题而非 Marathon 的问题。除了前面提到的 try_files 配置,如果项目使用了相对路径的 publicPath,部署在子路径下时资源会加载失败,构建时应通过环境变量把 base 设置正确。
排查命令方面,curl http://marathon-host:8080/v2/apps/frontend/vue-app 能看到每个实例的状态和主机分配情况,结合 Mesos UI 的 sandbox 日志可以快速定位容器启动阶段的报错。把这些观测手段固化到运维文档里,后续交接和应急处理都会顺畅很多。
整体来看,Vue 3 加 Marathon 的组合本质上是把前端工程纳入统一的容器调度体系,构建、发布、回滚、伸缩形成闭环。虽然 Mesos 生态如今不如 Kubernetes 活跃,但对于已经在运行 Mesos 集群的团队来说,这套方案的迁移成本低、上手快,依然是值得采用的工程化路径。