背景
之前做專案一直使用單頁應用方式部署前端,是時候遷移到服務端渲染框架了
今天不討論怎麼寫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埠,我們來測試下效果,成功