docker nodejs开发环境,node docker镜像
node.js速度课程简介:进入学习
本文将展示如何用节点程序优化Docker镜像(优化思路通用,不考虑程序),主要解决CI/CD镜像尺寸过大和镜像构建速度等问题。本文演示了如何逐步优化Dockerfile。建议先赞一下再看。看完赞取消也不是不可能。
优化结果如下:
大小从 1.06G 到 73.4M
构建速度从 29.6 秒到 1.3 秒(比较第二次构建的速度)
[推荐研究:《nodejs 教程》]
Node 项目
我简单的写了一个自己用的微信-bot,然后用这个项目演示了如何优化Docker镜像。
以下是Docker刚开始写的没有仔细研究的Dockerfile文件。
自节点:14.17.3
#设置环境变量
环境节点_环境=生产
环境应用程序路径=/节点/应用程序
#设置工作目录
工作目录$APP_PATH
#将当前目录中的所有文件复制到镜像的工作目录中。dockerignore指定的文件将不会被复制。
收到。$APP_PATH
#安装依赖项
运行纱线
#暴露端口
暴露4300
CMD yarn startbuild后,如下图,我的简单节点程序镜像居然有1G多。接下来,我们将逐步优化和减少这个大小。
优化前言
优化之前,有些事情是我们必须知道的。解决问题的第一步是先找出问题的原因。
Dockerfile文件包含一系列指令,每条指令构建一个层,所以每条指令的内容就是描述这个层是如何构建的。
Docker镜像不只是一个文件,而是一堆文件,其中最重要的是层(层)。
镜像建立的时候会一层一层的建立,前一层是后一层的基础。
每一层建成后都不会再发生变化,后一层的任何变化都只发生在自己的层上。比如删除上一层文件的操作,实际上并没有删除上一层的文件,只是在当前层标记为删除。当最终容器运行时,虽然这个文件不会被看到,但它实际上会一直跟随图像。
镜像层会被缓存重用(这也是从第二次开始建镜像会更快的原因,优化建镜像速度的原理也是基于缓存原理)。
当Dockerfile的指令被修改,操作的文件被改变,或者构造镜像时指定的变量不同时,对应的镜像层缓存就会失效。
docker build 的缓存机制,docker 是怎么知道文件变化的呢?
Docker采用的策略是获取Dockerfile的内容(包括文件的部分inode信息)并计算一个唯一的哈希值。如果哈希值没有变化,可以认为文件内容没有变化,可以使用缓存机制,反之亦然。
当某一层的镜像缓存失效时,该层之后的所有镜像缓存都会失效。
镜像的每一层只记录文件更改。容器启动时,Docker会计算镜像的每一层,最终生成一个文件系统。
当我知道这些的时候,我才恍然大悟,我们用的操作系统,比如Android,ios,win,mac等等。实际上是一个文件系统,我们的软件接口相互交互。事实上,我们正在读写文件,在我们的网页上写一个子弹框,并操作dom,这是在内存中读写本地文件或数据。不知道有些个人观点对不对。我是一个前端编码员,不是专业人士。
参考:docker镜像分层原理
好了,我们已经知道镜像是由多层文件系统组成的。为了优化它的大小,我们需要减少层数,每层应该尽可能只包含它需要的东西。任何多余的东西应在层施工结束前清理干净。下面开始正文。
优化 Dockerfile
优化第一层 FROM node:14.17.3
方案一:使用 node 的 Alpine 版本
这也是大多数人都知道的优化镜像的手段。Alpine是一个很小的Linux发行版,如果选择Alpine版本的Node会有很大的提升。我们把这句话改成node的指令:14.17.4-alpine(可以去dockerhub查一下Node有什么版本标签)。建好之后图像大小如下图,瞬间从 1.06G 降到 238M,可以说效果显著。
也可以用其他基本的小镜子,比如mhart/alpine-node。这个可以小一点。将其更改为FROM mhart/alpine-node:14.17.3,然后重试。可以看到小了5M。虽然不多,但是秉承了你能挤一点点,积少成多,挤到极致的“老板原则”。
方案二:使用纯净 Alpine 镜像手动装 Node
既然Alpine是最小的Linux,那我们就试着用纯Alpine镜像一下,自己安装Node。
来自阿尔卑斯山:最新
#使用apk命令安装nodejs和yarn。如果从npm开始,就不需要装纱了。
运行apk add-no-cache-update nodejs=14 . 17 . 4-r0 yarn=1 . 22 . 10-r0
# .以下步骤保持不变。构建完成后,看下图。只有174M,小很多。
结论是,不要不厌其烦地追求极端,用方案2,从 1.06G 减少到 174M就好。
减少层数、不经常变动的层提到前面去
ENV指令可以一次设置多个环境变量。如果指令可以执行一次,就不需要执行两次。多一条指令就多加一层。
EXPOSE命令用于公开端口。其实不需要写这个命令,启动容器的时候就可以自己映射端口了。如果您编写此命令,因为端口不会频繁更改,所以推进此命令并编写此命令有两个好处:
帮助镜像用户了解这个镜像服务的守护端口,方便配置映射。
在运行时使用随机端口映射时,即docker run -P,EXPOSE的端口会自动随机映射。
至于写不写,看个人了。我个人一般不会写,因为我会在项目启动命令里指定项目端口,启动容器的时候映射就好了,这样就要维护一个地方。如果Dockerfile也写了,项目端口变了,这里就需要修改了,维护起来成本会多一点。当然,也有办法从配置文件中获取两边的端口变量,只需更改配置文件即可。
以下是重写的Dockerfile
来自阿尔卑斯山:最新
#使用apk命令安装nodejs和yarn。如果从npm开始,就不需要装纱了。
运行apk add-no-cache-update nodejs=14 . 17 . 4-r0 yarn=1 . 22 . 10-r0
#暴露端口
暴露4300
#设置环境变量
环境节点_环境=生产\
APP_PATH=/node/app
#设置工作目录
工作目录$APP_PATH
#将当前目录中的所有文件复制到镜像的工作目录中。dockerignore指定的文件将不会被复制。
收到。$APP_PATH
#安装依赖项
运行纱线
#开始命令
CMD纱开始在这一步进行了优化,无论是图像大小还是图像构建速度都没有明显的差别,因为改变的图层很少(无法体现),但是可以查看的图层更少。您可以尝试自己查看镜像层。
减少镜像层数是一个“好老板”的传统好习惯,不让员工浪费资源。
package.json 提前提高编译速度
从下图可以看出,我们每次构建最耗时的就是执行yarn命令安装依赖项的时候。大多数时候,我们只是改变代码,依赖关系保持不变。这时候如果能缓存这一步,在依赖项保持不变的情况下,就不需要重新安装依赖项,可以大大提高编译速度。
我们前面说过,建镜子的时候是一层一层建起来的,前一层是后一层的基础。既然是这样,我们就提前把package.json文件单独复制到镜像,然后在下一步安装依赖项。这一层的前一层是复制package.json文件,因为安装依赖命令不会改变,所以只要package.json文件不改变,yarn安装依赖就不会重新执行,它会重用之前安装的依赖。
已更改的Dockerfile文件
来自阿尔卑斯山:最新
#使用apk命令安装nodejs和yarn。如果从npm开始,就不需要装纱了。
运行apk add-no-cache-update nodejs=14 . 17 . 4-r0 yarn=1 . 22 . 10-r0
#暴露端口
暴露4300
#设置环境变量
环境节点_环境=生产\
APP_PATH=/node/app
#设置工作目录
工作目录$APP_PATH
#将package.json复制到工作目录。
复制package.json。
#安装依赖项
运行纱线
#将当前目录中的所有文件复制到镜像的工作目录中。dockerignore指定的文件将不会被复制。
收到。
#开始命令
CMD纱线开始构建如下图所示。编译时间从29.6秒到1.3秒在使用缓存的层前面会有一个缓存的字。仔细看下图就知道了。
充分利用docker缓存特性是优化构建速度的利器。
使用多阶段构建再次压榨镜像大小
多阶段施工。我不想在这里说。如果不知道,可以先搜索相关信息。
因为我们只需要生产依赖项和node最终可以运行的文件就可以运行节点程序,也就是说我们只需要package.js文件中的依赖项就可以运行项目,devDependencies只在编译阶段使用。例如,eslint等这些工具在项目运行时并不使用。比如我们的项目是用typescript写的,node不能直接运行ts文件,ts文件需要编译成js文件。要运行项目,我们只需要编译好的文件和依赖关系中的依赖关系,也就是说最终的镜像只需要我们需要的,其他的都可以删除。下面,我们使用Dockerfile的多级重写。
#构建基础映像
来自阿尔卑斯山:3.14作为基数
#设置环境变量
环境节点_环境=生产\
APP_PATH=/node/app
#设置工作目录
工作目录$APP_PATH
#安装节点和纱线
运行apk add-no-cache-update nodejs=14 . 17 . 4-r0 yarn=1 . 22 . 10-r0
#使用基本映像装载依赖阶段
安装时从基础开始
#将package.json复制到工作目录。
复制package.json。/
#安装依赖项
运行纱线
#最后一个阶段,也就是输出的镜像在这个阶段建立,前面的阶段都是为这个阶段做铺垫。
从基地
#将依赖阶段生成的node_modules文件夹复制到工作目录。
COPY-from=install $ APP _ PATH/node _ modules。/节点_模块
#复制当前目录中的所有文件(除了。dockerignore)到映像的工作目录中。
收到。
#开始
CMD yarn start细心的朋友会发现,我这里有指定的alpine版本,但是都是最新版本,因为刚刚我发现了一个需要注意的漏洞,就是我们在选择alpine版本的时候,最好不要选择最新版本,因为后面要安装的软件版本可能在alpine的最新版本中没有对应的软件版本号,所以会安装错误。我刚翻过身。点击查看alpine版本下的包信息。
构建完成后,让我们来看看图像大小。上次174M又降到了73.4M,这是终极压榨。镜像:“别管我,我真的没有”
讲解:
我将这个构建分为三个阶段:
第一阶段:建立基本形象
安装、编译、运行等阶段。就是所有阶段共享的东西都密封在第一个阶段的一个基础镜像里,供其他阶段使用,比如设置环境变量,设置工作目录,安装nodejs,yarn等。
第二阶段:安装依赖阶段。
在此阶段,安装依赖项。如果项目需要编译,可以在这个阶段安装依赖项并进行编译。
这里说的是下载依赖的小细节,即执行yarn - production并添加一个production参数或将环境变量NODE_ENV设置为production。yarn不会安装devDependencies中列出的任何软件包。我查一下官方文档,因为我设置了环境变量,所以没有添加这个参数。
第三阶段:最后使用图像。
复制第二阶段安装好的依赖文件夹,然后将代码文件复制到工作目录,执行启动命令。第二阶段我们不需要一些额外的垃圾,所以只复制我们需要的,这样就大大缩小了图像的大小。
如果项目需要编译,复制编译后的文件夹,而不是复制预编译的代码,可以用编译后的代码和依赖项运行项目。
多阶段构建,最终生成的图像只能是最后一个阶段的结果,但是把前一个阶段的文件复制到后一个阶段才是多阶段构建的最大意义。
最终优化结果:
大小从 1.06G 到 73.4M
构建速度从 29.6 秒到 1.3 秒(比较第二次构建的速度)
至此,挤压镜面的手段就完成了。如果你们老板还有压榨手段可以分享。
镜像内心独白:“你有礼貌吗?也来”
github 的 actions 构建镜像问题
GitHub提供的动作每次都是干净的实例。什么叫每次执行都是干净的机器,会导致出问题导致docker无法使用缓存?有解决办法吗?我想到两个解决方案:
docker官方提供的动作缓存方案
我使用Github缓存方案。
运行计算机的自托管操作
相当于gitlab的runner。如果提供自己的转轮,就不会每次都是干净的机器。详情见行动正式文件。
参考资料:
使用Docker层缓存在GitHub上构建镜像操作
最后
项目仓库地址https://github.com/iamobj/wechat-bot
文章如有错误,请指正,以免误导孩子。
有关编程的更多信息,请访问:编程入门!以上就是如何教你Node.js项目中如何优化docker镜像的细节。更多请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。