이 기사에서는 Node 프로그램을 사용하여 Docker 이미지를 최적화하는 방법을 보여줍니다(최적화 아이디어는 프로그램에 관계없이 보편적임). 주로 과도한 이미지 크기 및 CI/CD 빌드 이미지 속도 문제를 해결합니다. 이 글은 Dockerfile을 단계별로 최적화하는 방법, 절대적인 유용한 정보를 보여줍니다. 먼저 좋아요를 누른 후 읽는 것이 좋습니다. 읽은 후에는 좋아요를 취소하는 것이 불가능하지 않으며 유용한 정보가 아닙니다.
최적화 결과는 다음과 같습니다.
크기는 1.06G~73.4M
빌드 속도는 29.6초~1.3초(두 번째 빌드 속도와 비교) )
[추천 학습: "nodejs tutorial"]
나는 단순히 나만의 용도로 wechat-bot을 작성했습니다. 다음으로 이 프로젝트를 사용하여 최적화 방법을 보여 드리겠습니다. 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(Layers)
이미지를 생성할 때 레이어별로 생성되며 이전 레이어가 다음 레이어의 기초가 됩니다
각 레이어가 생성된 후에는 변경 사항이 없습니다. , 후자 레이어의 모든 변경 사항은 자체 레이어에서만 발생합니다. 예를 들어, 이전 레벨의 파일을 삭제하는 작업은 실제로 이전 레벨의 파일을 삭제하는 것이 아니라, 현재 레벨의 파일을 삭제된 것으로 표시만 할 뿐입니다. 최종 컨테이너가 실행되면 이 파일은 보이지 않지만 실제로 파일은 항상 이미지를 따라갑니다. 이미지 레이어는 캐시되어 재사용됩니다(이것은 두 번째부터 이미지를 빌드할 때의 속도이기도 합니다). 더 빠른 이유는 이미지 빌드 속도를 최적화하는 원리도 캐싱 원리에 기반을 두고 있기 때문입니다.)
Dockerfile의 명령어를 수정하면 운영 파일이 변경되거나, 이미지 빌드 시 지정한 변수가 다르면 해당 이미지 레이어 캐시가 무효화됩니다
Docker가 채택한 전략은 Dockerfile의 내용(파일의 inode 정보 일부 포함)을 얻고 고유한 해시 값을 계산하는 것입니다. 해시 값이 변경되지 않으면 파일이 있다고 간주할 수 있습니다. 콘텐츠는 변경되지 않았으며 캐싱 메커니즘을 사용할 수 있으며 그 반대의 경우도 마찬가지입니다.
특정 레이어의 이미지 캐시가 무효화되면 그 이후의 이미지 레이어 캐시도 무효화됩니다
이미지의 각 레이어는 컨테이너가 시작될 때 파일 변경 사항만 기록합니다. 드디어 파일 시스템이 생성되었습니다
참고 자료 :
Docker 이미지 레이어링 원리좋아, 우리는 이미 이미지가 구성되어 있다는 것을 알고 있습니다. 다층 파일 시스템입니다. 크기를 최적화하려면 레이어 수를 줄여야 하며, 각 레이어에는 해당 레이어에 필요한 것만 포함되어야 하며, 추가 항목은 모두 정리되어야 합니다. 레이어 구성, 아래 텍스트를 시작하세요
FROM node:14.17.3
这也是绝多数人知道的优化镜像手段,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 是最小的 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
这一步的优化,无论从镜像大小还是构建镜像速度都看不到明显的差别,因为改动的层内容少(体现不出来),但是可以查看到镜像的层是变少了的,可以自行试试查看镜像的层试试
减少镜像层数是“好老板”的传统优良习惯,不让“员工”浪费资源
从下图可以看到每次我们 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,每次都是一个干净的实例,什么意思,就是每次执行,都是干净的机器,这会导致一个问题,会导致 docker 没法使用缓存,那有没有解决办法呢,我想到了两种解决办法:
我用的是 Github cache 方案
자체 호스팅 액션 런닝머신
런너를 직접 제공하신다면, 제공하는 머신이 매번 클린머신이 되지는 않을 것입니다. 자세한 사항은 액션 공식 문서를 참고해주세요
참고 자료:
프로젝트 창고 주소 https://github.com/iamobj/wechat-bot
수정해 주세요. 사람들의 오해를 피하기 위해 기사에 오류가 있는 경우 어린이
원본 주소: https://juejin.cn/post/6991689670027542564
저자: iamc
더 많은 프로그래밍 관련 지식을 보려면 방문하세요. : 프로그래밍 입문! !
위 내용은 Node.js 프로젝트에서 Docker 이미지를 최적화하는 방법을 단계별로 알려드립니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!