实测有效!手把手带你将 Docker Image 体积减少 90%

Docker Image 体积越大,那部署要花的时间就越长;假如每个版本都有好几 GB 也会拖慢服务打包编译的速度;因此笔者开始动手实践,想看看到底能将 Docker Image 的体积缩小多少!

ㄧ、先初始化一个简单的 Node.js 项目

bash 复制代码
# 创建文件夹
mkdir docker-test
cd docker-test

# 初始化应用
npm init

# 安装 express
npm install express --save

初始化后的 package.json 大概会长这样(scripts 的 start 笔者有微调):

json 复制代码
{
  "name": "docker-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.1"
  }
}

接著我们新增一个 index.js 的文件,文件内容如下:

javascript 复制代码
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

为了实测不同情况下打包的影响,我们使用 npm 安装ESlint 工具,并将其保存为开发依赖项:

npm install eslint -save-dev

二、编写 Dockefile,了解优化前体积有多大

这边我们就先用最简单的方式写 Dockerfile:

bash 复制代码
FROM node
# 工作目录
WORKDIR /usr/src/app
# 拷贝所需文件
COPY package.json index.js ./
# 安装依赖
RUN npm install
# 提供服务的接口
EXPOSE 3000
CMD ["npm", "start"]

接下来输入 docker build -t docker-test . 就可以创建 Docker Image(docker-test 是名称)。

然后输入 docker images ,确认刚刚创建的 Docker Image 是否存在;从下图我们可以看到 优化前的 Image 大小高达 1.01GB

三、使用 Node.js 的 Alpine 版本

Node.js Alpine 版本的 Image 体积会远小于完整的 Node.js Image,现在我们修改一下 Dockerfile:

bash 复制代码
# 使用 Alpine 版本 
FROM node:alpine
WORKDIR /usr/src/app
COPY package.json index.js ./
RUN npm install
EXPOSE 3000
CMD ["npm", "start"]

输入 docker build -t docker-test-alpine . 来创建 Image,完成后输入 docker images 看看 build 出来的 Image 体积是否改变。

从上图我们可以看到,Alpine 的版本让它的体积从 1.01GB 下降到了 189MB,整整少了 812MB!

四、正式环境下,不需要安装 devDependencies 的依赖

通常一个项目会安装一些开发环境下的依赖,但这些依赖只需要在开发环境中辅助使用,在正式环境下并没有安装的必要。

我们调整一下 Dockerfile 安装依赖的指令:

sql 复制代码
FROM node:alpine

WORKDIR /usr/src/app

COPY package.json index.js ./

# 只安装正式环境的依赖
RUN npm install --production

EXPOSE 3000

CMD ["npm", "start"]

在上图我们可以看到体积又变小了,果然还有优化的空间(少了 12MB)。

五、如果我们只使用最基础的 Alpine,然后 Node.js 自己安装呢?

刚刚我们使用的是 Node.js 的 Alpine 版本,如果更极端一点,只使用最基础的 Alpine,然后自己手动安装 Node.js 会有什么样的结果呢?

sql 复制代码
# 使用最基础的 Alpine
FROM alpine:latest

# 自己安装 Node.js & npm
RUN apk add --no-cache --update nodejs npm

WORKDIR /usr/src/app

COPY package.json index.js ./

RUN npm install --production

EXPOSE 3000

CMD ["npm", "start"]

没想到这个操作把整包 Image 的体积压的更低了(从 177MB 降到 64.7MB)。

六、采用多阶段构建

Docker Image 你可以理解为很多层互相叠加在一起,从Docker 1.10开始,COPY、ADD 和 RUN 语句会向镜像中添加新层;而在 Docker 的世界中可以允许有多个「FROM」,但只会取用最后一个「FROM」所创建的 Image。

Docker的层用于保存镜像的上一版本和当前版本之间的差异。就像Git的提交一样,如果你与其他存储库或镜像共享它们,就会很方便。但额外的层并不是没有代价的,层仍然会占用空间,你拥有的层越多,最终的镜像就越大。

在了解上面的概念后,我们就可以把「安装编译」的步骤放在第一个「FROM」里面执行,然后第二个 FROM 就只是单纯地把第一层的结果搬过去即可,那么 Dockerfile 实现会长这样:

sql 复制代码
FROM alpine:latest AS builder
RUN apk add --no-cache --update nodejs npm
WORKDIR /usr/src/app
COPY package.json index.js ./
# 先完成安裝编译
RUN npm install --production

FROM alpine:latest
RUN apk add --no-cache --update nodejs npm
WORKDIR /usr/src/app
# 把编译好的全部移动过来
COPY --from=builder /usr/src/app .
EXPOSE 3000
CMD ["npm", "start"]

经过这么多的努力后,我们的 Docker Image 也顺利从 1.01GB 下降到 58.9MB, 减少了超过 94% 的体积 ,并且可以明显感受到 build Image 的速度上升不少。

七、使用 Distroless 让正式环境更加安全

尽管上面我们已经使用 Alpine 让 Docker Image 变得这么小,但在文章的最后我想再提供读者另一个选择, 那就是「Distroless」!

先让我们用它来 Build 一个 Docker Image。

css 复制代码
FROM node AS builder
WORKDIR /usr/src/app
COPY package.json index.js ./
RUN npm install --production
# 改成用 Distroless
FROM gcr.io/distroless/nodejs
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app .
EXPOSE 3000
CMD ["index.js"]

如果单纯从结果来看,它在体积上(162MB)并没有什么优势,但如果你尝试用 Shell 打开它,会发现 Shell 根本不存在!换而言之,它的安全性相对更高,可以考虑使用它来做正式环境的部署。

以上就是笔者优化 Docker Image 体积的步骤啦!如果文中有表达不清晰、错误的部分再烦请告知。

相关推荐
爱上语文15 分钟前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people19 分钟前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
北岛寒沫3 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
everyStudy3 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
无心使然云中漫步5 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
m0_741768855 小时前
使用docker的小例子
运维·docker·容器
Bug缔造者5 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_5 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
罗政6 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
麒麟而非淇淋6 小时前
AJAX 入门 day1
前端·javascript·ajax