本文正在参加「金石计划 . 分割6万现金大奖」

前语

这是一篇叙述怎样依据docker布置微前端项目的文章。本文章会先从理论角度去描绘布置方法,然后再经过代码去完成布置的逻辑和细节。

本文合适以下人群阅览:

  1. 刚知道docker,想经过实践进一步学习docker的前端 er
  2. 想了解怎样用docker布置微前端项目的前端 er

本文中所布置的微前端项目是micro-fe,来源于之前写的一篇叙述怎样接入微前端的文章给 vue-element-admin 接入 qiankun 的微前端开发实践总结 。

阅前须知:关于微前端项目的库房形式

关于微前端项目有两种库房管理形式:

  • 多库房形式主运用子运用别离放在不同的库房里。
  • 单库房形式主运用子运用别离放在同一个的库房里,但分隔放在不同的目录里。

本文推荐运用多库房形式管理形式。由于多库房形式单库房形式有以下长处:

  1. git独立

    • 分支管理相对简洁:不会呈现多个分支别离对应主运用子运用的开发特性。
    • 提交记载不密布:由于只要一个运用的提交而不是一切运用的提交。
    • 创立Releaselabel不混乱:假如存在创立Releaselabel的操作,则需求加上前缀去分辨不同运用,不然会呈现多个同名的版别。
  2. CICD流水线独立:只要对应当时运用的流水线,不会呈现多条对应不同运用的流水线。且假如运用单库房形式,则流水线的触发条件需求愈加复杂,不能呈现一个运用更新触发流水线导致一切运用进入CICD环节。

在下面的布置教程中,会以多库房形式为前提来进行剖析叙述。

当依据 docker 布置单体前端项目时,咱们是怎样做的

所谓学会走才能学会跑。在学习布置微前端项目之前,咱们先了解一下怎样依据docker布置单体前端项目。通常咱们把打包好的前端制品文件放在服务器里的nginx上,且对nginx的装备文件里做途径装备,用于区分获取前后端数据的恳求,如下代码所示:

server{
  # 处理获取前端的恳求
  location / {
    # 前端制品文件寄存在 /usr/share/nginx/html 里
    # 当nginx接收到途径名为/index.css的客户端恳求时,把/usr/share/nginx/html/index.css文件返回给客户端
    root   /usr/share/nginx/html;
    index  index.html index.htm;
  }
  # 假如途径以/api/开头,则判别该恳求是获取后端数据的恳求,因而把该恳求经过反应署理引往后端
  # 当以一般途径前缀匹配时,最长途径会优先匹配,因而即便途径为/api/xx的恳求也契合上面的途径格局,也会以此处规矩进行处理
  location /api/ {
    # proxy_pass中定义后端的uri
    # 当nginx接收到途径名为/api/login的客户端恳求时,nginx会进行反向署理的进程:
    #   1. 向host为http://backend的后端宣布/api/login的恳求,
    #   2. 在接收到呼应后,把呼应返回给客户端
    proxy_pass http://backend/;
  }
}

在装备下,nginx处理恳求的流向作用如下所示:

依据docker布置微前端项目的入门实践攻略

那么,布置单体前端项目这一进程其实便是在服务器启动一个装备好途径分发寄存前端制品nginx容器(不一定非要nginx,看团队的挑选)。接下来经过代码来完成这一进程:

1. 供给 nginx 装备文件

在前端代码项目中,供给用于装备nginx的装备文件nginx.conf,如下所示:

# nginx/nginx.conf
server {
  listen       80;
  listen  [::]:80;
  # 敞开日志记载
  access_log  /var/log/nginx/host.access.log  main;
  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
  }
  location /api/ {
    proxy_pass: http://backend/
  }
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}

2. 供给用于生成镜像的Dockerfile文件

Dockerfile文件用于对前端项目生成镜像,服务器经过镜像生成容器去运转

