这一次,我把构建流程放进容器里执行了…

哈喽大家好啊,我是广州小井。好久没更新前端发布平台实战系列文章了,那么这次给大家带来一篇将构建流程放到 Docker 容器里的实战分享,快到碗(容器)里来!

看到标题的你不用猜也知道我要讲 Docker 了…没错!本文主要就是要讲 Docker 相关的实战内容,但是本文不会对 Docker 的基础入门进行展开,也有你有一点 Docker 认知来看本文会有比较好的收益~

一、场景

有看过该专栏前面文章的同学可能都知道,我之前通过平台化的方式桥接了 Jenkins 来实现前端项目的构建。可以简单理解为我做了个发布平台,简化了很多 Jenkins 上面的配置、操作,在平台中提供了一键构建、回滚等功能,让每一个开发同学可以以更小的心智负担来构建、发布前端项目。

当然,到目前为止我依然保持着业内“能用就行”的优良传统,有时间摸鱼绝对不搞其他事情!但是最近遇到一些比较频发的问题,所以是时候吹响优化的号角了!以下就听我一一道来吧~

1. 现状

当前我面临的现状就是所有前端项目的构建环境只有一个…怎么理解环境只有一个?还记得之前的实战文章中,前端项目的构建都是通过 Jenkins 来实现的吗?所以 Jenkins 这台机器上面装的 nodenpmpnpm 等等基础工具的版本都是全局唯一的。

那么对于不同业务线,他们可能在构建前端项目时对 nodepnpm 等工具的版本有一定的要求,当时应对这种场景是通过开放 nvm 操作给开发者去搞的。nvm 中我指定了一个默认版本的 node 比如是 v16,如果某个业务同学要用 v14 那么他可以在构建脚本中自己通过 nvm 命令切换 node 版本!

乍眼一看这样处理好像也没什么问题,还挺灵活噢,自己装 node、切换 node 都可以实现了。只要业务同学不会过分到 nvm alias default vXXX 这样就好…但是如果说对 pnpm 这些基础包版本有一定的要求呢?也许会出现下面这样的场景:

image.png

上述这样的操作下来,那么其他团队、其他开发同学用发布平台构建项目的时候,可能会在无意地、被动地使用了一个其他版本的工具包…然后就会有突如其来的报错信息,让人看到心慌的 ERR 日志出现!

2. 存在的问题

前文简单介绍了构建环境共用的场景,那么这样子会有什么问题呢?为什么说我们需要优化构建环境,通过启动容器来构建前端项目呢?大家再接着往下看。

如果仅仅说版本的问题,node 版本切换的话影响好像并不至于要上 Docker,因为 nvm 切换就只运行在当前命令窗口,只要不改掉默认值还是很稳的。那么我本次优化构建环境的动机,主要是为了解决以下两个问题:

1.pnpm7pnpm8 lockfile 导致装包时报错的问题频发。

装包过程会出现错误:ERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE。通过此错误也可以 Google 出很多解决方案,比如说通过以下命令来装包:

pnpm install --frozen-lockfile

这个问题的原因呢,大概就是这两个版本之间的 lockfile 不兼容,我们可以看到 gayhub 中有个老铁的评论(详细了解大家戳:github.com/pnpm/pnpm/i… 这个地址):

image.png

2.npm cache 导致装包失败的问题。

这个问题应该也是比较常见的,报错时会出现如下提示:

npm ERR! Cannot read property 'pickAlgorithm' of null

问题的原因大概是 npm 装包时,根据包名和版本在比对缓存时产生了一些错误(哈哈哈,具体我也不是特别清楚,如果大佬们知道评论区告诉我吧),然后会导致整个构建流程失败。针对这个问题,当然也有简单的解决方案,就是通过如下命令清除 npm 缓存:

npm cache clear --force

关于这个报错的详情大家可以点开 stackoverflow 来看看~我就不展开说了。

回到本文,针对当前的现状和遇到的问题,我简单总结出以下两个需求点

  1. 固定 pnpm 版本供构建前选择。以此避免出现线上机器和本地机器 pnpm 版本导致装包时出现 lockfile 的问题

  2. 杜绝 npm 缓存。解决 npm 缓存导致装包失败的问题

二、方案设计

根据前文提及的两点需求点,我直接就想到了 Docker,于是乎就有了本文的内容…

