搭建自動化 Web 頁面效能檢測系統 —— 部署篇

袋鼠云数栈前端發表於2024-07-29

我們是袋鼠雲數棧 UED 團隊,致力於打造優秀的一站式資料中臺產品。我們始終保持工匠精神,探索前端道路,為社群積累並傳播經驗價值。

本文作者:琉易 liuxianyu.cn

這一篇是系列文章:
搭建自動化 Web 頁面效能檢測系統 —— 設計篇
搭建自動化 Web 頁面效能檢測系統 —— 實現篇

作為一個前端想去做全棧的專案時,可能第一個思路是 node + vue/react。一開始可能會新建多個工程目錄去實現,假設分別為 web 和 server,也許還有管理後臺的程式碼 admin,那麼就有了三個工程的程式碼。此時為了方便管理就需要在遠端倉庫新建一個 group 統一管理程式碼,一般這種方式稱之為 MultiRepo。

file

這顯然是不夠簡潔的,對於開發者而言也不便於開發和部署。這類多模組的專案我們可以引入 Monorepo 的概念,下面是一些最佳化方法的嘗試,以 yice-performance(易測) 作為例子講解,本地裝置為 M1 晶片的 arm64v8 平臺。

一、node 託管靜態頁面

可以將 web 打包的程式碼交給 node 託管,此時就可以將 web 的程式碼作為一個資料夾放到 server 的目錄中,這時候我們一般直接訪問後端介面的根路徑即可。如:yice-performance - v1.0
對應的 nginx 配置一般為:

server {
    listen          80;
    server_name     yice.dtstack.cn;

    location / {
        proxy_pass http://localhost:4000/;
    }
}

常見的 node 框架都支援託管靜態檔案目錄:

// express
app.use(express.static(path.join(__dirname, 'web/dist')));


// NestJS
import { ServeStaticModule } from '@nestjs/serve-static';

ServeStaticModule.forRoot({
    serveRoot: '/',
    rootPath: join(__dirname, '.', 'web/dist'),
}),

// egg
{
    static: {
        dir: path.join(appInfo.baseDir, 'web/dist'),
    }
}

程式碼基本大同小異,從 nginx 配置和專案結構我們也能看出這還是屬於一個 node 專案的結構,前端專案的 nginx 配置一般為:

server {
    listen          80;
    server_name     yice.dtstack.cn;
    root						/opt/dtstack/yice-performance/web/dist/

    location /api {
        proxy_pass http://localhost:4000/;
    }
    location / {
        try_files $uri $uri/ /index.html;
    }
}

二、Turborepo

Turborepo 是用於 JavaScript 和 TypeScript 程式碼庫的高效能構建系統。

藉助 Turborepo 我們可以並行的執行和構建程式碼,當我們使用傳統的 yarn workspace 管理程式碼時,我們的一般會執行以下命令:

# server
yarn
yarn dev

# web
cd web
yarn
yarn dev

此時,本地開發不僅需要同時開啟兩個終端,而且還得分別注意兩個終端所在的路徑,lint、build、test 等命令皆如此。

file

想要更快的完成以上工作,可以使用 turbo run lint test build

file

新專案往往更容易使用 Turborepo,使用 create-turbo 建立即可,參考 官方文件。歷史專案想要使用 Turborepo 時需要注意一下專案結構:

yice-performance
├─package.json
├─pnpm-lock.yaml
├─pnpm-workspace.yaml
├─turbo.json
├─apps
|  ├─server
|  └─web

