背景
之前做项目一直使用单页应用方式部署前端,是时候迁移到服务端渲染框架了
今天不讨论怎么写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
新建项目
pnpm dlx nuxi@latest init <project-name>
打包
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
/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 #
公开的可以直接用容器地址:
ccr.ccs.tencentyun.com/rootegg/node:21.7.3-pm2-nginx-alpine
Dockerfile文件很长,具体可以看 gitee.com/rootegg/cic…
编写shell启动脚本
我们知道在Dockerfile里面CMD和ENTRYPOINT可以作为启动命令,启动需要的应用程序,比如
比如启动nginx
FROM nginx EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
CMD中写的 nginx -g daemon off;
,意思就是启动nginx容器,并 -g daemon off;
保留在前台不退出,否则容器会关闭
比如启动pm2
# 基础镜像用的我已经封装好的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
下面这些都已经放入到我公开的容器中了,只需要使用即可,这一章可以跳过,看最后怎么使用这个容器即可。
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 文字
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脚本的方式来启动多个应用程序
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配置文件
module.exports = { apps : [{ name : "app", script : "./app.js" }] }
start.sh
启动脚本,启动nginx,这里不能用 nginx -g daemon off;
,因为nginx后面还有pm2,只有pm2要保留在前台,nginx要运行在后台,只有有一个应用是在前台,其他都要在后台运行
#!/bin/bash nginx pm2-runtime start ecosystem.config.js
正式使用封装的容器
新建Nuxt项目
刚才已经新建好项目,只需要在根目录增加一个 Docker 文件即可,其他都不用动
关键是使用 ccr.ccs.tencentyun.com/rootegg/node:21.7.3-pm2-nginx-alpine
,里面环境都已经安装好了
# compile stage FROM ccr.ccs.tencentyun.com/rootegg/node:21.7.3-pm2-nginx-alpine as build-stage WORKDIR /appinstall 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 /appinstall/.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
在服务器上构建项目
# 克隆源码 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
进入容器查看 pm2 集群
在网页上,输入ip和50080端口,我们来测试下效果,成功