网易首页 > 网易号 > 正文 申请入驻

如何优化 Node 项目的 Docker 镜像

0
分享至

作者:imac来源:掘金

本文将以 Node 程序展示如何优化 Docker 镜像(优化思想是通用的,不分程序),主要解决镜像大小过大、CI/CD 构建镜像速度,本文演示如何一步步优化 Dockerfile 文件,优化的结果如下:

  • 大小从 1.06G 到 73.4M
  • 构建速度从 29.6 秒到 1.3 秒(对比的是第二次构建的速度)

Node 项目

简单写了一个自己用的 wechat-bot,接下来就以这个项目演示怎么去优化 Docker 镜像。

以下是我没有仔细研究 Docker 刚开始写的 Dockerfile 文件。

FROM node:14.17.3
# 设置环境变量
ENV NODE_ENV=production
ENV APP_PATH=/node/app
# 设置工作目录
WORKDIR $APP_PATH
# 把当前目录下的所有文件拷贝到镜像的工作目录下 ·dockerignore 指定的文件不会拷贝
COPY · $APP_PATH
# 安装依赖
RUN yarn
# 暴露端口
EXPOSE 4300
CMD yarn start

build 之后,如下图,我这个简单的 Node 程序镜像竟然有 1G 多,接下来我们将逐步去优化减少这个大小。

优化前言

在优化之前,有些东西我们必须了解,解决问题的第一步就是先找出导致问题的原因。

Dockerfile 文件,其内包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层如何构建。

Docker 镜像并非只是一个文件,而是由一堆文件组成,最主要的文件是层(Layers)

  • 镜像构建时,会一层层构建,前一层是后一层的基础每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。
  • 镜像层将会被缓存和复用(这也是从第二次开始构建镜像时,速度会快的原因,优化镜像构建速度的原理也是利用缓存原理来做)
  • 当 Dockerfile 的指令修改了,操作的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效
  • docker build 的缓存机制,Docker 是怎么知道文件变化的呢?
  • Docker 采取的策略是:获取 Dockerfile 下内容(包括文件的部分 inode 信息),计算出一个唯一的 hash 值,若 hash 值未发生变化,则可以认为文件内容没有发生变化,可以使用缓存机制,反之亦然。
  • 某一层的镜像缓存失效之后,它之后的镜像层缓存都会失效
  • 镜像的每一层只记录文件变更,在容器启动时,Docker 会将镜像的各个层进行计算,最后生成一个文件系统当我知道这点时,我恍然大悟,我们使用的操作系统,比如安卓、iOS、Windows、macOS 等,其实就是一个文件系统,我们的软件界面交互等,其实就是在读写文件,我们网页写个弹框,操作 dom,就是在读写本地文件或者是读写内存里的数据,个人的一些见解不知道对不对,本人非科班出身的前端 coder。

参考资料:https://www.cnblogs.com/handwr ... .html

ok,我们已经知道镜像是由多层文件系统组成,想要优化它的大小,就需要去减少层数、每一层尽量只包含该层需要的东西,任何额外的东西应该在该层构建结束前清理掉,下面开始正文。

优化 Dockerfile

优化第一层 FROM node:14.17.3

方案一:使用 Node 的 Alpine 版本

这也是绝大多数人知道的优化镜像手段,Alpine 是一个很小的 Linux 发行版,只要选择 Node 的 Alpine 版本,就会有很大改进,我们把这一句改成指令改成 FROM node:14.17.4-alpine(可以去 Dockerhub 查看 Node 有哪些版本标签),build 后镜像大小如下图,瞬间从 1.06G 降到 238M,可以说是效果显著。

还可以使用其它的基础小镜像,比如 mhart/alpine-node,这个还能再小,改成 FROM mhart/alpine-node:14.17.3 再试试,可以看到又小了 5M
,虽然不多,但是秉着能压榨一点是一点的“老板原则”,积少成多,极致压榨。

方案二:使用纯净 Alpine 镜像手动装 Node

既然 Alpine 是最小的 Linux,那我们试下用纯净的 Alpine 镜像,自己再装 Node 试试。

FROM alpine:latest
# 使用 apk 命令安装 nodejs 和 yarn,如果使用 npm 启动,就不需要装 yarn
RUN apk add --no-cache --update nodejs=14·17·4-r0 yarn=1·22·10-r0
# ... 后面的步骤不变

build 后看下图,只有 174M 了,又小了不少。

结论就是不嫌麻烦追求极致就用方案二,从 1.06G 减少到 174M。

减少层数、不经常变动的层提到前面去

ENV 指令是可以一次性设置多个环境变量,能一次指令执行完,就不用两次,多一个指令就多一层

