首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何缩小您的docker 镜像体积

如何缩小您的docker 镜像体积

原创
作者头像
iginkgo18
修改2021-08-23 10:35:09
修改2021-08-23 10:35:09
2.7K00
代码可运行
举报
文章被收录于专栏:devops_k8sdevops_k8s
运行总次数:0
代码可运行

1.0 简介

写好node代码后,打包进docker发现镜像非常大,下面方法有助于构建一个一个体积小很多的镜像;

2.0 常规构建镜像

当 Git 存储库变大时,你可以选择将历史提交记录压缩为单个提交。

事实证明,在 Docker 中也可以使用多阶段构建达到类似的目的。

在这个示例中,你将构建一个 Node.js 容器。

让我们从 index.js 开始:

app.js

代码语言:javascript
代码运行次数:0
运行
复制
const express = require('express')
const app = express()

app.get('/', function(req, res){
  res.send('hello world')
})

app.listen(3000)

package.json

代码语言:javascript
代码运行次数:0
运行
复制
{
  "name": "docker-test",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "node app"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.4"
  },
  "devDependencies": {
    "eslint": "^5.16.0"
  }
}

你可以使用下面的 Dockerfile 来打包这个应用程序:

代码语言:javascript
代码运行次数:0
运行
复制
FROM node
COPY . /home/app
RUN cd /home/app && npm install
WORKDIR /home/app
CMD ['npm', 'start']

开始构建镜像

代码语言:javascript
代码运行次数:0
运行
复制
 docker build -t myapp .

查看镜像

代码语言:javascript
代码运行次数:0
运行
复制
[root@client-1 node]# docker images |grep myapp
myapp                                                      latest              5a9b3b2be4cd        7 seconds ago       935MB

然后用以下方法验证他是否可以正常运行:

代码语言:javascript
代码运行次数:0
运行
复制
docker run -p 3000:3000 -ti --rm --init myapp

3.0 优化docker生产环境镜像

3.1 用distroless去除容器不必要东西

“distroless”镜像只包含应用程序及其运行时依赖项,不包含程序包管理器、shell 以及在标准 Linux 发行版中可以找到的任何其他程序。

攻击者无法利用应用程序获得对容器的访问权限将无法像访问shell那样造成太多破坏,换句话说,更少的二进制文件意味着更小的体积和更高的安全性,不过这是以痛苦的调试为代价,比如: 进不去shell, ls,ps,ping等;

Example

代码语言:javascript
代码运行次数:0
运行
复制
FROM node:8 as build
WORKDIR /app
COPY package.json index.js ./
RUN npm install
FROM gcr.io/distroless/nodejs
COPY --from=build /app /
EXPOSE 3000
CMD ["index.js"]

3.2 使用Node.js Alpine镜像

大幅减小镜像体积的最简单和最快的方法是选择一个小得多的基本镜像。Alpine是一个很小的Linux发行版,可以完成这项工作。只要选择Node.js的Alpine版本,就会有很大的改进。

Alpine基础镜像是基于 muslc 的 C语言的一个替代标准库, 而大多数Linux发行版如Ubuntu, Debian和CentOS都是基于glibc的, 这两个库应该实现相同的内核接口;

目的不一样:

glibc更常见,速度更快;

muslc使用较少空间,并侧重于安全性;

换句话说,基于 Alpine 基础镜像构建容器可能会导致非预期的行为,因为标准 C 库是不一样的。

你可能会注意到差异,特别是当你处理预编译的二进制文件(如 Node.js C++ 扩展)时。

例如,PhantomJS 的预构建包就不能在 Alpine 上运行。

代码语言:javascript
代码运行次数:0
运行
复制
FROM node:alpine
COPY . /home/app
RUN cd /home/app && npm install
WORKDIR /home/app
CMD ['npm', 'start']

查看镜像

代码语言:javascript
代码运行次数:0
运行
复制
[root@client-1 node]# docker images |grep myapp
myapp                                                      latest              b395ac63c1f2        6 seconds ago       141MB

3.3 生产环境不打包开发的依赖包

但我们还能继续优化。我们正在安装所有依赖项,即使我们最终只需要生成环境下的依赖包。如果只打包生产环境的以来不会怎么样,继续改进一下。

代码语言:javascript
代码运行次数:0
运行
复制
FROM node:alpine
COPY . /home/app
RUN cd /home/app && npm install --production
WORKDIR /home/app
CMD ['npm', 'start']

查看镜像

代码语言:javascript
代码运行次数:0
运行
复制
[root@client-1 node]# docker images |grep myapp
myapp                                                      latest              ac66a7d72ef6        7 seconds ago       120MB

3.4 使用基础版本的Alpine镜像组合Nodejs

如果我们使用基础版本的 Alpine 镜像,然后自己安装Nodejs结果会怎么样呢?

代码语言:javascript
代码运行次数:0
运行
复制
  FROM alpine:latest
  RUN apk add --no-cache --update nodejs nodejs-npm
  COPY . /home/app
  RUN cd /home/app && npm install --production
  WORKDIR /home/app
  CMD ['npm', 'start']