# 新建构建阶段build-stage,指定node为根底镜像,该阶段用于生成前端制品文件
FROM node:16-alpine as build-stage
# 指定作业目录/app用于寄存前端制品,以便在COPY,RUN以及下一个构建阶段中运用。
WORKDIR /app
# 仿制package.json、package-lock.json、yarn.lock到作业目录里,COPY最终一个参数dest假如是相对途径,则会以作业目录有作为基准,仿制到WORKDIR/<dest>里
COPY package*.json yarn.lock ./
# 下载依靠
RUN yarn install
COPY . .
# 生成制品
RUN yarn build
# 新建构建阶段deploy-stage,指定nginx为根底镜像,该阶段用于装备和运转nginx
FROM nginx:stable-alpine as deploy-stage
# 把build-stage阶段中的前端制品和nginx.conf装备文件仿制到nginx的指定途径里
# 这姿态就能够设置nginx的装备文件,而装备文件中的默许匹配途径是/usr/share/nginx/html,也便是寄存前端制品的途径
COPY --from=build-stage /app/build/ /usr/share/nginx/html
COPY --from=build-stage /app/nginx/nginx.conf /etc/nginx/conf.d/default.conf
# 指定容器在运转进程时监听80端口
EXPOSE 80
# 指定在容器敞开运转时,经过运转以下命令行指令敞开nignx
CMD ["nginx", "-g", "daemon off;"]

关于上述的Dockerfile需求提及以下注意点

  1. build-stage的逻辑中,是先仿制package.jsonyarn.lock且装置依靠后,然后再把一切项目文件仿制到作业目录。为什么不直接把一切项目文件仿制到作业目录然后装置依靠呢?

    这儿需求提及到Dockerfile的镜像以及缓存机制,篇幅较长,因而放在下面的 进阶- 1. 怎样写好Dockerfile 章节中解说。

  2. 关于DockerfileEXPOSE指令,用于指定容器在运转进程中监听特定端口号。该指令不会敞开指定端口到宿主机->容器的映射表,假如要履行此操作请运用docker run -p去手动指定。

    关于EXPOSE的描绘里更多的可看此处

3. 在宿主机构建镜像且运转容器

完成上面的操作后,在宿主机顺次履行以下过程即可布置单体前端项目:

  1. 拉取前端项目代码:经过git clone仿制前端项目代码
  2. 生成镜像:进入前端项目的根目录,运转命令行docker build -t imageName .去生成镜像。命令行中的最终一个参数用于指定Dockerfile的位置。
  3. 生成容器且运转:运转命令行docker run -d --name containerName imageName,以对指定的镜像生成容器且在后台运转。

至此,一个单体前端项目的布置进程现已介绍完毕了。当然在实践中还要考虑其他细节,但主要操作流程现已在本节中叙述清楚了。

那么,怎样布置微前端项目呢

一个微前端项目中包括一个主运用和数个子运用。咱们能够用上节中剖析的布置单体前端方法把主运用子运用都生成对应的nginx容器放在服务器里,在主运用nginx容器里装备途径规矩转发获取子运用资源的恳求。如下图所示:

依据docker布置微前端项目的入门实践攻略

客户端宣布途径前缀为/vue-app/的恳求,则是恳求VueApp子运用的资源;宣布前缀为/react-app/的恳求,则是恳求ReactTSApp子运用的资源。这些恳求都会由主运用nginx容器做反向署理处理。

完成图中的多容器布置需求补充一些额定的细节。接下来咱们分隔主运用子运用来列出这些额定细节:

主运用细节

1. 修正子运用的加载链接

这儿的主运用registerMicroAppsstart子运用进行加载和注册,在registerMicroApps的相关代码中用以下写法:

const isProd = process.env.NODE_ENV === "production";
registerMicroApps([
  {
    name: "react app",
    // 假如是出产环境则用特定前缀途径,当客户端宣布该恳求时,服务器中的主运用nginx容器收到恳求会做反向署理分发到后端
    entry: isProd ? `//${location.host}/react-app/` : "//localhost:3001",
    container: "#app-react",
    loader,
    activeRule: "/app-react/index",
    props: {
      basepath: "/app-react/index",
    },
  },
  {
    name: "vue app",
    entry: isProd ? `//${location.host}/vue-app/` : "//localhost:3002",
    container: "#app-vue",
    loader,
    activeRule: "/app-vue/index",
    props: {
      basepath: "/app-vue/index",
    },
  },
  // ...省掉其余子运用
]);

