写点什么

如何快速将容器云镜像大小精简 98%

  • 2020-05-14
  • 本文字数:5659 字

    阅读完需:约 19 分钟

如何快速将容器云镜像大小精简98%

接触过容器云或者用过容器的用户,想必都遇到容器镜像占用空间很大的问题,遇到此类问题的时候,大部分人可能更加习惯于为容器的镜像仓库增加磁盘空间。当然这种方式无可厚非,毕竟这种方式可以帮助我们快速的解决掉手里的问题。


除了上面扩磁盘的解决方式,其实我们还可以 采用缩减容器镜像的方式 。此种方式不但可以帮助我们节省添加新磁盘的开支,还可减少我们制作镜像和传输镜像的时间。优化的比较好的镜像占用的空间,基本和应用的文件占用的空间相当,基本不会占用太多的额外存储空间。


下文中,我们会具体看看如何快速精简容器云镜像。



接触过 Docker 的同学都知道,Dockerfile 是由一些指令的组成,且 Dockerfile 文件中的每条指令对应着 Linux 操作系统中的一条命令,当我们构建镜像时,Docker 程序会将这些 Dockerfile 指令翻译成 Linux 可执行的命令。


Dockerfile 中每一条指令都会创建一个镜像层,随着指令的执行继而会增加镜像整体的大小。


常用 Dockerfile 指令

Dockerfile 文件有自己的书写格式和支持的命令,常用的 Dockerfile 指令有:


  • FROM 指定基镜像。

  • MAINTAINER 设置镜像的作者信息,如作者姓名、邮箱等。

  • COPY 将文件从本地复制到镜像,拷贝前需要保证本地源文件存在。

  • ADD 与 COPY 类似,复制文件到镜像。不同的是,如果文件是归档文件(tar, zip, tgz, xz 等),会被自动解压。

  • ENV 设置环境变量,格式: ENV key=value 或 ENV key value,运行容器后,可直接在容器中使用。

  • EXPOSE 暴露容器中指定的端口,只是一个声明,主要用户了解应用监听的端口。

  • VOLUME 挂载卷到容器,需要注意的是,保存镜像时不会保存卷中的数据。

  • WORKDIR 设置当前工作目录,后续各层的当前目录都被指定。

  • RUN 在容器中运行指定的命令。

  • CMD 容器启动时运行的命令。Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。

  • ENTRYPOINT 设置容器启动时运行的命令。Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT,这个是与 CMD 的区别。


精简镜像的好处不言而喻,可以节省存储存储空间,更可以减少镜像传输时间,减少带宽的消耗,加快传输。


容器镜像的基本理论

在开始制作镜像之前,我们先了解一下容器镜像的基本理论知识。


容器镜像中最重要的概念就是 layers,即镜像层。



