第一次用docker快速部署pm2和nuxt前端项目

背景

之前做项目一直使用单页应用方式部署前端,是时候迁移到服务端渲染框架了

今天不讨论怎么写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,上海时区,最终我已经构建好一个容器里面包含 nodepnpmyarn, 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端口,我们来测试下效果,成功

相关推荐
hackeroink10 分钟前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者2 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-2 小时前
验证码机制
前端·后端
燃先生._.3 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖4 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235244 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240255 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar5 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人6 小时前
前端知识补充—CSS
前端·css