实测有效!手把手带你将 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 体积的步骤啦!如果文中有表达不清晰、错误的部分再烦请告知。

相关推荐
南猿北者11 分钟前
docker Network(网络)
网络·docker·容器
2401_8576100341 分钟前
Spring Boot框架:电商系统的技术优势
java·spring boot·后端
Dread_lxy1 小时前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
sam-1231 小时前
k8s上部署redis高可用集群
redis·docker·k8s
奔跑草-2 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与2 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
Fanstay9852 小时前
在Linux中使用Nginx和Docker进行项目部署
linux·nginx·docker
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR2 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式
帅帅哥的兜兜2 小时前
CSS:导航栏三角箭头
javascript·css3