ホームページ  >  記事  >  ウェブフロントエンド  >  Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

青灯夜游
青灯夜游転載
2021-08-03 10:14:032600ブラウズ

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

この記事では、Node プログラムを使用して、Docker イメージを最適化する方法 (最適化のアイデアはプログラムに関係なく普遍的です) を示し、主に次の問題を解決します。過剰なイメージ サイズ、CI/CD イメージのビルド速度に関して、この記事では Dockerfile を段階的に最適化する方法を説明します。絶対に役立つ情報です。最初に「いいね」を押してから読むことをお勧めします。読んだ後は、役に立たない、そしてそれとは異なります。

最適化の結果は次のとおりです:

  • サイズの範囲は 1.06G ~ 73.4M

  • ビルド速度が 29.6 秒から 1.3 秒に短縮されました (2 番目のビルドの速度と比較)

  • ##[推奨調査: "
nodejsチュートリアル

》]

ノードプロジェクト

私は単に自分用に

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

ビルド後、以下に示すように、単純な Node プログラム イメージは実際には次のようになります。

1G

次に、このサイズを徐々に最適化して縮小していきます

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

最適化の序文

最適化する前に、理解する必要がある事項がいくつかあります。問題を解決するための最初のステップは、問題の原因を見つけることです。

    Dockerfile ファイルには命令が 1 つずつ含まれています。各命令はレイヤーを構築します。各命令の内容は、この層がどのように構築されるかを説明するものです。
  • Docker イメージは単なるファイルではなく、多数のファイルで構成されています。最も重要なファイルは
  • ですレイヤー

    (レイヤー)

      画像が構築されると、レイヤーごとに構築されます。前のレイヤーは次のレイヤーの基礎となります。
    • 各レイヤーが構築された後は変更はありません。後者のレイヤーでの変更は、そのレイヤーでのみ発生します。たとえば、前のレベルでファイルを削除する操作では、実際には前のレベルのファイルが削除されず、現在のレベルでファイルに削除済みのマークが付けられるだけです。最後のコンテナが実行されると、このファイルは表示されませんが、実際にはファイルは常に画像に従います。

    • #画像レイヤーはキャッシュされ、再利用されます (これも第 2 章からのものです) 1 2 回目にイメージのビルドを開始するときに速度が速くなるのは、イメージのビルド速度の最適化の原理もキャッシュの原理に基づいているためです)
    • When Dockerfileの命令が変更されたり、操作するファイルが変更されたり、イメージビルド時に指定した変数が異なる場合、対応するイメージレイヤーキャッシュが無効になります
    • docker buildのキャッシュ機構、その方法docker はファイルの変更を認識しますか?

      Docker が採用する戦略は、Dockerfile の内容 (ファイルの i ノード情報の一部を含む) を取得し、一意のハッシュ値を計算することです。ハッシュ値が変わらない場合は、ファイルの内容は変更されていないと見なされます。変更にはキャッシュ メカニズムが使用される場合もあり、その逆も同様です。

      #あるレイヤーの画像キャッシュが無効になると、それ以降の画像レイヤーのキャッシュも無効になります
    • 各レイヤーのイメージはファイルの変更のみを記録します。コンテナーが起動すると、Docker はイメージの各レイヤーを計算し、最終的にファイル システムを生成します。
    • これを知ったとき、Android などのオペレーティング システムが使用していることに突然気づきました。 、ios、win、Mac などは、実際にはファイル システムです。ソフトウェア インターフェイスの対話などは、実際にはファイルの読み取りと書き込みを行っています。Web ページにポップアップ ボックスを作成し、DOM を操作するとき、私たちはローカル ファイルの読み取りと書き込み、またはメモリ内のデータの読み取りと書き込み。個人的な意見の一部が正しいかどうかはわかりません。私はフロントエンド プログラマーで、専門的な経歴はありません。

      参考:
    • docker イメージの階層化の原則

    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,可以说是效果显著

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

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

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

方案二:使用纯净 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 了,又小了不少

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

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

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

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

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

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

    • 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射

    • 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口

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

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

下面是改写后的 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

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

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

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

package.json 提前提高编译速度

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

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

前面我们说了镜像构建时,是一层层构建,前一层是后一层的基础,既然是这样的话,我们就把 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 字眼,仔细看下图可以看到

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

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

1Node.js プロジェクトの 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 版本下的包信息

Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

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

1Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

讲解:

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

  • 第一阶段:构建基础镜像

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

  • 第二阶段:装依赖阶段

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

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

  • 第三阶段:最终使用镜像

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

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

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

最终优化成果:

  • 大小从 1.06G 到 73.4M

  • 构建速度从 29.6 秒到 1.3 秒(对比的是第二次构建的速度)

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

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

github 的 actions 构建镜像问题

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

参考資料:

最後に

プロジェクト ウェアハウス アドレス https://github.com/iamobj/wechat-bot

誤解を招くことを避けるために、記事内の間違いを修正してください。その他

1Node.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。

元のアドレス: https://juejin.cn/post/6991689670027542564

著者: iamc

# #その他のプログラミング 関連知識については、

プログラミング入門をご覧ください。 !

以上がNode.js プロジェクトの Docker イメージを最適化する方法を段階的に説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事は掘金--iamcで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。