2. 结合DockerNetwork修正nginx.conf装备文件

在展现nginx.conf代码之前,咱们需求了解Docker Network这一概念:当咱们成功启动一个容器时,该容器会接入到Docker Network实例里,假如docker run指令中有用--network参数指定网络,则接入到指定网络里,不然默许接入到bridge network里。咱们能够经过docker network ls来检查当时存在哪些网络:

docker network ls
输出:
NETWORK ID     NAME      DRIVER    SCOPE
95a79d75ba56   bridge    bridge    local
ebb2b72a64a9   host      host      local
b3fb09dbb1d0   none      null      local

上面三个network是默许存在的网络,关于网络类型bridgenonehost的区别可检查#network-drivers。

咱们能够经过docker network inspect networkName指令检查指定network中有哪些接入的容器:

docker network inspect bridge
输出:
[
    {
        "Name": "bridge",
        // 省掉其他字段
        // 在Containers里能够检查接入的容器
        "Containers": {
            "1e2d0761c5ad2154c7cfc2c9c912c094f3dda59623b1a992c1a66d9a14c4dedf": {
                "Name": "master-app",
                "EndpointID": "b206d19490336b0527496cf43c90b61befbd036b6c2d9909926124c0979458ea",
                "MacAddress": "02:42:ac:11:00:04",
                "IPv4Address": "172.17.0.4/16",
                "IPv6Address": ""
            },
            "2c94c287b6ff3ec6561864cbad86f4fbd57ed39ec89a4bbf432f245887332c50": {
                "Name": "vue-app",
                "EndpointID": "3ee5696cd3cc0424c9246d1c4e5c1c5de76a4f638b4317a6df5fd7e0c0cb13ab",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            },
            "e2299a334abdec2a01b0caca15cacf980dc3f9c55ea54519758789c8daa3742e": {
                "Name": "react-ts-app",
                "EndpointID": "ec876f84a999fb7f77cd8f7185e077bb597f0a50daa850b55729b4b9662ce50a",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
    }
]

从上面的输出信息可知:

  • master-app,即主运用nginx容器的 ip 为 172.17.0.4
  • vue-app,即VueAppnginx容器的 ip 为 172.17.0.3
  • react-ts-app,即ReactTSAppnginx容器的 ip 为 172.17.0.2

在同一网络中,容器能够经过 ip 相互拜访。例如,当VueAppnginx容器以 80 端口EXPOSE时,主运用nginx容器可经过 172.17.0.3 拜访VueAppnginx容器

但在实践中,咱们偏向于把需求通讯的容器接入到自定义网络(经过docker network create networkName指令来创立)。由于自定义网络比照于默许bridge网络有一个长处:

  • User-defined bridges provide automatic DNS resolution between containers.

    Containers on the default bridge network can only access each other by IP addresses, unless you use the –link option, which is considered legacy. On a user-defined bridge network, containers can resolve each other by name or alias.

    引自Differences between user-defined bridges and the default bridge

引文中的意思是:自定义网络会对其接入的容器供给DNS主动解析: 在默许bridge网络中,容器能够经过 ip 拜访其他容器。而在自定义网络,容器能够经过容器名或者昵称(docker run --network-alias指定)来拜访别的容器。例如:主运用nginx容器能够经过http://vue-app去拜访VueAppnginx容器

选用容器名作为域名拜访容器能够说是十分方便。并且因迭代更新导致镜像改变,然后生成新的子运用nginx容器时,即便容器名字不变,但从头接入到网络时,容器 ip 可能会改变。此刻假如再次经过固定 ip 拜访子运用nginx容器则会拜访失利。但采取容器名拜访就不用担心这种状况。

因而,一切微前端项目中的主运用nginx容器子运用nginx容器,都接入到自定义网络里,就能够经过容器名作为域名拜访彼此。在这前提下,咱们的nginx.conf的写法如下所示:

server {
    listen       80;
    listen  [::]:80;
    access_log  /var/log/nginx/host.access.log  main;
    error_log  /var/log/nginx/host.error.log  error;
    location / {
        try_files $uri $uri/ /index.html;
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
    # 关于途径前缀为/vue-app/的恳求,反向署理到名为vue-app的容器
    location /vue-app/ {
        proxy_pass http://vue-app/;
    }
    # 关于途径前缀为/react-app/的恳求,反向署理到名为react-ts-app的容器
    location /react-app/ {
        proxy_pass http://react-ts-app/;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

至此,主运用的细节现已讲完。

子运用细节

子运用方面需求在打包进程中给引入资源的途径加上公共前缀。

对此,在运用vue-cli脚手架创立的VueApp运用中,vue.config.js装备如下所示:

const isProd = process.env.NODE_ENV === "production";
module.exports = defineConfig({
  transpileDependencies: true,
  // 加上公共途径前缀
  publicPath: isProd ? "/vue-app/" : undefined,
  //...省掉其他不变的装备
});

对此,在运用cra脚手架创立的在ReactTSApp运用中,.rescriptsrc.js装备如下所示:

const isProd = process.env.NODE_ENV === "production";
module.exports = (
  isProd ? [] : [["use-stylelint-config", ".stylelintrc.js"]]
).concat({
  webpack: (config) => {
    // 加上公共途径前缀
    config.output.publicPath = isProd ? "/react-app/" : undefined;
    //...省掉其他不变的装备
  },
});

至此,子运用的细节已讲完。

动手实践:依据Githb Action来完成CD以布置微前端项目

为什么要挑选Githb Action来完成CD

无妨考虑一个问题:CD流程要放在出产机器上履行吗?

大多数公司里,会用一台机器或者集群来担任CICD操作,在走完CICD流程后把制品上传到出产机器上布置。由于CICD操作是会占用机器的资源和算力的,假如CICD流程放在出产机器上运转,毫无疑问会下降出产机器处理外部恳求的速度。因而要专门建立担任履行CICD的机器或集群。

但关于咱们个人开发而言,手上往往只要一台自费购买的机器,并且机器装备还不怎样高,都是 1 核 1G 的(都是薅首购优惠的,薅完腾讯云就薅阿里云)。那假如咱们要在自己的低装备机器上做微前端CD操作,一起跑主运用和好几个子运用CD流程,那机器机器会处于高负载的状况。我在自己的出产机器上跑自己的微前端项目(主运用+四个子运用)的CD流程时,CPU运用率达到 60%多,连在本地用SSH拜访出产机器都失利。

但咱们也没有另一台的机器用于跑CICD。在这种状况下就能够挑选Github Action。关于公共库房和私有库房,他都能够分配机器来运转咱们的CICD流程,咱们只需求编写yml文件来指定CICD流程即可。我之前写过一篇关于Github Action的入门等级文章作为前端,要学会用 Github Action 给自己的项目加上 CICD,有兴趣的能够看看。这儿就不再介绍Github Action的入门常识了,直接进入设计环节。

CD的设计与完成

在设计CD流程时,咱们需求考虑两点:

  1. CD的履行机遇
  2. CD履行什么操作

下面经过本次CD的流程图来展现上述两点:

依据docker布置微前端项目的入门实践攻略

首要准备docker.json文件,用于定义镜像名、镜像版别和容器名:

{
  "name": "react-ts-app",
  "version": "0.1.9"
}

为什么不取package.json中的nameversion字段来定义呢?由于咱们每次迭代更新而生成新版别的镜像时,都要更新版别,假如版别取自package.json里的version,则会导致Dockerfile中的RUN yarn install指令对应的缓存失效。导致每次生成镜像时,即便运用依靠没改变,但由于package.json改变了导致要从头装置依靠。关于Dockerfile的缓存细节会放到下面的 进阶 – 1. 怎样写好Dockerfile 章节中剖析。

接下来编写yml文件对应上面的CD流程图:

name: CD
on:
  # 履行机遇设计为master分支被推送时触发
  push:
    branches: master
jobs:
  # ReadInfo Job用于读取且导出package.json里的信息,用于下一个Job作为环境变量运用
  ReadInfo:
    runs-on: ubuntu-latest
    outputs:
      info: ${{ steps.read-info.outputs.info }}
    steps:
      # 拉取代码
      - name: Checkout repository
        uses: actions/checkout@v3
      # docker.json,把其内容即json字符串注入到寄存输出成果的outputs.info
      - name: Read Info
        id: read-info
        run: |
          JSON=`cat ./$APP_PATH/docker.json`
          JSON="${JSON//'%'/''}"
          JSON="${JSON//$'n'/''}"
          JSON="${JSON//$'r'/''}"
          echo "info=$JSON" >> $GITHUB_OUTPUT
  CD:
    runs-on: ubuntu-latest
    needs: ReadInfo
    # 依据上一步ReadInfo Job中的输出成果info来注入环境变量
    env:
      # 容器名取自docker.json里的name
      Container: ${{fromJson(needs.ReadInfo.outputs.info).name}}
      # 镜像名和镜像标签取自docker.json里的name和version
      Image: ${{fromJson(needs.ReadInfo.outputs.info).name}}:${{fromJson(needs.ReadInfo.outputs.info).version}}
      # 镜像文件为docker.json里的name + 后缀.tar
      ImageFile: ${{fromJson(needs.ReadInfo.outputs.info).name}}.tar
      # 发布版别取自docker.json里的version
      Release: ${{fromJson(needs.ReadInfo.outputs.info).version}}
    steps:
      # 拉取代码
      - name: Checkout repository
        uses: actions/checkout@v3
      # 装置docker
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      # 创立docker镜像
      - name: Build Docker Image
        uses: docker/build-push-action@v3
        with:
          # 缓存类型运用gha,该类型能很好的和github action结合完成缓存镜像,更多细节可看 https://docs.docker.com/build/building/cache/backends/gha/
          cache-from: type=gha
          cache-to: type=gha,mode=max
          tags: ${{env.Image}}
          context: .
          load: true
      # 导出docker镜像文件
      - name: Export Image as Tar
        run: |
          docker images
          docker save ${{env.Image}} > ${{env.ImageFile}}
      # 创立Release版别且上传镜像文件
      - name: Create GitHub Release
        id: create_release
        uses: softprops/action-gh-release@v1
        with:
          token: ${{ secrets.PROJECT_ACCESS_TOKEN }}
          tag_name: ${{ env.Release }}
          name: ${{ env.Release }}
          draft: false
          prerelease: false
          # 附上镜像文件
          files: |
            ${{ env.ImageFile }}
      # 把镜像文件上传至出产服务器
      - name: Upload Image Tar to Deploy Server
        # easingthemes/ssh-deploy的原理时运用rsync+ssh上传文件
        uses: easingthemes/ssh-deploy@main
        env:
          SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_TOKEN }}
          ARGS: "-avzr --delete"
          SOURCE: ${{env.ImageFile}}
          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
          REMOTE_USER: ${{secrets.REMOTE_USER}}
          TARGET: ${{secrets.TARGET}}
      # 登陆进出产服务器进行操作
      - name: Run Docker Container
        uses: appleboy/ssh-action@master
        env:
          Network: microfe
        with:
          host: ${{secrets.REMOTE_HOST}}
          username: ${{secrets.REMOTE_USER}}
          key: ${{ secrets.DEPLOY_TOKEN }}
          # 过程:
          # 1. 中止移除容器
          # 2. 移除已有镜像
          # 3. 进入寄存镜像文件的目录且把镜像文件转为镜像
          # 4. 检测若不存在microfe网络,则创立microfe网络
          # 5. 依据镜像创立且运转容器,且把容器接入到microfe网络
          # 6. 移除镜像文件
          script: |
            ${{ format('docker ps -q --filter "name={0}" | grep -q . && docker rm -f {0}', env.Container) }}
            ${{ format('docker rmi -f $(docker images -q  --filter reference="{0}")', env.Container) }}
            ${{ format('cd {0}' , secrets.TARGET)}}
            ${{ format('docker load < {0}', env.ImageFile)}}
            ${{ format('docker network ls -q --filter "name={0}" | grep -q . || docker network create {0}',env.Network)}}
            ${{ format('docker run -d --name {0} --network {1} {2}',env.Container,env.Network,env.Image)}}
            ${{ format('rm {0}',env.ImageFile)}}

最终运转成果如下所示:

依据docker布置微前端项目的入门实践攻略

咱们也能够拜访micro-fe 的 CD 履行记载来检查其间的履行细节。

进阶

1. 怎样写好Dockerfile

本节中的内容是Docker官网中总结出来的。假如在阅览以下内容后还想深入学习可点击阅览Optimizing builds with cache management。

关于Dockerfile的分层机制

拿下面的Dockerfile代码进行剖析:

FROM node:16-alpine
WORKDIR /app
COPY . .
RUN yarn install
RUN yarn build

Dockerfile中,FROM指令除了指定根底镜像外,还用于声明一个新的构建阶段构建阶段在履行完毕后都会输出一个新的镜像作为产物(下称输出镜像)。输出镜像中有多个分层,每个分层都对应一条指令。拿上述Dockerfile代码来画图剖析如下所示:

依据docker布置微前端项目的入门实践攻略

每一个分层都是依据前一个分层进行构建的。咱们能够把整个输出镜像当作是一个寄存分层的栈。每一个分层都能够会放到缓存里,假如下一次再次履行相同的Dockerfile代码指令时,假如缓存中有对应该指令的分层,可直接取出运用,然后越过构建分层的进程,减少构建输出镜像的时刻。但假如指令发生改变,例如,COPY所操作的文件发生改变,会导致缓存失效,需求重构对应的分层。如下所示:

依据docker布置微前端项目的入门实践攻略

而当一个指令发成改变导致分层重构后,该指令下面的一切指令都不能运用缓存分层只能全部重构。由于缓存中的分层是依据前一分层构建。如下所示:

依据docker布置微前端项目的入门实践攻略

假如咱们在构建镜像时运用上述的Dockerfile,在每次迭代时,由于新增修正开发代码,会让COPY . .缓存失效然后导致对应的分层重构,继而履行yarn installyarn build两个进程。但假如咱们选用下面的写法:

FROM node:16-alpine as build-stage
WORKDIR /app
COPY package*.json yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build

这姿态就能够在依靠不变的状况下,越过yarn install环节,然后加速构建镜像的速度。

咱们能够比照在上一章Github Action CD流水线下,履行RUN yarn install指令时,不运用缓存和运用缓存的比照:

不运用缓存:耗时 79 秒

依据docker布置微前端项目的入门实践攻略

运用缓存:无须耗时,直接越过

依据docker布置微前端项目的入门实践攻略

至于更多关于怎样充分利用缓存的写法可阅览How can I use the cache efficiently?

关于上述Dockerfile代码的进一步改善

关于以下Dockerfile代码,还存在改善的空间:假如我只改用于装备nginx规矩的nginx.conf装备文件,其他的不做修正。那么是能够经过缓存机制来越过yarn installyarn build环节的。

FROM node:16-alpine as build-stage
WORKDIR /app
COPY package*.json yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build
FROM nginx:stable-alpine as deploy-stage
COPY --from=build-stage /app/dist/ /usr/share/nginx/html
COPY --from=build-stage /app/nginx/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

关于上述代码,咱们能够改善成以下代码:

FROM node:16-alpine as build-stage
WORKDIR /app
COPY package*.json yarn.lock ./
RUN yarn install
# COPY . .
# 把上面的COPY换成下面几行COPY指令。以明确指定仿制除nginx以外的参加到yarn build的文件和目录
# 注意COPY指令在仿制目录时会主动解包,只仿制目录里面的子文件和子目录,因而方针目标必须是同名的目录
COPY src ./src
COPY public ./public
COPY .env .rescriptsrc.js tsconfig.json ./
RUN yarn build
FROM nginx:stable-alpine as deploy-stage
COPY --from=build-stage /app/build/ /usr/share/nginx/html
# COPY --from=build-stage /app/nginx/nginx.conf /etc/nginx/conf.d/default.conf
# 把上面的COPY换成下面的COPY指令。由于经过上面的调整,build-stage的作业目录里不存在nginx文件,需求直接从当时目录里仿制曩昔
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

上面的写法也存在着缺点,便是需求写多行COPY指令以明确指定每一个参加生成制品的文件目录,缺一个都会导致制品失利。

为了检查上面写法的作用,我触发了下面三种状况的CD,并得出以下实验数据:

  1. package.json或者yarn.lock发生改变: 构建镜像时刻为2m53s,其间RUN yarn install消耗85.5sRUN yarn build消耗25.6s,更多流水线细节可看此处
  2. package.jsonyarn.lock没改变,开发代码发生改变: 构建镜像时刻为1m20s,其间RUN yarn install射中缓存无耗时,RUN yarn build消耗40.2s,更多流水线细节可看此处
  3. package.jsonyarn.lock没改变和开发代码没发生改变,nginx.conf发生改变: 构建镜像时刻为10s,其间RUN yarn installRUN yarn build都射中缓存无耗时,多流水线细节可看此处

2. 怎样完成回滚操作

回滚是指把服务器上的项目撤回到上一个版别,用于在上线新版别的项目后,发现紧迫 bug。需求紧迫手动撤回到旧版别的情景。接下来经过Github Action完成一个简单可用的Rollback回滚流水线。直接附上Rollback流水线的流程图:

依据docker布置微前端项目的入门实践攻略

接下来编写yml文件对应上面的Rollback流程图:

name: Rollback
on:
  workflow_dispatch:
    inputs:
      version:
        description: "choose a version to deploy"
        required: true
jobs:
  Rollback:
    runs-on: ubuntu-latest
    env:
      # 指定从头生成的容器名
      Container: vue3-ts-app
      # 指定镜像名
      Image: vue3-ts-app:${{ github.event.inputs.version }}
      # 指定要拉取的镜像文件
      ImageFile: vue3-ts-app.tar
      # 指定要从哪个发布版别拉取镜像文件
      Release: vue3-ts-app/${{ github.event.inputs.version }}
    steps:
      - name: Echo Input
        run: |
          echo "Version: $VERSION"
      # 从指定的发布版别里拉取镜像文件
      - name: Download ImageFile
        uses: dsaltares/fetch-gh-release-asset@master
        with:
          version: "tags/${{env.Release}}"
          file: ${{env.ImageFile}}
          token: ${{ secrets.GITHUB_TOKEN }}
      # 把镜像文件上传至出产服务器
      - name: Upload Image Tar to Deploy Server
        uses: easingthemes/ssh-deploy@main
        env:
          SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_TOKEN }}
          ARGS: "-avzr --delete"
          SOURCE: ${{env.ImageFile}}
          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
          REMOTE_USER: ${{secrets.REMOTE_USER}}
          TARGET: ${{secrets.TARGET}}
      # 登陆进出产服务器进行操作
      - name: Run Docker Container
        uses: appleboy/ssh-action@master
        env:
          Network: microfe
        with:
          host: ${{secrets.REMOTE_HOST}}
          username: ${{secrets.REMOTE_USER}}
          key: ${{ secrets.DEPLOY_TOKEN }}
          # 过程:
          # 1. 中止移除容器
          # 2. 移除已有镜像
          # 3. 进入寄存镜像文件的目录且把镜像文件转为镜像
          # 4. 检测若不存在microfe网络,则创立microfe网络
          # 5. 依据镜像创立且运转容器,且把容器接入到microfe网络
          # 6. 移除镜像文件
          script: |
            ${{ format('docker ps -q --filter "name={0}" | grep -q . && docker rm -f {0}', env.Container) }}
            ${{ format('docker rmi -f $(docker images -q  --filter reference="{0}")', env.Container) }}
            ${{ format('cd {0}' , secrets.TARGET)}}
            ${{ format('docker load < {0}', env.ImageFile)}}
            ${{ format('docker network ls -q --filter "name={0}" | grep -q . || docker network create {0}',env.Network)}}
            ${{ format('docker run -d --name {0} --network {1} {2}',env.Container,env.Network,env.Image)}}
            ${{ format('rm {0}',env.ImageFile)}}

最终的运转成果如下所示:

依据docker布置微前端项目的入门实践攻略

关于上图中Rollback流水线的更多履行细节可看此处

跋文

这篇文章写到这儿就完毕了,假如觉得有用能够点赞保藏,假如有疑问能够直接在谈论留言,欢迎交流 。