EXPOSE 指令是暴露端口,其实也可以不用写这个指令,在启动容器的时候自己映射端口,如果写了这个指令的话,因为端口不经常变,所以把这个指令提前,写上这个指令有两个好处:

  • 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射
  • 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口

至于写还是不写,看个人吧,我个人一般不写,因为我在项目启动命令会指定项目端口,启动容器的时候映射出来就好,这样我就要维护一个地方,Dockerfile 也写了的话,项目端口变了,这里也要修改,多了点维护成本,当然也有办法让两边端口变量取自配置文件,只要改配置文件即可。

下面是改写后的 Dockerfile。

FROM alpine:latest
# 使用 apk 命令安装 nodejs 和 yarn,如果使用 npm 启动,就不需要装 yarn
RUN apk add --no-cache --update nodejs=14·17·4-r0 yarn=1·22·10-r0
# 暴露端口
EXPOSE 4300
# 设置环境变量
ENV NODE_ENV=production \
APP_PATH=/node/app
# 设置工作目录
WORKDIR $APP_PATH
# 把当前目录下的所有文件拷贝到镜像的工作目录下 ·dockerignore 指定的文件不会拷贝
COPY · $APP_PATH
# 安装依赖
RUN yarn
# 启动命令
CMD yarn start

这一步的优化,无论从镜像大小还是构建镜像速度都看不到明显的差别,因为改动的层内容少(体现不出来),但是可以查看到镜像的层是变少了的,可以自行试试查看镜像的层试试。

减少镜像层数是“好老板”的传统优良习惯,不让“员工”浪费资源。

package.json 提前提高编译速度

从下图可以看到每次我们 build 的时候最耗时的就是在执行 yarn 命令装依赖的时候,大部分时候我们只是改代码,依赖不变,这时候如果可以让这一步缓存起来,依赖没有变化的时候,就不需要重新装依赖,就可以大大改进编译速度。

前面我们说了镜像构建时,是一层层构建,前一层是后一层的基础,既然是这样的话,我们就把 package.json 文件单独提前拷贝到镜像,然后下一步装依赖,执行命令装依赖这层的前一层是拷贝 package.json 文件,因为安装依赖命令不会变化,所以只要 package.json 文件没变化,就不会重新执行 yarn 安装依赖,它会复用之前安装好的依赖,原理讲清楚了,下面我们看效果。

改变后的 Dockerfile 文件:

FROM alpine:latest
# 使用 apk 命令安装 nodejs 和 yarn,如果使用 npm 启动,就不需要装 yarn
RUN apk add --no-cache --update nodejs=14·17·4-r0 yarn=1·22·10-r0
# 暴露端口
EXPOSE 4300
# 设置环境变量
ENV NODE_ENV=production \
APP_PATH=/node/app
# 设置工作目录
WORKDIR $APP_PATH
# 拷贝 package·json 到工作跟目录下
COPY package·json ·
# 安装依赖
RUN yarn
# 把当前目录下的所有文件拷贝到镜像的工作目录下 ·dockerignore 指定的文件不会拷贝
COPY · ·
# 启动命令
CMD yarn start

build 看下图,编译时间从 29.6s 到 1.3s,使用了缓存的层前面会有个 CACHED 字眼,仔细看下图可以看到。

充分利用 Docker 缓存特性是优化构建速度的利器。

使用多阶段构建再次压榨镜像大小

多阶段构建这里不多说了,不了解的可以先搜索相关资料了解。

因为我们运行 Node 程序时只需要生产的依赖和最终 Node 可以运行的文件,就是说我们运行项目只需要 package.js 文件里 dependencies 里的依赖,devDependencies 依赖只是编译阶段用的,比如 eslint 等这些工具在项目运行时是用不到的,再比如我们项目是用 typescript 写的,Node 是不能直接运行 ts 文件,ts 文件需要编译成 js 文件,运行项目我们只需要编译后的文件和 dependencies 里的依赖就可以运行,也就是说最终镜像只需要我们需要的东西,任何其他东西都可以删掉,下面我们使用多阶段改写 Dockerfile。

# 构建基础镜像
FROM alpine:3·14 AS base
# 设置环境变量
ENV NODE_ENV=production \
APP_PATH=/node/app
# 设置工作目录
WORKDIR $APP_PATH
# 安装 nodejs 和 yarn
RUN apk add --no-cache --update nodejs=14.17.4-r0 yarn=1.22.10-r0
# 使用基础镜像依赖阶段
FROM base AS install
# 拷贝 package.json 到工作目录下
COPY package.json ./
# 安装依赖
RUN yarn
# 最终阶段,也就是输出的镜像是这个阶段构建的,前面的阶段都是为这个阶段做铺垫
FROM base
# 拷贝 装依赖阶段 生成的 node_modules 文件夹到工作目录下
COPY --from=install $APP_PATH/node_modules ·/node_modules
# 将当前目录下的所有文件(除了·dockerignore排除的路径),都拷贝进入镜像的工作目录下
COPY . .
# 启动
CMD yarn start

