各位靓仔靓女,晚上好!我是今天的主讲人,很高兴能和大家聊聊 Vue SSR 应用在容器化环境下的部署那些事儿。今天咱们的目标,就是把 Vue SSR 应用装进 Docker,再搬到 Kubernetes 上溜达溜达,顺便让它跑得飞快!
第一章:SSR 与容器化的完美邂逅
首先,咱们得明白,为啥要把 Vue SSR 应用塞到容器里。
- 环境一致性: 保证开发、测试、生产环境一模一样,再也不怕“在我电脑上跑得好好的”这种玄学问题。
- 易于部署和扩展: Docker 镜像就像一个打包好的快递,随处可部署。Kubernetes 更是个超级调度员,能帮你自动扩容,应对突如其来的流量高峰。
- 资源隔离: 每个容器都像一个独立的小房间,互不干扰,保证应用安全稳定。
所以,SSR 和容器化,简直就是天作之合!
第二章:Docker 镜像构建:从零开始,打造专属战舰
咱们一步一步来,先创建一个简单的 Vue SSR 应用。
vue create ssr-demo
cd ssr-demo
vue add @vue/cli-plugin-typescript
vue add @vue/cli-plugin-eslint
vue add @vue/cli-plugin-vuex
vue add @vue/cli-plugin-router
vue add @vue/cli-plugin-pwa
vue add @vue/cli-plugin-unit-jest
vue add @vue/cli-plugin-e2e-cypress
vue add @vue/cli-plugin-babel
vue add @vue/cli-plugin-pwa
vue add @vue/cli-plugin-eslint
vue add @vue/cli-plugin-router
vue add @vue/cli-plugin-vuex
vue add @vue/cli-plugin-typescript
vue add @vue/cli-plugin-unit-jest
vue add @vue/cli-plugin-e2e-cypress
vue add @vue/cli-service
npm install vue vue-server-renderer vue-router vuex axios -S
npm install webpack webpack-dev-middleware webpack-hot-middleware cross-env -D
修改package.json
增加ssr启动命令
"scripts": {
"ssr:build": "npm run build && node build/setup-dev-server.js",
"ssr:start": "cross-env NODE_ENV=production node server.js"
},
添加一个简单的ssr服务端server.js
const express = require('express');
const fs = require('fs');
const { createBundleRenderer } = require('vue-server-renderer');
const app = express();
const template = fs.readFileSync('./dist/index.html', 'utf-8');
const bundle = require('./dist/vue-ssr-server-bundle.json');
const clientManifest = require('./dist/vue-ssr-client-manifest.json');
const renderer = createBundleRenderer(bundle, {
template,
clientManifest,
runInNewContext: false
});
app.use(express.static('./dist'));
app.get('*', (req, res) => {
const context = {
url: req.url
};
renderer.renderToString(context, (err, html) => {
if (err) {
console.error(err);
return res.status(500).send('Server Error');
}
res.send(html);
});
});
const port = 3000;
app.listen(port, () => {
console.log(`Server started at http://localhost:${port}`);
});
修改vue.config.js
增加ssr配置
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const nodeExternals = require('webpack-node-externals')
const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'
const target = TARGET_NODE ? 'server' : 'client'
module.exports = {
css: {
extract: false
},
configureWebpack: () => ({
entry: `./src/entry-${target}.js`,
devtool: 'source-map',
target: TARGET_NODE ? 'node' : 'web',
node: TARGET_NODE ? undefined : false,
output: {
libraryTarget: TARGET_NODE ? 'commonjs2' : undefined
},
externals: TARGET_NODE
? nodeExternals({
allowlist: [/.css$/]
})
: undefined,
optimization: {
splitChunks: undefined
},
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
}),
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
return Object.assign(options, {
optimizeSSR: false
})
})
}
}
修改src/app.vue
<template>
<div id="app">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
msg: 'Hello, Vue SSR!'
};
}
};
</script>
创建src/entry-client.js
import { createApp } from './main'
const { app, router } = createApp()
router.onReady(() => {
app.$mount('#app')
})
创建src/entry-server.js
import { createApp } from './main'
export default context => {
return new Promise((resolve, reject) => {
const { app, router } = createApp()
router.push(context.url)
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
if (!matchedComponents.length) {
return reject({ code: 404 })
}
resolve(app)
}, reject)
})
}
创建build/setup-dev-server.js
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const chokidar = require('chokidar')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const serverConfig = require('../webpack.config.js')
const clientConfig = require('../webpack.config.js')
module.exports = function setupDevServer (app, templatePath, cb) {
let bundle
let template
let clientManifest
const update = () => {
if (bundle && clientManifest) {
cb(bundle, {
template,
clientManifest
})
}
}
// 监听构建客户端的 webpack 配置
const clientCompiler = webpack(clientConfig)
const devMiddleware = webpackDevMiddleware(clientCompiler, {
publicPath: clientConfig.output.publicPath,
writeToDisk: true
})
app.use(devMiddleware)
clientCompiler.hooks.done.tap('webpack-client-done', () => {
clientManifest = JSON.parse(
fs.readFileSync(path.join(clientConfig.output.path, 'vue-ssr-client-manifest.json'), 'utf-8')
)
update()
})
// 热更新
app.use(webpackHotMiddleware(clientCompiler))
// 监听构建服务端的 webpack 配置
const serverCompiler = webpack(serverConfig)
serverCompiler.outputFileSystem = fs
serverCompiler.watch({}, (err, stats) => {
if (err) throw err
if (stats.hasErrors()) return
bundle = JSON.parse(fs.readFileSync(path.join(serverConfig.output.path, 'vue-ssr-server-bundle.json'), 'utf-8'))
update()
})
return update
}
好了,基础项目准备完毕。接下来,就是编写 Dockerfile
了!
# 使用 Node.js 官方镜像作为基础镜像
FROM node:16-alpine
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json
COPY package*.json ./
# 安装依赖
RUN npm install
# 复制项目文件
COPY . .
# 构建 SSR 应用
RUN npm run ssr:build
# 暴露端口
EXPOSE 3000
# 启动命令
CMD [ "npm", "run", "ssr:start" ]
咱们来解读一下:
FROM node:16-alpine
:选择一个轻量级的 Node.js 镜像,减少镜像体积。WORKDIR /app
:设置工作目录,后续操作都在/app
目录下进行。COPY package*.json ./
:复制package.json
和package-lock.json
,利用 Docker 缓存,加速依赖安装。RUN npm install
:安装项目依赖。COPY . .
:复制所有项目文件到镜像中。RUN npm run ssr:build
:构建 SSR 应用。EXPOSE 3000
:暴露 3000 端口,允许外部访问。CMD [ "npm", "run", "ssr:start" ]
:设置容器启动命令。
有了 Dockerfile
,咱们就可以构建镜像了!
docker build -t vue-ssr-app:latest .
这条命令会创建一个名为 vue-ssr-app
,标签为 latest
的镜像。
接下来,运行一下看看:
docker run -p 3000:3000 vue-ssr-app:latest
打开浏览器,访问 http://localhost:3000
,如果看到熟悉的 Vue SSR 页面,恭喜你,成功了!
第三章:Kubernetes 部署:让应用在云端起舞
有了 Docker 镜像,咱们就可以把它部署到 Kubernetes 上了。
首先,创建一个 Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vue-ssr-deployment
spec:
replicas: 2 # 副本数量
selector:
matchLabels:
app: vue-ssr-app
template:
metadata:
labels:
app: vue-ssr-app
spec:
containers:
- name: vue-ssr-container
image: vue-ssr-app:latest
ports:
- containerPort: 3000
这个 Deployment 会创建两个 Pod,每个 Pod 都运行一个 vue-ssr-app
容器。
然后,创建一个 Service:
apiVersion: v1
kind: Service
metadata:
name: vue-ssr-service
spec:
selector:
app: vue-ssr-app
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
这个 Service 会将外部流量转发到 vue-ssr-app
Pod 的 3000 端口。type: LoadBalancer
表示使用负载均衡器,方便外部访问。
将这两个 YAML 文件保存为 deployment.yaml
和 service.yaml
,然后执行以下命令:
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
Kubernetes 就会按照你的指示,创建 Deployment 和 Service。稍等片刻,就可以通过 Service 的外部 IP 访问你的 Vue SSR 应用了!
第四章:性能优化:让你的 SSR 应用飞起来
光能跑起来还不够,咱们还得让它跑得更快!
-
缓存: SSR 应用的一大痛点就是性能。每次请求都重新渲染,CPU 压力山大。所以,缓存是必须的!
- 页面缓存: 将渲染好的 HTML 页面缓存起来,下次直接返回,避免重复渲染。可以使用 Redis、Memcached 等缓存方案。
- 组件缓存: Vue 提供了
keep-alive
组件,可以缓存组件状态,减少组件重新渲染的开销。 - CDN 缓存: 将静态资源(JS、CSS、图片)放到 CDN 上,加速访问速度。
-
代码优化: 代码写得好,性能自然高。
- 减少 Bundle 体积: 使用 Webpack 的 Code Splitting 功能,将代码分割成多个小块,按需加载。
- 优化组件渲染: 避免在组件中进行复杂的计算,尽量使用 Pure Components。
- 懒加载: 对于非首屏需要的组件,可以使用懒加载,减少首屏渲染时间。
-
服务器优化: 服务器性能也很重要。
- 使用高性能服务器: 选择 CPU、内存都比较好的服务器。
- 使用 Node.js 集群: 利用 Node.js 的 Cluster 模块,启动多个 Node.js 进程,充分利用多核 CPU。
- Gzip 压缩: 对返回的 HTML、JS、CSS 进行 Gzip 压缩,减少网络传输时间。
-
Kubernetes 优化: 在 Kubernetes 环境下,也有一些优化技巧。
- 资源限制: 为每个容器设置 CPU 和内存限制,避免资源争抢。
- Horizontal Pod Autoscaling (HPA): 根据 CPU 使用率自动调整 Pod 数量,应对流量高峰。
- 节点亲和性: 将 SSR 应用部署到性能较好的节点上。
咱们来举个例子,如何在 Node.js 中使用 Redis 缓存页面:
首先,安装 Redis:
npm install redis --save
然后,修改 server.js
:
const express = require('express');
const fs = require('fs');
const { createBundleRenderer } = require('vue-server-renderer');
const redis = require('redis');
const app = express();
const template = fs.readFileSync('./dist/index.html', 'utf-8');
const bundle = require('./dist/vue-ssr-server-bundle.json');
const clientManifest = require('./dist/vue-ssr-client-manifest.json');
const renderer = createBundleRenderer(bundle, {
template,
clientManifest,
runInNewContext: false
});
const redisClient = redis.createClient({
host: 'your-redis-host', // 替换成你的 Redis 地址
port: 6379
});
redisClient.on('error', err => {
console.error('Redis error:', err);
});
app.use(express.static('./dist'));
app.get('*', (req, res) => {
const cacheKey = req.url;
redisClient.get(cacheKey, (err, cachedHtml) => {
if (err) {
console.error('Redis get error:', err);
}
if (cachedHtml) {
console.log('Serving from cache:', cacheKey);
return res.send(cachedHtml);
}
const context = {
url: req.url
};
renderer.renderToString(context, (err, html) => {
if (err) {
console.error(err);
return res.status(500).send('Server Error');
}
redisClient.setex(cacheKey, 60, html); // 缓存 60 秒
res.send(html);
});
});
});
const port = 3000;
app.listen(port, () => {
console.log(`Server started at http://localhost:${port}`);
});
这段代码会在每次请求时,先从 Redis 中查找缓存。如果找到缓存,直接返回;否则,渲染页面,并将渲染结果缓存到 Redis 中。
第五章:监控与日志:随时掌握应用状态
部署之后,别忘了监控和日志!
- 监控: 监控 CPU 使用率、内存使用率、请求响应时间等指标,及时发现问题。可以使用 Prometheus、Grafana 等工具。
- 日志: 收集应用日志,方便排查错误。可以使用 ELK Stack (Elasticsearch, Logstash, Kibana) 等工具。
总结:
咱们今天聊了 Vue SSR 应用在 Docker/Kubernetes 环境下的部署策略,包括镜像构建、Kubernetes 部署和性能优化。希望这些内容能帮助大家更好地部署和管理 Vue SSR 应用。记住,容器化只是手段,最终目的是为了提升应用的可用性、可扩展性和可维护性。
一些额外的 Tips:
优化方向 | 优化策略 |
---|---|
镜像构建 | 1. 使用多阶段构建,减少最终镜像体积。 2. 利用 Docker 缓存,加速构建过程。 3. 选择合适的 Node.js 基础镜像。 |
Kubernetes | 1. 合理设置资源限制。 2. 使用 HPA 自动扩缩容。 3. 使用节点亲和性,将应用部署到合适的节点上。 4. 使用 Readiness Probe 和 Liveness Probe,保证应用健康状态。 |
性能优化 | 1. 使用缓存(页面缓存、组件缓存、CDN 缓存)。 2. 优化代码(减少 Bundle 体积、优化组件渲染、懒加载)。 3. 优化服务器(使用高性能服务器、使用 Node.js 集群、Gzip 压缩)。 |
好了,今天的分享就到这里,谢谢大家!如果有什么问题,欢迎随时提问。