1. 容器构建设想

针对这两个需求,我们分别来看 Docker 可以给我们提供什么帮助:

  • 关于需求1,我可以预先制作好一些 Docker 的基础镜像,比如说 node-vx.x.x & pnpm-vx.x.x 这样的基础镜像,然后让用户在构建前选择跟自己本机相同的 pnpm 版本的镜像作为运行构建的环境。

  • 关于需求2,更直接了,通过 Docker 来启动一个容器,通过容器的隔离效果直接躲过了 npm cache 这个老贼!毕竟在容器内运行是一个独立于宿主?的环境。

当然,需求2的内容并不能这么肤浅地就做完了。因为当我丢弃了 npm cache 后,还需要解决装包慢的问题。毕竟在日常的工作中,npm 装包慢、甚至偶尔的超时也是个很影响体验的问题。其实装包速度慢这个问题,之前一直想在内部搭一个类似 cnpm 这样的站点(让其做 npm 包的缓存),尽量让包安装都在内网进行,加速包安装的同时也能一定程度避免 npm 等源抽风导致安装超时的问题。

虽然但是,跑题了啊!解决装包慢的问题不会在本文中展开讲因为我都还没做这一步!),到时候搞完了,有时间会另外写一篇文章来分享内部搭一个 npm 包缓存站点的实战文章~

继续回到本文,这里我简单地通过图来分析一下现在的构建流程:

docker1.png

如图所示,当前执行构建过程就是在jenkins 本机的一个路径中(也就是 jenkinsworkspace)。对 jenkinsworkspace 这些不是很熟悉的可以简单理解成如下:

  • jenkins 就是你的电脑?器。

  • workspace 就是你把项目放在哪里。

现在,我将在原来的基础上,把构建流程这一步骤放到 Docker容器 里执行:

docker2.png

可以看出跟第一张图的区别在于构建项目前先启动了一个 Docker容器,然后映射了宿主机的路径到容器中再执行的项目构建(蓝色虚线框)。所以,这里的项目构建是在一个相对独立的环境里面去运行的,并且项目打包完后的产物 dist 依然可以原封不动地放到宿主机的一个项目路径内(因为做了路径映射)。

2. 细节详解

这里对不太熟悉 Docker 的同学来展开细说上述的一些节点的,如果是熟悉 Docker 的同学可以跳过了。

这里先简单介绍一下跟本文相关的 Docker 中的两个概念(详细地大家自行了解哈,不会太深入展开):

  • 镜像。可以理解为这是一个最基础的操作系统包,里面安装了一些软件如 node、pnpm 等,当我们启动了这个镜像就能在里面运行我们的一些程序了。

  • 容器。将上述的镜像运行起来。可以简单想象成是对上述的操作系统包做一个解压然后执行,在当前机器上跑起来了一个独立的操作系统。

再回到上文的容器中构建的流程,我们可以再细化成下图:

docker3.png