查看镜像

3.5 多阶段构建

  • Docker镜像是分层的,Dockerfile中的每个指令都会创建一个新的镜像层,镜像层可以被复用和缓存。当Dockerfile的指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效,某一层的镜像缓存失效之后,它之后的镜像层缓存都会失效。
  • 因此我们还可以将RUN指令合并,但是需要记住的是,我们只能将变化频率一致的指令合并。
  • 我们应该把变化最少的部分放在Dockerfile的前面,这样可以充分利用镜像缓存。
  • 通过最小化镜像层的数量,我们可以得到更小的镜像。

上述示例中,源代码会经常变化,则每次构建镜像时都需要重新安装NPM模块,这显然不是我们希望看到的。因此我们可以先拷贝package.json,然后安装NPM模块,最后才拷贝其余的源代码。这样的话,即使源代码变化,也不需要重新安装NPM模块。

代码语言:javascript
代码运行次数:0
运行
复制
FROM alpine AS builder
WORKDIR /home/app
RUN apk add --no-cache --update nodejs nodejs-npm
COPY package.json package-lock.json ./
RUN npm install --production

FROM alpine
WORKDIR /home/app
RUN apk add --no-cache --update nodejs nodejs-npm
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY . .
CMD [ 'npm', 'start' ]

# 这个dockerfile构建有点慢

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

在上面的Dockerfile文件中,我们先 copy 了package.json,然后 npm install,在第二阶段构建时,我们直接 copy 了第一阶段已经下载好的node_moduls,在下一次 build 时,如果没有新增依赖,docker将使用缓存中的node_modules,这样就减少了部署的时间。

使用 docker inspect imageId命令 我们可以看到,虽然我们有多个指令,但是最终的镜像也只有5层,这就是层的共享机制。

使用多阶段构建可以充分利用Docker镜像的缓存,大大减少最终部署到生产环境的时间。

查看镜像

代码语言:javascript
代码运行次数:0
运行
复制
[root@client-1 Pressure]# docker images |grep myapp
myapp                                                      latest              4b8d4ce5dd1a        2 hours ago         71.2MB

3.6 编写Dockerfiel细节

1 . 编写.dockerignore文件

构建镜像时,docker需要先准备context,将所有需要的文件收集到进程中。默认的context包含 Dockerfile 目录中的所有文件,但是实际上,我们并不需要.git 目录,node_modules 目录等内容。 .dockerignore 的作用和语法类似于 .gitignore,可以忽略一些不需要的文件,这样可以有效加快镜像构建时间,同时减少 Docker 镜像的大小。示例如下:

代码语言:javascript
代码运行次数:0
运行
复制
.git/
node_modules/

2 . 容器只运行单个应用;

3 . 多个RUN指令合并为一个;

Dockerfile中每个指令会创建一个新的镜像层;

4 . 基础镜像标签尽量不要用latest

因为镜像更新后,latest会指向不同镜像,此时构建可能会失效,最好指定确定的镜像标签;

5 . RUN指令后删除多余文件

假设我们更新了 apt-get 源,下载,解压并安装了一些软件包,它们都保存在/var/lib/apt/lists/目录中。但是,运行应用时 Docker 镜像中并不需要这些文件。我们最好将它们删除,因为它会使 Docker 镜像变大;

6 . COPY与ADD优先使用前者

7 . 合理调整COPY与RUN的顺序

尽量把变化最少的部分放在Dockerfile前面,充分利用镜像缓存;

示例中,源代码会经常变化,则每次构建镜像时都需要重新安装 NPM 模块,这显然不是我们希望看到的。因此我们可以先拷贝package.json,然后安装 NPM 模块,最后才拷贝其余的源代码。这样的话,即使源代码变化,也不需要重新安装 NPM 模块。

代码语言:javascript
代码运行次数:0
运行
复制
FROM node:7-alpine

WORKDIR /app

COPY package.json /app
RUN npm install
COPY . /app

ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]

8 . 使用LABEL设置镜像元数据

使用LABEL指令,可以为镜像设置元数据,例如镜像创建者或者镜像说明。旧版的 Dockerfile 语法使用MAINTAINER指令指定镜像创建者,但是它已经被弃用了。

https://blog.fundebug.com/2017/05/15/write-excellent-dockerfile/

4.0 结论

尽可能在不影响性能前提减少docker构建体积,不然抛开部署速度,还会被缓慢的ci/cd构建困扰;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.0 简介
  • 2.0 常规构建镜像
  • 3.0 优化docker生产环境镜像
    • 3.1 用distroless去除容器不必要东西
    • 3.2 使用Node.js Alpine镜像
    • 3.3 生产环境不打包开发的依赖包
    • 3.4 使用基础版本的Alpine镜像组合Nodejs
    • 3.5 多阶段构建
    • 3.6 编写Dockerfiel细节
  • 4.0 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档