• 30分钟入门前端容器化



    title: 30分钟入门docker前端容器化
    date: 2023-09-08
    categories: [tool]
    tags: [前端容器化,npm,pnpm,docker,react,工具]
    description: 前端docker容器化的一些尝试和记录

    1.引言

    前端容器化是一种将前端应用程序打包成容器的技术,使其可以在不同的环境中快速、高效地部署和运行。

    2.背景

    前后端分离的趋势已形成现状,前端工程复杂度叠加增长,新、老项目部署依赖的环境和Node.js版本会存在差异,生产环境下构建混淆后的脚本、静态资源文件依赖环境部署服务进行访问,前端工程未能形成"单体工件"部署,容器的出现大大简化了部署流程。

    前端容器化可以方便的管理前端环境变量注入、运行环境(不同项目依赖不同的node环境,node的版本兼容是个很大的问题)、节约服务器成本、更快捷方便的版本回滚、多架构部署、CI/CD自动化集成部署、DevOps等等,好处只有多到你想不到(此处手动偷笑)。

    本文基于React项目结合Docker,分享在前端引入容器技术带来的变革。

    3.容器化在github的运用

    github推出了github-action来做容器化的ci/cd,我下面展示用github-action做一个npm自动化发包的示例:

    • 在项目根目录下新建.github/workflows/ci.yml文件
    • 去npm官网申请一个token(具体怎么去申请,请自己去搜索解决)
    • 将这段代码贴入ci.yml文件
    • push代码到master分支,就会自动走ci/cd进行部署啦!
    name: CI
    on:
      push:
        branches:
          - master
    jobs:
      build:
        # 指定操作系统
        runs-on: ubuntu-latest
        steps:
          # 将代码拉到虚拟机
          - name: Checkout repository
            uses: actions/checkout@v2
          # 指定node版本
          - name: Use Node.js
            uses: actions/setup-node@v3
            with:
              node-version: '16.x'
              registry-url: 'https://registry.npmjs.org'
          # 依赖缓存策略
          - name: Cache
            id: cache-dependencies
            uses: actions/cache@v3
            with:
              path: |
                **/node_modules
              key: ${{runner.OS}}-${{hashFiles('**/pnpm-lock.yaml')}}
          - name: Install pnpm
            run: npm install -g pnpm@7.5.0
          # 依赖下载
          - name: Installing Dependencies
            if: steps.cache-dependencies.outputs.cache-hit != 'true'
            run: pnpm install
          # 打包
          - name: Running Build
            run: pnpm run build
          # 测试
          - name: Running Test
            run: pnpm run test-unit
          # 发布
          - name: Running Publish
            run: npm publish
            env:
              # NPM_TOKEN is access token
             NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    4.基于docker构建前端镜像

    在学习前端项目ci/cd构建之前,让我们先学习下前端镜像怎么构建

    4.1 安装docker

    点此处坐飞机去安装docker
    安装完成后执行以下命令查看docker版本,尽量带buildx的版本

    docker -v
    Docker version 24.0.2, build cb74dfc
    
    • 1
    • 2

    4.2 编写Dockerfile

    这里先需要普及一个前端工程知识,我们都知道一个基于npm的项目,需要一个package.json文件,然后执行npm run install下载包,npm run build打包,打包出来的文件其实是不能直接运行的,需要启动一个node服务运行,所以我们就写一个最基本的基于node和nginx的镜像,示例如下

    在项目根目录下添加nginx配置文件,取名为nginx.conf,内容如下

    worker_processes  1;
     
    events {  
        worker_connections  1024;  
    } 
    http {  
        sendfile         on;  
        tcp_nodelay       on;  
        keepalive_timeout  30; 
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
        server {
            listen 80;
            server_name localhost;
            root /usr/share/nginx/front/dist;
            autoindex on;   
            autoindex_exact_size off;   
            autoindex_localtime on;
            location / {
                try_files $uri $uri/ =404;
                index index.html index.htm;
                gzip_static on;
                expires max;
                add_header Cache-Control public;
                if ($request_filename ~* ^.*?\.(eot)|(ttf)|(woff)|(svg)|(otf)$) {
                    add_header Access-Control-Allow-Origin *;
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在项目根目录下添加docker配置文件,取名为Dockerfile,内容如下

    FROM node:17-buster as builder
    
    WORKDIR /src
    COPY ./ /src
    
    RUN npm install -g pnpm \
        && pnpm install \
        && pnpm build
    
    FROM nginx:alpine-slim
    
    RUN mkdir /usr/share/nginx/front \
        && mkdir /usr/share/nginx/front/dist \
        && rm -rf /etc/nginx/nginx.conf
     
    COPY --from=builder /src/nginx.conf /etc/nginx/nginx.conf
    
    COPY --from=builder /src/dist /usr/share/nginx/front/dist
    
    EXPOSE 80
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    接下来使用docker build打包镜像(如果有桌面工具,打包成功后docker桌面工具的images栏目能看到), docker run执行镜像(如果有桌面工具,运行成功后docker桌面工具的containers栏目能看到), docker run运行成功后可以打开浏览器输入:http://localhost 进行查看

    docker buildx build -t webapp-demo:v1 .
    
    docker run -d -p 80:80 webapp-demo:v1
    
    • 1
    • 2
    • 3

    4.3 如何基于Dockerfile做pnpm缓存

    这里我引用一段话:
    使用多阶段构建,构建的镜像中只包含了目标文件夹 dist,但仍然存在一些问题,当 package.json 文件变动时,RUN npm i && rm -rf ~/.npm 这一层会重新执行,变更多次后,生成了大量的中间层镜像。

    为解决这个问题,进一步的我们可以设想一个类似 数据卷 的功能,在镜像构建时把 node_modules 文件夹挂载上去,在构建完成后,这个 node_modules 文件夹会自动卸载,实际的镜像中并不包含 node_modules 这个文件夹,这样我们就省去了每次获取依赖的时间,大大增加了镜像构建效率,同时也避免了生成了大量的中间层镜像。

    此处课代表总结一下:就是尽量减少中间层镜像的可能,最小化docker映像大小和构建时间

    由于我使用的是pnpm进行npm包管理,所以我去翻阅了pnpm官方文档关于此优化如下:

    FROM node:20-slim AS base
    
    ENV PNPM_HOME="/pnpm"
    ENV PATH="$PNPM_HOME:$PATH"
    RUN corepack enable
    
    COPY . /app
    WORKDIR /app
    
    FROM base AS prod-deps
    RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
    
    FROM base AS build
    RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
    RUN pnpm run build
    
    FROM base
    COPY --from=prod-deps /app/node_modules /app/node_modules
    COPY --from=build /app/dist /app/dist
    EXPOSE 8000
    CMD [ "pnpm", "start" ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    于是本着依葫芦画瓢的精神,还有使用生产化的nginx配置,我找了同事写的nginx包装镜像,同样你可以执行docker build、docker run进行验证,然后我改造后的代码如下:

    FROM node:17-buster AS builder
    
    ENV PNPM_HOME="/pnpm"
    ENV PATH="$PNPM_HOME:$PATH"
    RUN corepack enable
    
    WORKDIR /src
    COPY ./ /src
    
    RUN --mount=type=cache,target=/src/node_modules,id=myapp_pnpm_module,sharing=locked \
        --mount=type=cache,target=/pnpm/store,id=pnpm_cache \
            pnpm install
    
    RUN --mount=type=cache,target=/src/node_modules,id=myapp_pnpm_module,sharing=locked \
            pnpm run build
    
    FROM ghcr.io/zboyco/webrunner:0.0.7
    
    COPY --from=builder /src/dist /app
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    4.4 如何利用buildx制作多架构镜像

    docker buildx的工具,说白了就是给你提供一个能力,当你的宿主机是x86 64的架构时,你想构建镜像为ARM64的架构,就需要这个工具,给人的感觉有点类似交叉编译,诸如:go build的交叉编译,在win10下编译可执行程序,可用于特定linux平台

    buildx本质上调用了 buildkit 的 api,构建是在 buildkit 的环境中进行的。 是否支持多架构,取决于 buildkit 的环境,如果需要 buildkit支持多架构,需要在宿主机执行(当然这个不是必须的,按构建的需求进行控制,Docker桌面版无需进行此项设置):

    docker run --privileged --rm tonistiigi/binfmt --install all
    
    • 1

    我们这里改造上面Dockerfile代码,使它支持多架构,由于platform是实验性质,所以需要先执行docker pull docker/dockerfile 拉取镜像

    # syntax = docker/dockerfile:experimental
    # --platform, 会让 builder 只会有一份,且 arch 与宿主机一致
    FROM --platform=${BUILDPLATFORM:-linux/amd64} node:17-buster AS builder
    
    ENV PNPM_HOME="/pnpm"
    ENV PATH="$PNPM_HOME:$PATH"
    RUN corepack enable
    
    WORKDIR /src
    COPY ./ /src
    
    RUN --mount=type=cache,target=/src/node_modules,id=myapp_pnpm_module,sharing=locked \
        --mount=type=cache,target=/pnpm/store,id=pnpm_cache \
            pnpm install
    
    RUN --mount=type=cache,target=/src/node_modules,id=myapp_pnpm_module,sharing=locked \
            pnpm run build
    
    FROM ghcr.io/zboyco/webrunner:0.0.7
    
    COPY --from=builder /src/dist /app
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在执行打包镜像命令之前,我们先查看下我们机器默认的 builder 实例

    docker buildx ls
    
    NAME/NODE       DRIVER/ENDPOINT STATUS  BUILDKIT                              PLATFORMS
    default         docker
      default       default         running v0.11.7-0.20230525183624-798ad6b0ce9f linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
    desktop-linux * docker
      desktop-linux desktop-linux   running v0.11.7-0.20230525183624-798ad6b0ce9f linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    使用buildx执行上面的脚本打包镜像会报错如下

    docker buildx build --platform linux/arm,linux/arm64,linux/amd64 -t webapp-official-website:v1 .
    
    ERROR: Multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. "docker buildx create --use")
    
    • 1
    • 2
    • 3

    由于 Docker 默认的 builder 实例不支持同时指定多个 --platform,我们必须首先创建一个新的 builder 实例。同时由于国内拉取镜像较缓慢,我们可以使用配置了镜像加速地址的 dockerpracticesig/buildkit:master 镜像替换官方镜像。

    如果你有私有的镜像加速器,可以基于 https://github.com/docker-practice/buildx 构建自己的 buildkit 镜像并使用它。

    # 适用于国内环境
    $ docker buildx create --use --name=mybuilder-cn --driver docker-container --driver-opt image=dockerpracticesig/buildkit:master
    
    # 适用于腾讯云环境(腾讯云主机、coding.net 持续集成)
    $ docker buildx create --use --name=mybuilder-cn --driver docker-container --driver-opt image=dockerpracticesig/buildkit:master-tencent
    
    # $ docker buildx create --name mybuilder --driver docker-container
    
    $ docker buildx use mybuilder
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们选择适用于国内环境的方案的命令进行创建, 可以看到多了name为 mybuilder-cn 的实例

    docker buildx create --use --name=mybuilder-cn --driver docker-container --driver-opt image=dockerpracticesig/buildkit:master
    
    docker buildx ls
    NAME/NODE       DRIVER/ENDPOINT  STATUS   BUILDKIT                              PLATFORMS
    mybuilder-cn *  docker-container
      mybuilder-cn0 desktop-linux    inactive
    default         docker
      default       default          running  v0.11.7-0.20230525183624-798ad6b0ce9f linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
    desktop-linux   docker
      desktop-linux desktop-linux    running  v0.11.7-0.20230525183624-798ad6b0ce9f linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
    
    $ docker buildx build --platform linux/arm,linux/arm64,linux/amd64 -t myusername/hello . --push
    
    # 查看镜像信息
    $ docker buildx imagetools inspect myusername/hello
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在不同架构运行该镜像,可以得到该架构的信息。

    $ docker run -it --rm myusername/hello
    
    • 1

    5.如何利用容器化做前端环境变量注入

    • 前端虽然对环境变量的需求场景不多,但是基本的api baseURL, appName, env这些是需要的
    • 如果是微前端的场景,那么需要的其他网站url都是环境变量,还挺多的
    • 依稀记得刚入行时,前端区分测试环境,线上环境就是直接通过域名进行判断,例如: inlcudes(url, “.com”)?, 然后得到isProd去获取项目中配置的不同环境的变量,这样显得很low
    • 后面出了vue, react这种框架,可以在npm run dev时候指明–prod,然后通过process拿到isProd,去获取对应配置
    • 现在可以直接通过容器化注入环境变量,然后使用nginx将容器中的环境变量注入前端项目html的meta标签content中, 然后从meta标签获取变量
    • 如果是monorepo项目,npm run build的时候,Dockerfile里面也需要通过容器中的环境变量获取打包哪个项目
    • 测试环境通过ts文件配置环境变量,然后项目启动时组合这些环境变量信息,生成config default.yml, ci/cd时k8s自动将default.yml中配置的环境变量写入容器中
    • 线上环境直接提供UI页面配置环境变量,然后调用api,后端api通过k8s将变量写入容器中
    • 如何通过k8s读取配置的环境变量并写入容器中且听下回分解

    下面贴一段生产场景下的Dockerfile示例代码(公司应该不会砍我吧!),这里省略k8s如何往容器中注入环境变量,只考虑如何从容器中读取环境变量(假设环境变量已经注入容器中)

    FROM --platform=${BUILDPLATFORM} hub-dev.rockontrol.com/rk-infrav2/docker.io/library/node:17-bullseye as builder
    
    WORKDIR /src
    COPY ./ ./
    
    ARG APP
    ARG ENV
    
    ARG PROJECT_GROUP
    ARG PROJECT_NAME
    ARG PROJECT_VERSION
    
    ARG YARN_NPM_REGISTRY_SERVER
    
    RUN npm install -g --registry=${YARN_NPM_REGISTRY_SERVER} pnpm
    RUN pnpm --registry=${YARN_NPM_REGISTRY_SERVER} install
    
    
    RUN PROJECT_GROUP=${PROJECT_GROUP} PROJECT_VERSION=${PROJECT_VERSION} \
        npx devkit build --prod ${APP} ${ENV}
    
    FROM hub-dev.rockontrol.com/rk-infrav2/ghcr.io/zboyco/webrunner:0.0.7
    
    ARG PROJECT_NAME
    COPY --from=builder /src/public/${PROJECT_NAME} /app
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    下面贴一段代码展示nginx如何组合环境变量,并利用nginx配置写入html的mate标签中

    #!/bin/sh
    
    # This script is used to start the application
    
    # 初始化一个字符串,用于存储拼接后的值
    app_config="${APP_CONFIG}"
    ext_config=""
    
    # 遍历所有环境变量
    for var in $(env | cut -d= -f1); do
        # 检查变量是否以 "APP_CONFIG__" 开头
        if [ "$(echo "$var" | grep '^APP_CONFIG__')" ]; then
            # 去除变量名前缀 "APP_CONFIG__"
            trimmed_var=$(echo "$var" | sed 's/^APP_CONFIG__//')
            # 使用 eval 来获取变量值并拼接到字符串中
            value=$(eval echo "\$$var")
            app_config="${app_config},${trimmed_var}=${value}"
        fi
    done
    
    # 去掉起始的逗号
    export app_config=$(echo "$app_config" | sed 's/^,//')
    
    # 解析app_config变量
    # 以,分割 app_config
    IFS=","
    set -- $app_config
    # 遍历数组
    for config in "$@"; do
        # 以等号分剥数组
        IFS="="
        set -- $config
        # 将单个环境变量单独注入
        ext_config="${ext_config}        sub_filter '__$1__' '$2';\n"
        echo "$1: $2"
    done
    
    # Install envsubst
    echo "Installing envsubst"
    # 将扩展变量替换到 conf.template 中
    sed "s@__EXTENT_CONFIG__@${ext_config}@g" /etc/nginx/conf.d/conf-base.template > /etc/nginx/conf.d/conf.template 
    
    envsubst '${PROJECT_VERSION} ${ENV} ${app_config}' < /etc/nginx/conf.d/conf.template > /etc/nginx/conf.d/default.conf
    
    # Start nginx
    echo "Starting nginx"
    nginx -g 'daemon off;'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    nginx配置代码,mate标签的占位符将会被替换成真实的环境变量,见代码中的sub_filter

    server {
        listen 80;
        listen  [::]:80;
        server_name  localhost;
        root /app;
    
        #开启gzip
        gzip on;  
        #低于1kb的资源不压缩 
        gzip_min_length 1k;
        #压缩级别1-9,越大压缩率越高,同时消耗cpu资源也越多,建议设置在5左右。 
        gzip_comp_level 6; 
        #需要压缩哪些响应类型的资源,多个空格隔开。不建议压缩图片.
        gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
    
        location ~ ^/(static|__built__)/ {
            root /app;
            expires max;
            proxy_cache static_memory_cache;  # 使用内存缓存
            proxy_cache_valid 200 1d;
            proxy_cache_lock on;
        }
    
        location / {
            expires -1;
            try_files $uri /index.html;
    
            add_header X-Frame-Options sameorigin;
            add_header X-Content-Type-Options nosniff;
            add_header X-XSS-Protection "1;mode=block" always;
    
            sub_filter '__PROJECT_VERSION__' '$PROJECT_VERSION';
            sub_filter '__ENV__' '$ENV';
            sub_filter '__APP_CONFIG__' '$app_config';
    
            # 需要将以下字符串替换为注入的扩展环境变量
    __EXTENT_CONFIG__
    
            sub_filter_once on;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    下面贴一段前端如何从html meta标签中读取环境变量

    import appConfig from "../../config";
    
    interface IConfig {
      appName: string;
      baseURL: string;
      version?: string;
      env?: string;
    }
    
    export function getConfig(): IConfig {
      const defaultAppConfig = {
        appName: "",
        version: "",
        env: "",
        baseURL: "",
      };
      console.log("metaEnv", import.meta.env);
    
      if (import.meta.env.DEV) {
        return appConfig;
      } else {
        const appConfigStr = getMeta("app_config");
    
        if (!appConfigStr) return defaultAppConfig;
    
        return parseEnvVar(appConfigStr);
      }
    }
    
    function getMeta(metaName: string) {
      const metas = document.getElementsByTagName("meta");
    
      for (let i = 0; i < metas.length; i++) {
        if (metas[i].getAttribute("name") === metaName) {
          return metas[i].getAttribute("content");
        }
      }
    
      return "";
    }
    
    function parseEnvVar(envVarURL: string) {
      const arrs = envVarURL.split(",");
    
      return arrs.reduce((pre, item) => {
        const keyValues = item.split("=");
    
        return {
          ...pre,
          [keyValues[0]]: keyValues[1],
        };
      }, {} as IConfig);
    }
    
    const BASE_URL = getConfig().baseURL;
    
    const instance = axios.create({
      baseURL: BASE_URL,
      headers: {
        "Content-Type": "application/json",
      },
      timeout: 60000, // 超时时间60秒
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    最后的最后,都贴了Dockerfile了,那就一不做二不休,贴个真实场景下的ci文件源码吧!!!

    stages:
      - ship
      - deploy
    
    ship:
      stage: ship
      image: hub-dev.rockontrol.com/rk-infrav2/gitlab-runner-buildx:0.0.0-b0450fe
      # variables:
      #   MULTI_ARCH_BUILDER: 1
      before_script:
        - echo "${DOCKER_PASSWORD}" | docker login "${DOCKER_REGISTRY}" -u="${DOCKER_USERNAME}" --password-stdin
        - BUILDKIT_NAME=node-buildkit hx buildx ci-setup
      script:
        - export PLATFORM=linux/amd64,linux/arm64
        - |
          if [[ -f ./.platform ]]; then
            source ./.platform
          else
            echo "WARNING, there is no .platform in project, USE default PLATFORM=${PLATFORM} "
          fi
        - hx buildx --with-builder --push --platform=${PLATFORM}
      tags:
        - webapp
    
    deploy:
      stage: deploy
      script:
        - hx config
        - hx deploy
      dependencies:
        - ship
      tags:
        - webapp
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    6.前端应用的Kubernetes部署

    Kubernetes是一个开源的容器编排平台,可以实现自动化部署、扩展和管理容器化应用程序。以下是将前端应用部署到Kubernetes集群的步骤:

    6.1 创建Deployment

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: frontend-app
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: frontend-app
      template:
        metadata:
          labels:
            app: frontend-app
        spec:
          containers:
            - name: frontend-app
              image: my-frontend-app:latest
              ports:
                - containerPort: 3000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    6.2 创建Service

    apiVersion: v1
    kind: Service
    metadata:
      name: frontend-service
    spec:
      selector:
        app: frontend-app
      ports:
        - protocol: TCP
          port: 80
          targetPort: 3000
      type: LoadBalancer
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    6.3 部署到Kubernetes集群

    使用kubectl命令部署应用到Kubernetes集群:

    kubectl apply -f deployment.yaml
    kubectl apply -f service.yaml
    
    • 1
    • 2

    现在,您的前端应用已经在Kubernetes集群中运行,并可以通过LoadBalancer类型的Service外部访问。

    7.关于前端React项目架构

    7.1 核心技术

    7.2 基于openapi自动获取api请求函数

    // src/core/openapi/index.ts
    
    // 示例代码
    generateService({
      // openapi地址
      schemaPath: `${appConfig.baseURL}/${urlPath}`,
      // 文件生成目录
      serversPath: "./src",
      // 自定义网络请求函数路径
      requestImportStatement: `/// <reference types="./typings.d.ts" />\nimport request from "@request"`,
      // 代码组织命名空间, 例如:Api
      namespace: "Api",
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    7.3 调用接口(react-query), 支持自动loading和接口请求联动

    // HelloGet是一个基于axios的promise请求
    export async function HelloGet(
      // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
      params: Api.HelloGetParams,
      options?: { [key: string]: any },
    ) {
      return request<Api.HelloResp>('/gin-demo-server/api/v1/hello', {
        method: 'GET',
        params: {
          ...params,
        },
        ...(options || {}),
      });
    }
    
    // 自动调用接口获取数据
    const { data, isLoading } = useQuery({
      queryKey: ["hello", name],
      queryFn: () => {
        return HelloGet({ name: name });
      },
    });
    
    export async function HelloPost(body: Api.HelloPostParam, options?: { [key: string]: any }) {
      return request<Api.HelloResp>('/gin-demo-server/api/v1/hello', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        data: body,
        ...(options || {}),
      });
    }
    
    // 提交编辑数据
    const { mutate, isLoading } = useMutation({
      mutationFn: HelloPost,
      onSuccess(data) {
        setName(data?.data || "");
      },
      onError() {
        // 清除queryKey为hello的接口数据缓存,自动重新获取接口数据
        queryClient.invalidateQueries({ queryKey: ['hello'] });
      }
    })
    
    mutate({ name: "lisi" });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    8.前端React代码CLI

    9.结语

    • 介绍了gitlab-action的在前端npm领域的基本配置
    • 介绍了前端Dockerfile文件的编写,以及pnpm在docker的优化方案,如何利用buildx生成前端多架构镜像
    • 介绍了生产场景下前端环境变量的使用
    • 介绍了前端React项目技术架构方案
  • 相关阅读:
    Python学习之CSDN21天学习挑战赛计划之5
    SOHO帮客户找新品,如何拿到最优价?要不要选择大型机械类产品?
    2022-08-26 Unity视频播放4——全景视频
    for循环,for of, forEach, map, for in
    Quill 文本编辑器
    使用Golang调用摄像头
    2、Linux权限理解
    CS231n-2022 Module1: 神经网络3:Learning and Evaluation
    Github项目徽标
    Android 12(S) 图像显示系统 -
  • 原文地址:https://blog.csdn.net/luo1055120207/article/details/132742642