这个图大家可以看到比之前的图多了一个镜像的节点。没错,我们可以通过 dockerhub 下载镜像(比如说 node),也可以自己用 Dockerfile 撸一个基础镜像(我自己也撸了个)。(这里要注意一下,不是把自己的项目打成镜像,而是用基础镜像启动的容器来构建我们的项目而已。

image.png

当我们已经配置、下载好基础镜像之后,我们就可以通过基础镜像来启动一个空容器运行构建过程,这样我们的构建过程就可以在一个相对独立的环境中运行,可以避免 npm cache 的干扰,这就能实现我们的需求2了。

那对于需求1呢,就需要我们对基础镜像做手脚了。我们可以制作 node+pnpm7node+pnpm8 两个基础镜像,然后在用户构建前提供构建选项给用户选择,这样一来我们也就把需求1给实现了,可以有效地规避由于 pnpm 版本不同导致装包时的报错问题了。

最后这里要提一嘴宿主机的路径映射

以至于我为什么要做路径映射,这是因为目前部署实现是在 jenkins 的 workspace 中拿到产物去部署的,所以我要保证改在容器内构建后,宿主机的 workspace 中依然有产物 dist。(这一块因场景不同会有不同的做法,我就不深入展开了)

三、实战容器构建

经过前面两个小节的讲解,基本已经明确了场景、问题和解决方案了,马上进入到实战的环节。实战环节分为两块:

  1. 制作基础镜像。主要是 node+pnpm7node+pnpm8

  2. 容器内构建项目。改造当前构建脚本,映射宿主机路径。

1. 准备基础镜像

这一步我就简单演示如何搞个基础镜像,具体到实际开发中,大家还是要自己按需来折腾折腾。比如说现在我们要做一个 node16+pnpm7的基础镜像,我们可以搞一个如下的 Dockerfile(本文不会展开细节,想详细学习的同学自行找找资料吧,网上全都是教你如何写 Dockerfile 的):

# dockerfile

FROM node:16.20


MAINTAINER xxx

# 安装 pnpm、yarn
RUN npm install pnpm@7.x.x -g && \
    npm install yarn -g

# npm config
RUN npm config set @scope/xxx/xxx

如上我们简单的整了个 Dockerfile,依赖了在 dockerhub 拉下来的 node16 镜像,并且预装了 pnpm@7.x.x。那么这个时候,我们只需要通过 docker build 命令来创建一个基础镜像就行了。比如说这样:

docker build -t base_node16:1.0.0 .

执行完之后 build 命令后,我们就可以通过 docker images 查看到我们创建的镜像了:

image.png

2. 启动容器&构建项目

回顾专栏文章介绍配置 Jenkins job 的章节,之前我们直接把构建相关的命令配置在 Execute shell 中:

image.png

我们会在上面去写执行项目构建的脚本如:

npm install
npm build:prod

现在,由于我们要在 docker容器 中去执行构建,所以 Execute shell 输入框中的内容不再是直接写构建命令,而是要写成:启动容器 & 容器中执行构建命令。

基于此,我们可以把上面的这些命令放到一个 sh 文件中,以便后续启动 docker容器 后去执行,所以第一步改造,我们首先是要生成一个 sh 文件。我们简单地通过 echo -e 来做:

echo -e "#\!/bin/sh

npm install

npm build:prod" > build.sh

如上代码块,执行后会生成一个 build.sh 文件如下:

image.png

最后,我们只需要启动容器,并执行这个 sh 文件就完成我们的容器内构建过程了。那关于容器的启动命令也不是本文的主要内容,我也就先一笔带过了,详细的大家去翻阅下文档或者看看专门介绍它的文章吧。(有映射路径需要的话自行了解下 -v 命令)

docker container run -v /workspace/xxx:/dockerPath --rm -w /dockerPath --name test base_node16:1.0.0 ./build.sh

如上命令中,我们就通过 docker run 来运行了 base_node16:1.0.0 这个镜像,其中一些参数我大概讲解一下:

  • -v:映射宿主机路径到容器中。

  • --rm:构建过程成功后删除中间环节的容器。也就是运行完就删容器了,不用自己手动去删

  • -w:配置工作路径,使得 build.sh 是在当前工作路径运行的

  • --name:容器名称。运行后可以通过 docker ps 查看到

  • base_node16:1.0.0:代表要运行的基础镜像和tag(这里是我们上面自己制作的镜像)

总的来说,我们需要把 Jenkins 中的 Execute shell 改成如下即可实现容器内构建项目

echo -e "#\!/bin/sh

npm install

npm build:prod" > build.sh



docker container run -v /workspace/xxx:/dockerPath --rm -w /dockerPath --name test base_node16:1.0.0 ./build.sh

基于这种改造方式,我可以保证原本的构建、部署流程不受影响,又可以满足当前的需求,算是一个成本较低的实现方案了。可能有部分同学比较难仅从这一篇文章中 get 到全部的点,毕竟一次性接受比较多的新概念不好理解。不过没关系,只要自己实战过一次了,就什么都清楚了。

当然,有时间了我也会接着出几篇文章来详细介绍 Docker 基础镜像的制作 和 其他相关的内容~

总结

本文主要从一个场景和当前面临的问题为切入点介绍了将项目的构建步骤放到容器中执行的方案,将原本的单一构建环境改造为容器环境,以此解决了当前面临的问题。那在整个容器构建实战环节中,最为关键的无非就是制作基础镜像并启动容器,再在容器里面进行项目的构建。当然,现在在容器内构建会面临装包慢的问题,等我实战完有时间了再来水一篇吧。anyway,这次我把构建放到容器里面了!

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYNxGlpP' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片