镜像层依赖于一系列的底层技术,比如文件系统(filesystems)、写时复制(copy-on-write)、联合挂载(union mounts)等技术,这些技术的细节在此我们不再赘述,感兴趣的同学可以直接直接查看 Docker 官方文档(https://docs.docker.com/storage/storagedriver/)进行学习。


总的来说,精简镜像我们最需要记住的一句话是:


“在 Dockerfile 中,每条指令都会创建一个镜像层,继而会增加镜像整体的大小。”


下面我们以一个示例来说明一下:


我们 pull 一个镜像,以 busybox 为例:


[root@work ~]# docker pull hub.c.163.com/library/busybox:latestTrying to pull repository hub.c.163.com/library/busybox ...latest: Pulling from hub.c.163.com/library/busyboxaab39f0bc16d: Pull compl`eteDigest: sha256:662af1d642674367b721645652de96f9c147417c2efb708eee4e9b7212697762Status: Downloaded newer image for hub.c.163.com/library/busybox:latest
复制代码


# 看下镜像大小


[root@work ~]# docker images | grep busyboxhub.c.163.com/library/busybox                          latest                                          d20ae45477cb        18 months ago       1.129 MB
复制代码


从上面结果看我们 pull 下来的镜像大小只有 1.129MB。


下面我们编写一个 Dockerfile 文件,文件中我们新建一个目录,目录中新建一个 100MB 的文件,最后我们删掉新建的文件。


Dockerfile 内容如下:


#基镜像FROM hub.c.163.com/library/busybox:latest#新建目录RUN mkdir /tmp/dir1#新建一个100MB的文件RUN dd if=/dev/zero of=/tmp/dir1/file1 bs=1M count=100#删除文件RUN rm /tmp/dir1/file1
复制代码


从 Dockerfile 内容看,其实我们基本什么都没干。


然后我们用这个 Dockerfile 构建新建的镜像,并查看新镜像的大小:


[root@work ~]# docker build -t busybox:v1 .Sending build context to Docker daemon 1.307 GBStep 1 : FROM hub.c.163.com/library/busybox:latest ---> d20ae45477cbStep 2 : RUN mkdir /tmp/dir1 ---> Running in 63fa5f27c779 ---> da95ea8ae5eeRemoving intermediate container 63fa5f27c779Step 3 : RUN dd if=/dev/zero of=/tmp/dir1/file1 bs=1M count=100 ---> Running in d3e8bbb4f151100+0 records in100+0 records out104857600 bytes (100.0MB) copied, 0.247500 seconds, 404.0MB/s ---> 42b721238144Removing intermediate container d3e8bbb4f151Step 4 : RUN rm -rf /tmp/dir1/file1 ---> Running in 6b51b633fb21 ---> 04096cc5d718Removing intermediate container 6b51b633fb21Successfully built 04096cc5d718
复制代码


# 查看镜像信息 [root@work ~]# docker images | grep busybox busybox                                                v1                                          04096cc5d718        58 seconds ago      106 MB hub.c.163.com/library/busybox                          latest                                          d20ae45477cb        18 months ago       1.129 MB
复制代码


从上面的结果可以看出,虽然在 Dockerfile 中我们将新建的 100MB 的文件删除了,但新镜像的大小仍大于 100MB。


多出了 100 多 MB,这是为何?其实这点和 git 类似,Docker 镜像和 git 都用到了写时复制技术,git 每次提交时都会保存一个文件的版本,Dockerfile 每行指令都会增加整体镜像的大小,即使我们什么都没做。


如何进行容器镜像精简

下面我们开始说下本文的重点: 镜像精简


我们将以最常见的 nosql 数据库 Redis 为例,一步步来介绍如何制作更精简的 Docker 镜像。


首先我们来编写一下构建 Redis 镜像的 Dockerfile 文件,具体内容如下:


FROM hub.c.163.com/library/ubuntu:trusty#redis 版本ENV VER     3.0.0ENV TARBALL http://download.redis.io/releases/redis-$VER.tar.gzRUN apt-get update#安装依赖的工具RUN apt-get install -y  curl make gcc#下载redis源码包并解压RUN curl -L $TARBALL | tar zxv#进入解压后的目录WORKDIR  redis-$VER#编译redis源码RUN make#安装redisRUN make installWORKDIR /#清理前面安装的依赖工具RUN apt-get remove -y --auto-remove curl make gccRUN apt-get cleanRUN rm -rf /var/lib/apt/lists/*  /redis-$VER#运行redisCMD ["redis-server"]
复制代码


然后再利用上面的 Dockerfile 构建镜像:


[root@work ~]# docker build -t redis:3.0.0 .…………Removing intermediate container b55656487022Successfully built 7df9c7899ae3
复制代码


查看构建出的镜像大小:


[root@work ~]# docker images | grep redisredis                                                  3.0.0                                            7df9c7899ae3        10 hours ago        359.7 MB
复制代码


从结果看构建出优化前的镜像约为 360MB。


下面我们将开始逐步优化。



1. 选用更小的基镜像

常用的 linux 系统一般有 CentOS、Debian、Ubuntu,三者中 Debian 更轻量,且 Debian 系统镜像中提供的功能一般也是够用的,三个系统镜像尺寸对比如下:



在此我们以上面最小的镜像 debian:wheezy 作为即镜像,重新进行构建:


Dockerfile 内容:


FROM hub.c.163.com/library/debian:wheezy#redis 版本ENV VER     3.0.0ENV TARBALL http://download.redis.io/releases/redis-$VER.tar.gzRUN apt-get update#安装依赖的工具RUN apt-get install -y  curl make gcc#下载redis源码包并解压RUN curl -L $TARBALL | tar zxv#进入解压后的目录WORKDIR  redis-$VER#编译redis源码RUN make#安装redisRUN make installWORKDIR /#清理前面安装的依赖工具RUN apt-get remove -y --auto-remove curl make gccRUN apt-get cleanRUN rm -rf /var/lib/apt/lists/*  /redis-$VER#运行redisCMD ["redis-server"]
复制代码


构建新镜像:


[root@work ~]# docker build -t redis:3.0.0-v2 .…………Removing intermediate container 3498689792ceSuccessfully built 4faa1aa0936d
复制代码


对比两个镜像大小:



从结果看,更换基镜像后的新镜像减少了 37%,精简效果还算可以,但精简效果并未达到我们的目标。


如果仔细看的话我们会发现,原本只有 85MB 大小的 debian 基镜像,在构建后增加到了 228MB,可见此处还有很大的优化空间。后续的优化就需要用到我们在上文中说到的镜像层相关的知识了。



2. 合并 Dockerfile 中指令

Dockerfile 中指令的合并一般是指 RUN 指令的合并。


我们可以通过 &&符号和/ 将 Dockerfile 中的多个 RUN 指令合并成一条 RUN 指令,此种方式一般精简效果较好。


优化后的 Dockerfile 内容如下:


FROM hub.c.163.com/library/debian:wheezy#redis 版本ENV VER     3.0.0ENV TARBALL http://download.redis.io/releases/redis-$VER.tar.gzRUN apt-get update && \apt-get install -y  curl make gcc &&\curl -L $TARBALL | tar zxv  && \cd  redis-$VER  && \make  && \make install && \cd /  && \apt-get remove -y --auto-remove curl make gcc && \apt-get clean  && \rm -rf /var/lib/apt/lists/*  /redis-$VER#运行redisCMD ["redis-server"]
复制代码


构建新镜像:


[root@work ~]# docker build -t redis:3.0.0-v3 .…………Removing intermediate container 9e5cffcd8bdbSuccessfully built dafd91993dfb
复制代码


查看镜像大小:



从结果看镜像大小约缩减 72%,可见合并 Dockerfile 指令的方式精简效果较明显,新镜像只比基镜像增加约 10MB。


合并 Dockerfilec 指令精简镜像这种方式是最常用的精简镜像尺寸的方式。


3. 使用最精简的基镜像

上文中第 1 步中,我们使用的基镜像为 Debian 镜像,约 89MB,但如果我们只是安装 Redis 服务的话不一定非得使用这么大的系统镜像,我们可以借助一些更小的镜像,如 scratch、busybox、alpine 等,这些镜像大小往往小于 5MB,因此我们可以直接以此作为基镜像来构建新的 Redis 镜像。


此处我们以 scratch 作为基镜像构建 Redis。scratch 镜像往往只有 1~5MB 大小。


Scrach 是一个空镜像,只能用于构建镜像。在构建一些基础镜像,如 debian、busybox 时非常有用。Scrach 也常用于构建超小的镜像,如构建一个只包含所有库的二进制文件。


但使用最精简的基镜像,我们还需要做些额外的工作,具体过程见下文。


4. 提取.so 库

了解过 Redis 源码的话大家会知道 Redis 开发语言为 C 语言,会依赖一些.so 库,因此我们需要先准备好编译 Redis 所需的.so 文件。


我们通过前面构建好的 redis:3.0.0-v3 镜像运行容器,然后进入容器中获取下 redis 依赖的.so 文件。


# 后台运行容器:


[root@work ~]# docker run --name redisv3 -d  redis:3.0.0-v3ab361e7fc2e70b5b45fa1545917ee92158bb859e833c3f7fcfb80e43bb69cb0c
复制代码


# 查看容器运行状态



# 进入容器


[root@work ~]# docker exec -ti redisv3 /bin/bashroot@ab361e7fc2e7:/#
复制代码


查看 redis-server 二进制文件位置

root@ab361e7fc2e7:/# which redis-server/usr/local/bin/redis-server
复制代码


# 查看 redis-server 依赖的.so 文件


root@ab361e7fc2e7:/# ldd /usr/local/bin/redis-serverlinux-vdso.so.1 =>  (0x00007ffedfd01000)libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f0de7a5e000)libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f0de785a000)libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f0de763d000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0de72b0000)/lib64/ld-linux-x86-64.so.2 (0x00007f0de7ce4000)
复制代码


将编译 Redis 需要的所有依赖打包:


root@ab361e7fc2e7:/# mkdir soroot@ab361e7fc2e7:/# cp usr/local/bin/redis-server so/root@ab361e7fc2e7:/# cp lib/x86_64-linux-gnu/libm.so.6 so/root@ab361e7fc2e7:/# cp lib/x86_64-linux-gnu/libpthread.so.0 so/root@ab361e7fc2e7:/# cp lib/x86_64-linux-gnu/libc.so.6 so/      root@ab361e7fc2e7:/# cp lib64/ld-linux-x86-64.so.2 so/  root@ab361e7fc2e7:/# cd soroot@ab361e7fc2e7:/# tar zcvf so.tar.gz ./*so/so/redis-serverso/libm.so.6so/libpthread.so.0so/libc.so.6so/ld-linux-x86-64.so.2
复制代码


# 将打包好的文件从容器拷贝出来:


[root@work ~]# docker cp redisv3:/so/so.tar.gz .
复制代码


编写 Dockerfile 文件,具体内容如下:


FROM scratch# 添加依赖的库文件ADD  so.tar.gz  /# redis 配置文件,需要自己准备一份COPY redis.conf     /etc/redis/redis.conf# 暴露的端口EXPOSE 6379CMD ["redis-server"]
复制代码


构建新镜像:


[root@work ~]# docker build -t redis:3.0.0-v4 .Sending build context to Docker daemon 1.316 GBStep 1 : FROM scratch --->Step 2 : ADD so.tar.gz / ---> Using cache ---> 82b2b6def214Step 3 : COPY redis.conf /etc/redis/redis.conf ---> 3f382da261beRemoving intermediate container 60af6a5ab042Step 4 : EXPOSE 6379 ---> Running in 78c541686668 ---> 043ed6cf87e0Removing intermediate container 78c541686668Step 5 : CMD redis-server ---> Running in 2c8b9fb0547d ---> 75d828ebf3aaRemoving intermediate container 2c8b9fb0547dSuccessfully built 75d828ebf3aa
复制代码


对比镜像大小:



从结果我们可以看出,精简效果非常显著,基于 scratch 构建的新镜像大小只有 6.9MB,相比之前的 359MB、228MB、102MB,新镜像空间占用已经很少。


结语

以上即是本文精简 Docker 镜像的整个过程。


除了上面我们介绍的精简方法之外,还有一些常见的精简方式,如使用镜像压缩工具 docker-squash,但此种方式压缩效果并不明显,因此在此我们并未做详细介绍,感兴趣的朋友也可以自己尝试噢。


2020-05-14 22:261333

评论

发布
暂无评论
发现更多内容

金三银四跳槽季,美团、字节、阿里、腾讯Java面经,终入字节

Geek_0c76c3

Java 数据库 开源 程序员 开发

进击的PyTorch,和它背后的开源领袖

OneFlow

人工智能 机器学习 深度学习 开源

双活数据中心建设要点

穿过生命散发芬芳

10月月更 双数据中心

全网独家首发Java面试题,包含Spring全家桶+高并发+Netty+Redis+Dubbo等面试专题

小二,上酒上酒

Java Linux Netty 高并发 Spring全家桶

阿里内部整理的Spring boot保姆级笔记,简直太牛了

小二,上酒上酒

spring springboot

阿里出品的这份Java性能调优实战手册,直接涨薪25K,真的香啊

小二,上酒上酒

Java 阿里 阿里面试

大型企业选择低代码的主要原因是什么?

优秀

低代码 低代码平台

数据中台建设5大关键步骤

阿泽🧸

数据中台 10月月更

牛啊牛啊,这篇Spring Cloud Alibaba笔记一应俱全,几乎涵盖了所有操作

小二,上酒上酒

spring spring cloud alibaba

这份神仙级Spring Security源码手册,真的很强悍

小二,上酒上酒

spring spring security springboot

在数字化浪潮中,为企业建造一艘“方舟”

元年技术洞察

微服务 云原生 企业数字化 PaaS 平台

【高并发】ScheduledThreadPoolExecutor与Timer的区别和简单示例

冰河

并发编程 多线程 高并发 协程 异步编程

vue组件通信6种方式总结(常问知识点)

bb_xiaxia1998

Vue

ConcurrentDictionary<T,V> 的这两个操作不是原子性的

有态度的马甲

【一Go到底】第十二天---switch

指剑

Go Goalng 10月月更

惊为天人,百度推出的Redis笔记真的太香了

小二,上酒上酒

redis 面试

肝下50万字的《Linux内核精通》笔记,你的底层原理水平将从入门到入魔【建议收藏】

Linux爱好者

内存管理 嵌入式 Linux内核 进程管理 驱动开发

见大牛、聊感悟、拿好礼...开发者一起来微软Ignite赴约!

InfoQ写作社区官方

热门活动

腾讯资深架构师整理出来的Java高级开发需要的分布式技术,简直绝了

小二,上酒上酒

Java 编程 JAVA开发

阿里大佬手码的SpringCloud+Alibaba笔记开源了,堪称保姆式教学

Geek_0c76c3

Java 数据库 开源 程序员 开发

Java后端没这些东西都不敢跳!对标阿里P7技术路线你值得拥有

Geek_0c76c3

Java 数据库 程序员 架构 开发

TDengine | taosdump的使用方法和注意事项

TDengine

数据库 tdengine 开源 时序数据库 taosdump

还不懂Spring?阿里架构师整理的Spring宝典助你一臂之力

小二,上酒上酒

spring spring cloud ali spring宝典

来了来了,阿里p9整理的Netty速成笔记,应有尽有

小二,上酒上酒

Netty

短期内跳槽的Java程序员必看的八项知识点+两大项目实战

Geek_0c76c3

Java 数据库 开源 程序员 开发

vue组件通信方式有哪些?

bb_xiaxia1998

Vue

SAP | 在abap开发过程中常用的Tcode

暮春零贰

SAP abap 10月月更

【Go】Go 操作 excel 代码封装

非晓为骁

Excel go语言

Koordinator v0.7: 为任务调度领域注入新活力

阿里巴巴云原生

阿里云 云原生 Koordinator

精彩演讲推荐|智能化变更防控方法、架构与组织实践

TRaaS

太牛了,这份Spring Cloud Alibaba学习文档清晰全面,一应俱全

小二,上酒上酒

spring Spring Cloud

如何快速将容器云镜像大小精简98%_文化 & 方法_Rancher_InfoQ精选文章