將歷史專案的程式碼整合到單個資料夾後移入 apps ,注意需要修改相對路徑等程式碼,比如 tsconfig.json 檔案中關於 @/* 等路徑別名的寫法,以及 import 依賴的路徑,將公共依賴包統一提到根目錄的 package.json 中。
在根目錄新增 turbo.json 檔案,這裡是 dev 和 build 命令為例:

{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".apps/server/dist/**", "!.apps/server/cache/**"]
    },
    "dev": {
      "persistent": true,
      "cache": false
    }
  }
}

然後在 apps 下的產品中依次新增兩種命令:

{
  "scripts": {
    "dev": "NODE_ENV=development nest start --watch",
    "build": "NODE_ENV=production nest build"
  }
}
{
  "scripts": {
    "dev": "NODE_ENV=development vite --port 7001",
    "build": "tsc && NODE_ENV=production vite build"
  }
}

這樣就可以透過 pnpm dev 一條命令同時啟多個服務了,pnpm build 可以快速完成多個專案的打包工作。

file

三、docker

以易測依賴的 Puppeteer 為例,對於裝置環境的要求就比較多,參考 Puppeteer 故障排除;再比如易測 v2.x 版本新增的資料週報功能使用到 node 端的 echarts,最終依賴 node-canvas,對裝置環境的要求也很苛刻。
同時,部署命令寫的指令碼中還需要考慮不同環境的差異,比如 Windows 中的情況。
docker 在這裡的作用就是抹平不同裝置間的環境差異,減少補充安裝依賴包的痛苦,amd64、arm64 等環境差異導致的依賴包安裝失敗問題,我們可以構建適用於不同平臺的 docker 映象包(以下以 linux/amd64 為例,也就是常說的 x86_64 架構)。

Dockerfile

本地編寫 Dockerfile 檔案,然後執行 docker build命令構建映象。在構建映象之前,需要注意下 Dockerfile 構建映象時有一個 的概念,對於構建時間會有較大影響。

Docker 映象是由多個只讀的層疊加而成的,每一層都是基於前一層構建。Dockerfile 檔案中的每條指令都會建立一個新的層,並對映象進行修改,執行 docker build 命令時會使用快取,當前面的層不發生變化時,我們再次構建映象時就會更快速。但因為每一層都是基於前一層構建,所以我們應該把變化可能性小的操作放到前面,後續改動只會構建變化的內容,而無需構建整個映象,這能大大加快映象的構建速度。

比如下方 Dockerfile.server 中的 nodejs 的安裝,如果放在 COPY . . 之後,則每次構建都需要安裝一次 nodejs,我們利用快取可以大大減少構建時間。

FROM ubuntu:22.04

# 設定時區
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \
     && apt-get update -y && apt-get install -y tzdata

# puppeteer 和 node-canvas 對系統依賴的要求
# https://github.com/Automattic/node-canvas?tab=readme-ov-file#compiling
# https://github.com/puppeteer/puppeteer/blob/puppeteer-v19.6.3/docs/troubleshooting.md#chrome-headless-doesnt-launch-on-unix
RUN apt-get update -y \
     && apt-get install -y build-essential libcairo2-dev libpango1.0-dev libnss3 libatk1.0-0 \
     && apt-get install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 \
     && apt-get install -y libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 \
     && apt-get install -y libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libpangocairo-1.0-0 \
     && apt-get install -y libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
     && apt-get install -y libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \
     && apt-get install -y libxss1 libxtst6

# 處理 chromium 等依賴問題
# https://github.com/puppeteer/puppeteer/blob/puppeteer-v19.6.3/docker/Dockerfile
RUN apt-get update -y \
     && apt-get install -y wget gnupg \
     && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/googlechrome-linux-keyring.gpg \
     && sh -c 'echo "deb [arch=amd64 signed-by=/usr/share/keyrings/googlechrome-linux-keyring.gpg] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
     && apt-get update -y \
     && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-khmeros fonts-kacst fonts-freefont-ttf libxss1 --no-install-recommends \
     && rm -rf /var/lib/apt/lists/* \
     && apt-get remove -y wget gnupg
# deb [arch=amd6 配置可能會在 /etc/apt/sources.list.d/google.list 和 /etc/apt/sources.list.d/google-chrome.list 中重複,再嘗試一次
RUN  rm -rf /etc/apt/sources.list.d/google-chrome.list \
     && apt-get update -y \
     && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-khmeros fonts-kacst fonts-freefont-ttf libxss1 --no-install-recommends

# 安裝 nodejs
RUN apt-get update -y && apt-get install -y curl \
     && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
     && apt-get remove -y curl \
     && apt-get install -y nodejs \
     && npm config set registry https://registry.npmmirror.com/ \
     && npm install pnpm@6.35.1 -g

# 設定工作目錄
WORKDIR /yice-performance

# 複製程式碼安裝依賴
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
COPY apps/server/package.json ./apps/server/
COPY apps/web/package.json ./apps/web/
RUN pnpm install

# 複製專案檔案
COPY apps .env ./
# 減少 node_modules 的磁碟佔用
RUN pnpm build \
     && find . -name "node_modules" -type d -prune -exec rm -rf '{}' + \
     && pnpm install --production

# 暴露埠
EXPOSE 4000

# 定義環境變數
ENV NODE_ENV=production
# Dockerfile 中需指定 chromium 路徑
ENV PUPPETEER_EXECUTABLE_PATH='google-chrome-stable'

VOLUME [ "/yice-performance/apps/server/yice-report" ]

# 啟動應用程式
CMD ["node", "apps/server/dist/main.js"]
ARG BASE_IMAGE=mysql:5.7
FROM ${BASE_IMAGE}

# 當容器啟動時,會自動執行 /docker-entrypoint-initdb.d/ 下的所有 .sql 檔案
COPY ./mysql/demo-data.sql /docker-entrypoint-initdb.d/
# 附加的 mysql 配置
COPY ./mysql/my_custom.cnf /etc/mysql/conf.d/

# 設定 MySQL root 使用者的密碼
ENV MYSQL_ROOT_PASSWORD=123456
ENV MYSQL_DATABASE=yice-performance

# 設定時區
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

# 暴露埠
EXPOSE 3306

根據 Dockerfile 檔案本地構建映象,構建完成後在 Docker Desktop 中就可以看到剛剛構建的映象。我們新建一個指令碼檔案來統一管理命令,並在 package.json 中新增 build:docker命令:

#!/bin/sh

cd docker

# amd64
docker buildx build --platform linux/amd64 -f Dockerfile.mysql -t liuxy0551/yice-mysql .
docker buildx build --platform linux/amd64 -f Dockerfile.server -t liuxy0551/yice-server ../

此時執行 pnpm build:docker 即可打包映象。

多平臺打包映象

由於我們目前使用的 Mac M 系列晶片較多,這是 arm64 v8 平臺的,但往往我們打包後的映象是在 x86 的機器上使用,比如 Centos、Ubuntu 等伺服器系統,這就要求我們應該相容 x86 平臺。
使用 docker inspect 的命令可以檢視映象架構,如下:

docker pull alpine
docker inspect alpine | grep Architecture

修改剛剛寫的 Dockerfile 檔案,支援透過 docker build 命令的 build argument 傳遞引數,這在明確不同平臺使用的基礎映象時比較有用。有些常用的基礎映象是支援多平臺,只需要新增 --platform linux/amd64, linux/arm64 即可,docker buildx 會自動處理一切,yice-mysql 支援了 arm64 v8,其他內容可以自行研究。

映象釋出

這裡使用的是阿里雲容器映象服務:https://cr.console.aliyun.com/

 docker login --username=your_username -p your_password registry.cn-hangzhou.aliyuncs.com
docker tag liuxy0551/yice-mysql registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-mysql:latest
docker tag liuxy0551/yice-server registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-server:latest

docker push registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-mysql:latest
docker push registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-server:latest

docker run

為了保證 yice-server 可以訪問到 yice-mysql兩個容器需要使用同一個網路

docker network create yice-network
docker run -p 3306:3306 -d --name yice-mysql --network=yice-network -v /opt/dtstack/yice-performance/yice-mysql/conf:/etc/mysql/conf.d -v /opt/dtstack/yice-performance/yice-mysql/log:/var/log/mysql -v /opt/dtstack/yice-performance/yice-mysql/data:/var/lib/mysql registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-mysql:latest
docker run -p 4000:4000 -d --name yice-server --network=yice-network -v /opt/dtstack/yice-performance/yice-report:/yice-performance/apps/server/yice-report registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-server:latest
  • -p 表示埠對映,-p 宿主機 port:容器 port,這裡暴漏埠是為了外部可以透過 GUI 工具檢視資料
  • -d 表示後臺執行並返回容器 id
  • --name 表示給容器指定的名稱
  • -v /opt/dtstack/yice-performance/yice-mysql:/etc/mysql/conf.d 等掛載路徑表示將容器中的配置項、資料、日誌都掛載到主機的 /opt/dtstack/yice-performance/yice-mysql
  • -v /opt/dtstack/yice-performance/yice-report:/yice-performance/apps/server/yice-report 表示將容器中的檢測報告掛載到宿主機
  • 掛載的目的是為了在刪除容器時資料不丟失,且儘量保持容器儲存層不發生寫操作。

執行 docker run 命令生成容器並執行,訪問 http://localhost:4000 即可看到頁面了。

docker-compose

docker-compose 是 Docker 官方提供的一個工具,用於管理多個 Docker 容器的應用程式,使用 docker-compose 可以協同多個容器執行。
新增 docker-compose.yml 檔案,在這個檔案裡定義應用程式所需的服務和容器,包括映象、環境變數、埠對映、掛載目錄等資訊。

version: '3'

services:
    mysql-service:
        container_name: yice-mysql
        image: registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-server:latest
        ports:
            - '3306:3306'
        restart: always
        networks:
            - yice-network

    server-service:
        container_name: yice-server
        image: registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-mysql:latest
        ports:
            - '4000:4000'
        restart: always
        depends_on:
            - mysql-service
        networks:
            - yice-network

networks:
  yice-network:
    driver: bridge
docker-compose -f docker/docker-compose.yml -p yice-performance up -d
命令 作用
docker-compose up 啟動程式,-d 後臺執行
docker-compose down 停止並移除容器、卷、映象等
docker-compose ps 列出正在執行的容器
docker-compose logs 檢視日誌
docker-compose stop 停止服務
docker-compose start 啟動服務
docker-compose restart 重啟服務

四、常見問題

yice-server 無法啟動

可能是 docker 版本較低,建議升級到 docker v24 及以上,升級前應當備份。

yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

image.pngimage.png
image.png

node[1]: ../src/node_platform.cc:61:std::unique_ptr<long unsigned int> node::WorkerThreadsTaskRunner::DelayedTaskScheduler::Start(): Assertion `(0) == (uv_thread_create(t.get(), start_thread, this))' failed.
 1: 0xb090e0 node::Abort() [node]
 2: 0xb0915e  [node]
 3: 0xb7512e  [node]
 4: 0xb751f6 node::NodePlatform::NodePlatform(int, v8::TracingController*) [node]
 5: 0xacbf74 node::InitializeOncePerProcess(int, char**, node::InitializationSettingsFlags, node::ProcessFlags::Flags) [node]
 6: 0xaccb59 node::Start(int, char**) [node]
 7: 0x7f2ffac64d90  [/lib/x86_64-linux-gnu/libc.so.6]
 8: 0x7f2ffac64e40 __libc_start_main [/lib/x86_64-linux-gnu/libc.so.6]
 9: 0xa408ec  [node]
gcc 版本過低

主機部署時建議使用 Ubuntu。
主機模式部署時 CentOS7 上啟動服務時報錯:Error: /lib64/libstdc++.so.6: version 'CXXABI_1.3.9' not found,這是因為 CentOS7gcc 版本過低,需要升級到 gcc-4.8.5 以上,執行下方命令可以看到沒有 CXXABI_1.3.9

strings /lib64/libstdc++.so.6 | grep CXXABI

相關連結:
https://github.com/Automattic/node-canvas/issues/1796
https://gist.github.com/nchaigne/ad06bc867f911a3c0d32939f1e930a11
https://ftp.gnu.org/gnu/gcc/

cd /etc/gcc
wget https://ftp.gnu.org/gnu/gcc/gcc-9.5.0/gcc-9.5.0.tar.gz
tar xzvf gcc-9.5.0.tar.gz
mkdir obj.gcc-9.5.0
cd gcc-9.5.0
./contrib/download_prerequisites
cd ../obj.gcc-9.5.0
../gcc-9.5.0/configure --disable-multilib --enable-languages=c,c++
make -j $(nproc)
make install

最後

歡迎關注【袋鼠雲數棧UED團隊】~
袋鼠雲數棧 UED 團隊持續為廣大開發者分享技術成果,相繼參與開源了歡迎 star

  • 大資料分散式任務排程系統——Taier
  • 輕量級的 Web IDE UI 框架——Molecule
  • 針對大資料領域的 SQL Parser 專案——dt-sql-parser
  • 袋鼠雲數棧前端團隊程式碼評審工程實踐文件——code-review-practices
  • 一個速度更快、配置更靈活、使用更簡單的模組打包器——ko
  • 一個針對 antd 的元件測試工具庫——ant-design-testing

相關文章