细心的朋友会发现我这里有指定 Alpine 版本,而上面都是用的 latest 版本,因为就在刚刚发现有个坑需要注意下,就是我们选择 Alpine 版本的时候,最好不要选择 latest 版本,因为后面要装的软件版本可能会在 Alpine 的 latest 版本没有对应软件的版本号,就会安装错误,我刚刚就翻车了,点击查看 Alpine 版本下的包信息。

build 后,我们看看镜像大小,上次的是 174M 再次降到 73.4M,极致压榨。镜像:“放过我把,我真的没有了”。

讲解:

我把这个构建分成了三个阶段:

第一阶段:构建基础镜像

安装依赖、编译、运行等等阶段,就是所有阶段共用的东西都在第一阶段封到一个基础镜像里供其它阶段使用,比如设置环境变量、设置工作目录、安装 nodejs、yarn 等等。

第二阶段:装依赖阶段

在这个阶段,装依赖,如果项目需要编译,可以在这个阶段装依赖编译好。

这里在说下装依赖的小细节,就是执行 yarn --production 加个 production 参数或者环境变量 NODE_ENV 为 production,yarn 将不会安装 devDependencies 中列出的任何软件包,点我查看官方文档说明,因为我设置了环境变量所以就没加这个参数

第三阶段:最终使用镜像

拷贝第二阶段安装的好的依赖文件夹,然后在拷贝代码文件到工作目录,执行启动命令,第二阶段安装依赖多出的一些垃圾我们不需要,我们就只拷贝我们要用的东西,大大减少镜像的大小。

如果项目需要编译,在拷贝编译后的文件夹,不需要拷贝编译前的代码,有编译后的代码和依赖就可以跑起项目。

多阶段构建,最后生成的镜像只能是最后一个阶段的结果,但是,能够将前置阶段中的文件拷贝到后边的阶段中,这就是多阶段构建的最大意义。

最终优化成果:

  • 大小从 1.06G 到 73.4M
  • 构建速度从 29.6 秒到 1.3 秒(对比的是第二次构建的速度)

至此,压榨镜像手段就完了,如果各位老板还有压榨手段可以分享分享。

镜像内心独白:“你礼貌吗?还来”

GitHub 的 actions 构建镜像问题

GitHub 提供的 actions,每次都是一个干净的实例,什么意思,就是每次执行,都是干净的机器,这会导致一个问题,会导致 Docker 没法使用缓存,那有没有解决办法呢,我想到了三种解决办法:

1、 Docker 官方提供的 action 缓存方案

我用的是 Github cache 方案。

2、自托管 actions 运行机器

相当于 GitLab 的 runner 一样,自己提供运行器,自己提供的就不会每次都是干净的机器,详情看 actions 官方文档。

3、先构建一个已经安装好依赖包的镜像,然后基于此镜像再次构建,相当于多阶段构建,把前两个阶段构建的镜像产物推送到镜像仓库,再以这个镜像为基础去构建后续部分。借助镜像仓库存储基础镜像从而达到缓存的效果。

# 以这个镜像为基础去构建,这个镜像是已经装好项目依赖的镜像并推送到镜像仓库里,这里从镜像仓库拉下来
FROM project-base-image:latest
COPY 。 。
CMD yarn start

参考资料:https://evilmartians.com/chron ... ching

最后

项目仓库地址:https://github.com/iamobj/wechat-bot

文章有错误的地方欢迎指正,避免误人子弟。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
3.3亿游艇到30亿涂鸦,扎克伯格壕得如此高调,挑战了我的想象力

3.3亿游艇到30亿涂鸦,扎克伯格壕得如此高调,挑战了我的想象力

电影烂番茄
2026-02-17 22:26:59
别光羡慕梅婷的生活,真给你一套四千万的别墅,

别光羡慕梅婷的生活,真给你一套四千万的别墅,

小光侃娱乐
2026-02-24 09:05:06
职业装原来可以这么穿,既亮眼又有正式感

职业装原来可以这么穿,既亮眼又有正式感

美女穿搭分享
2026-02-15 18:53:43
《乘风2026》最终阵容:29位姐姐名单出炉,没有大咖支撑

《乘风2026》最终阵容:29位姐姐名单出炉,没有大咖支撑

音乐钢琴娱乐咖
2026-02-22 14:07:52
湖南省“臭名远播”的4所大学,地处优势地段,毕业证毫无含金量

湖南省“臭名远播”的4所大学,地处优势地段,毕业证毫无含金量

