背景
之前做项目一直使用单页应用方式部署前端,是时候迁移到服务端渲染框架了
今天不讨论怎么写SSR代码,通过几种服务端渲染方案比较,我还是倾向使用成熟框架nuxt
今天我们讨论下,怎么使用nuxt快速部署,要解决下面几个问题:
-
nuxt项目打包后使用nodejs监听http服务,动态渲染html页面,使用pm2运行nodejs服务端,这里放到docker容器里面,可以单节点获取集群模式
-
前端接口转发到后端需要nginx,上面pm2已经有容器了,那nginx另外开一个容器吗,我渲染把pm2和nginx放到一个容器里面运行
-
需要一个容器,要求是已经安装好 pm2 \ nginx \ pnpm 软件包
-
docker容器运行多个程序,这里是pm2和nginx,需要通过shell脚本启动,并且最后一个应用需要驻留前台,否则容器会直接退出
源码 https://gitee.com/rootegg/nuxtweb
,使用效果看最后一章验证
认识 nuxt
新建项目
swift
pnpm dlx nuxi@latest init <project-name>
打包
arduino
pnpm run build
如下图,build 之后生成 .output
目录,测试初始化项目生成后能正常构建
思路
上面已经看到 .output
目录下有 server 和 public , 我没用 pnpm做包管理器,pm2运行server下nodejs服务端,用nginx做pm2和后端接口转发,将nginx的80端口抛出去
-
第一步我们需要一个干净的docker容器,里面已经安装好 pnpm \ pm2 \ nginx
-
第二步编写shell启动脚本同时启动nginx和pm2
-
第三步拷贝
.output
打包后内容到容器中,分别运行 pm2 和 nginx
构建干净容器
所有的容器都需要来源一个初始容器,这里我们选择 alpine:3.19
,上海时区,最终我已经构建好一个容器里面包含 node
,pnpm
,yarn
, pm2
, python3
, nginx
bash
/app # node -v
v20.12.2
/app # npm -v
10.5.0
/app # yarn -v
1.22.19
/app # pnpm -v
9.3.0
/app # pm2 -v
5.3.1
/app # python -V
Python 3.11.9
/app # nginx -v
nginx version: nginx/1.24.0
/app #
公开的可以直接用容器地址:
bash
ccr.ccs.tencentyun.com/rootegg/node:21.7.3-pm2-nginx-alpine
Dockerfile文件很长,具体可以看 gitee.com/rootegg/cic...
编写shell启动脚本
我们知道在Dockerfile里面CMD和ENTRYPOINT可以作为启动命令,启动需要的应用程序,比如
比如启动nginx
css
FROM nginx
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
CMD中写的 nginx -g daemon off;
,意思就是启动nginx容器,并 -g daemon off;
保留在前台不退出,否则容器会关闭
比如启动pm2
bash
# 基础镜像用的我已经封装好的node镜像
FROM ccr.ccs.tencentyun.com/rootegg/node:21.7.3-alpine
LABEL description="from ccr.ccs.tencentyun.com/rootegg/node:21.7.3-alpine, extend python3 pm2"
# 安装python3,因为pm2需要python,并用阿里云镜像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk add --update --no-cache curl make gcc jq py3-configobj py3-pip py3-setuptools python3 python3-dev ca-certificates g++
# 安装 pm2
RUN npm install pm2 -g
# 抛出端口
EXPOSE 80 443 43554 3000
# 启动
CMD ["pm2-runtime", "start", "ecosystem.config.js"]
CMD中用的pm2-runtime
,而不是pm2
启动,因为pm2-runtime
是专门为容器开发的,为了保留在前台不关闭容器,否则用 pm2
命令会不保留前台直接关闭容器
融合nginx和pm2
下面这些都已经放入到我公开的容器中了,只需要使用即可,这一章可以跳过,看最后怎么使用这个容器即可。
bash
ccr.ccs.tencentyun.com/rootegg/node:21.7.3-pm2-nginx-alpine
我们需要4个文件:
- app.js 是 pm2 运动的nodejs文件
- Dockerfile 是构建docker镜像文件
- ecosystem.config.js 是pm2启动脚本
- start.sh 是容器CMD启动脚本,同时启动nginx和pm2
app.js
这个文件没啥说的,就是nodejs启动http服务监听3000端口,返回 hello world 文字
ini
const http = require(`http`);
const server = http.createServer((req, res) => {
const response = "hello world";
res.writeHead(200);
res.end(response);
});
server.listen(3000, () => {
console.log(`Server is running on http://localhost:3000/`);
});
Dockerfile
安装 nginx,pm2已经过了,增加另外三个文件内容,最后一句 CMD 就是启动脚本,在脚本里在启动nginx和pm2,因为CMD不能启动多个程序,虽然可以写多个CMD,但是只有最后一个CMD有效,所以只能通过启动shell脚本的方式来启动多个应用程序
bash
FROM ccr.ccs.tencentyun.com/rootegg/node:20.12.2-pm2-alpine
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk add --update --no-cache nginx
RUN npm install -g pnpm
WORKDIR /app
COPY ./start.sh ./start.sh
COPY ./ecosystem.config.js ./ecosystem.config.js
COPY ./app.js ./app.js
RUN chmod 777 ./start.sh
CMD ["sh", "./start.sh"]
ecosystem.config.js
标准pm2配置文件
css
module.exports = {
apps : [{
name : "app",
script : "./app.js"
}]
}
start.sh
启动脚本,启动nginx,这里不能用 nginx -g daemon off;
,因为nginx后面还有pm2,只有pm2要保留在前台,nginx要运行在后台,只有有一个应用是在前台,其他都要在后台运行
bash
#!/bin/bash
nginx
pm2-runtime start ecosystem.config.js
正式使用封装的容器
新建Nuxt项目
刚才已经新建好项目,只需要在根目录增加一个 Docker 文件即可,其他都不用动
关键是使用 ccr.ccs.tencentyun.com/rootegg/node:21.7.3-pm2-nginx-alpine
,里面环境都已经安装好了
swift
# compile stage
FROM ccr.ccs.tencentyun.com/rootegg/node:pnpm-9.3.0 as build-stage
WORKDIR /app
COPY package*.json pnpm-lock.yaml ./
RUN pnpm install
COPY . .
RUN pnpm run build
# production stage
FROM ccr.ccs.tencentyun.com/rootegg/node:21.7.3-pm2-nginx-alpine as production-stage
WORKDIR /app
COPY --from=build-stage /app/.output/ .
RUN echo -e "module.exports = { \n\
apps: [{ \n\
name: 'app', \n\
exec_mode: 'cluster', \n\
instances: 'max', \n\
script: './server/index.mjs' \n\
}] \n\
}" > ./ecosystem.config.js
RUN echo -e "server { \n\
listen 80; \n\
location /api/ { \n\
proxy_pass http://172.16.0.10:8080/api/; \n\
} \n\
location / { \n\
proxy_pass http://127.0.0.1:3000/; \n\
} \n\
gzip on; \n\
gzip_min_length 1k; \n\
gzip_http_version 1.1; \n\
gzip_comp_level 6; \n\
gzip_types text/plain application/x-javascript text/css application/xml application/javascript; \n\
gzip_vary on; \n\
access_log /var/log/nginx/access.log ; \n\
} " > /etc/nginx/http.d/default.conf
验证效果
上面新建nuxt项目后,只增加了一个Dockerfile文件,项目上传gitee仓库
源码 https://gitee.com/rootegg/nuxtweb
在服务器上构建项目
bash
# 克隆源码
git clone https://gitee.com/rootegg/nuxtweb.git
# 进入文件夹
cd nuxtweb
# 构建镜像,test.com是随便取的
docker build -t test.com/nuxtweb:v1 .
# build成功后运行镜像
docker run -d -p 50080:80 test.com/nuxtweb:v1
# 查看容器状态
docker ps
在网页上,输入ip和50080端口,我们来测试下效果,成功