朗威谈星座
2026-02-24 00:42:47
“刚入职就吃到了前同事留下的瓜......”哈哈哈哈哈哈简直全员职场恶人啊!!!

“刚入职就吃到了前同事留下的瓜......”哈哈哈哈哈哈简直全员职场恶人啊!!!

不二表姐
2026-02-24 00:07:02
2026年一开年,不少网约车司机都深有体会:这行的跑法,彻底变了

2026年一开年,不少网约车司机都深有体会:这行的跑法,彻底变了

呼呼历史论
2026-02-24 11:41:02
日本老人容易长寿的秘诀就是:一个人独居,什么事都别放心上

日本老人容易长寿的秘诀就是:一个人独居,什么事都别放心上

千秋历史
2026-02-20 20:36:09
颐和园摸完猫手臭了,反复追问其他人,发现别人摸都不臭!

颐和园摸完猫手臭了,反复追问其他人,发现别人摸都不臭!

Magic宠物社
2026-02-23 18:35:10
突发!NBA考虑取消伦纳德合同

突发!NBA考虑取消伦纳德合同

鬼魅突破上篮
2026-02-24 09:57:42
“后坐力”太大!闫学晶又被爆出猛料,她和儿子已经一个也跑不了

“后坐力”太大!闫学晶又被爆出猛料,她和儿子已经一个也跑不了

来科点谱
2026-01-22 08:53:11
立陶宛总统:如果中国还想跟立陶宛和好,必须对我们展现充分诚意

立陶宛总统:如果中国还想跟立陶宛和好,必须对我们展现充分诚意

嫹笔牂牂
2026-02-23 22:04:20
掀翻东部第1!马刺夺9连胜抢西部第1:雷霆告急?

掀翻东部第1!马刺夺9连胜抢西部第1:雷霆告急?

体坛周报
2026-02-24 12:20:14
雷锋因公牺牲后,宋任穷质问:雷锋为何没被提干部?吕清一语道破

雷锋因公牺牲后,宋任穷质问:雷锋为何没被提干部?吕清一语道破

墨兰史书
2026-02-09 23:10:31
台大校长傅斯年:一生追求民主与真理,赴台湾后家人在大陆遭清算

台大校长傅斯年:一生追求民主与真理,赴台湾后家人在大陆遭清算

谈史论天地
2026-02-23 07:12:34
大年初六,和几个大厂(字节、阿里)朋友聚会。突然不聊房子了

大年初六,和几个大厂(字节、阿里)朋友聚会。突然不聊房子了

蚂蚁大喇叭
2026-02-23 17:08:47
不管你的孩子优不优秀,千万不要“逢人就讲”,父母别不当回事

不管你的孩子优不优秀,千万不要“逢人就讲”,父母别不当回事

新东方家庭教育
2026-01-13 09:37:57
不忍心告诉你:人旺财不旺,财旺人不旺,人财两旺寿不长

不忍心告诉你:人旺财不旺,财旺人不旺,人财两旺寿不长

舒山有鹿
2026-02-16 12:23:11
大胜爵士却现3槽点!申京感观极差,电风扇仍低迷,伊森越打越差

大胜爵士却现3槽点!申京感观极差,电风扇仍低迷,伊森越打越差

篮球资讯达人
2026-02-24 13:17:26
中国历史10个离奇巧合,看完头皮发麻:历史难道真有剧本?

中国历史10个离奇巧合,看完头皮发麻:历史难道真有剧本?

长风文史
2026-02-23 21:29:26
2026-02-24 14:04:49
Nodejs开发
Nodejs开发
分享只有程序员懂的干货
648文章数 823关注度
往期回顾 全部

科技要闻

AI颠覆发展最新牺牲品!IBM跳水重挫超13%

头条要闻

牛弹琴:白宫突然发了张图 伤害性不大侮辱性极强

头条要闻

牛弹琴:白宫突然发了张图 伤害性不大侮辱性极强

体育要闻

苏翊鸣总结米兰征程:我仍是那个热爱单板滑雪的少年

娱乐要闻

杨洋传遇上缅北剧组 开机就离开剧组?

财经要闻

县城消费「限时繁荣」了十天

汽车要闻

入门即满配 威兰达AIR版上市 13.78万元起

态度原创

时尚
房产
数码
教育
本地

今年春天一定要拥有的针织,这样穿减龄又好看!

房产要闻

窗前即地标!独占三亚湾C位 自贸港总裁行宫亮相

数码要闻

AMD没做第三方做到了!OptiScaler抢先为Vulkan游戏开启FSR 4

教育要闻

多地官宣中小学春秋假,有人欢喜有人忧!是福利还是新的负担?

本地新闻

春花齐放2026:《骏马奔腾迎新岁》

无障碍浏览 进入关怀版