精简Docker镜像的五种通用方法
http://dockone.io/article/8163
精简Docker镜像的好处很多,不仅可以节省存储空间和带宽,还能减少安全隐患。优化镜像大小的手段多种多样,因服务所使用的基础开发语言不同而有差异。本文将介绍精简Docker镜像的几种通用方法。
精简Docker镜像大小的必要性
Docker镜像由很多镜像层(Layers)组成(最多127层),镜像层依赖于一系列的底层技术,比如文件系统(filesystems)、写时复制(copy-on-write)、联合挂载(union mounts)等技术,你可以查看Docker社区文档以了解更多有关Docker存储驱动的内容,这里就不再赘述技术细节。总的来说,Dockerfile中的每条指令都会创建一个镜像层,继而会增加整体镜像的尺寸。
下面是精简Docker镜像尺寸的好处:
- 减少构建时间
- 减少磁盘使用量
- 减少下载时间
- 因为包含文件少,攻击面减小,提高了安全性
- 提高部署速度
五点建议减小Docker镜像尺寸
一、优化基础镜像
优化基础镜像的方法就是选用合适的更小的基础镜像,常用的 Linux 系统镜像一般有 Ubuntu、CentOs、Alpine,其中Alpine更推荐使用。大小对比如下:
lynzabo@ubuntu ~/s> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 74f8760a2a8b 8 days ago 82.4MB
alpine latest 11cd0b38bc3c 2 weeks ago 4.41MB
centos 7 49f7960eb7e4 7 weeks ago 200MB
debian latest 3bbb526d2608 8 days ago 101MB
lynzabo@ubuntu ~/s>
Alpine是一个高度精简又包含了基本工具的轻量级Linux发行版,基础镜像只有4.41M,各开发语言和框架都有基于Alpine制作的基础镜像,强烈推荐使用它。Alpine镜像各个语言和框架支持情况,可以参考《优化Docker镜像、加速应用部署,教你6个小窍门》。
查看上面的镜像尺寸对比结果,你会发现最小的镜像也有4.41M,那么有办法构建更小的镜像吗?答案是肯定的,例如 gcr.io/google_containers/pause-amd64:3.1 镜像仅有742KB。为什么这个镜像能这么小?在为大家解密之前,再推荐两个基础镜像:
1、scratch镜像
scratch是一个空镜像,只能用于构建其他镜像,比如你要运行一个包含所有依赖的二进制文件,如Golang程序,可以直接使用scratch作为基础镜像。现在给大家展示一下上文提到的Google pause镜像Dockerfile:
FROM scratch
ARG ARCH
ADD bin/pause-${ARCH} /pause
ENTRYPOINT ["/pause"]
Google pause镜像使用了scratch作为基础镜像,这个镜像本身是不占空间的,使用它构建的镜像大小几乎和二进制文件本身一样大,所以镜像非常小。当然在我们的Golang程序中也会使用。对于一些Golang/C程序,可能会依赖一些动态库,你可以使用自动提取动态库工具,比如ldd、linuxdeployqt等提取所有动态库,然后将二进制文件和依赖动态库一起打包到镜像中。
2、busybox镜像
scratch是个空镜像,如果希望镜像里可以包含一些常用的Linux工具,busybox镜像是个不错选择,镜像本身只有1.16M,非常便于构建小镜像。
二、串联 Dockerfile 指令
大家在定义Dockerfile时,如果太多的使用RUN指令,经常会导致镜像有特别多的层,镜像很臃肿,而且甚至会碰到超出最大层数(127层)限制的问题,遵循 Dockerfile 最佳实践,我们应该把多个命令串联合并为一个 RUN(通过运算符&&和/ 来实现),每一个 RUN 要精心设计,确保安装构建最后进行清理,这样才可以降低镜像体积,以及最大化的利用构建缓存。
下面是一个优化前Dockerfile:
FROM ubuntu ENV VER 3.0.0
ENV TARBALL http://download.redis.io/releases/redis-$VER.tar.gz
==> Install curl and helper tools...
RUN apt-get update
RUN apt-get install -y curl make gcc
==> Download, compile, and install...
RUN curl -L $TARBALL | tar zxv
WORKDIR redis-$VER
RUN make
RUN make install
...
==> Clean up...
WORKDIR /
RUN apt-get remove -y --auto-remove curl make gcc
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/* /redis-$VER
...
CMD ["redis-server"]
构建镜像,名称叫 test/test:0.1。
我们对Dockerfile做优化,优化后Dockerfile:
FROM ubuntu ENV VER 3.0.0
ENV TARBALL http://download.redis.io/releases/redis-$VER.tar.gz RUN echo "==> Install curl and helper tools..." && \
apt-get update && \
apt-get install -y curl make gcc && \
echo "==> Download, compile, and install..." && \
curl -L $TARBALL | tar zxv && \
cd redis-$VER && \
make && \
make install && \
echo "==> Clean up..." && \
apt-get remove -y --auto-remove curl make gcc && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /redis-$VER
...
CMD ["redis-server"]
构建镜像,名称叫 test/test:0.2。
对比两个镜像大小:
root@k8s-master:/tmp/iops# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test/test 0.2 58468c0222ed 2 minutes ago 98.1MB
test/test 0.1 e496cf7243f2 6 minutes ago 307MB
root@k8s-master:/tmp/iops#
可以看到,将多条RUN命令串联起来构建的镜像大小是每条命令分别RUN的三分之一。
提示:为了应对镜像中存在太多镜像层,Docker 1.13版本以后,提供了一个压扁镜像功能,即将 Dockerfile 中所有的操作压缩为一层。这个特性还处于实验阶段,Docker默认没有开启,如果要开启,需要在启动Docker时添加-experimental 选项,并在Docker build 构建镜像时候添加 --squash 。我们不推荐使用这个办法,请在撰写 Dockerfile 时遵循最佳实践编写,不要试图用这种办法去压缩镜像。
三、使用多阶段构建
Dockerfile中每条指令都会为镜像增加一个镜像层,并且你需要在移动到下一个镜像层之前清理不需要的组件。实际上,有一个Dockerfile用于开发(其中包含构建应用程序所需的所有内容)以及一个用于生产的瘦客户端,它只包含你的应用程序以及运行它所需的内容。这被称为“建造者模式”。Docker 17.05.0-ce版本以后支持多阶段构建。使用多阶段构建,你可以在Dockerfile中使用多个FROM语句,每条FROM指令可以使用不同的基础镜像,这样您可以选择性地将服务组件从一个阶段COPY到另一个阶段,在最终镜像中只保留需要的内容。
下面是一个使用COPY --from 和 FROM … AS … 的Dockerfile:
# Compile
FROM golang:1.9.0 AS builder
WORKDIR /go/src/v9.git...com/.../k8s-monitor
COPY . .
WORKDIR /go/src/v9.git...com/.../k8s-monitor
RUN make build
RUN mv k8s-monitor /root
Package
Use scratch image
FROM scratch
WORKDIR /root/
COPY --from=builder /root .
EXPOSE 8080
CMD ["/root/k8s-monitor"]
构建镜像,你会发现生成的镜像只有上面COPY 指令指定的内容,镜像大小只有2M。这样在以前使用两个Dockerfile(一个Dockerfile用于开发和一个用于生产的瘦客户端),现在使用多阶段构建就可以搞定。
四、构建业务服务镜像技巧
Docker在build镜像的时候,如果某个命令相关的内容没有变化,会使用上一次缓存(cache)的文件层,在构建业务镜像的时候可以注意下面两点:
- 不变或者变化很少的体积较大的依赖库和经常修改的自有代码分开;
- 因为cache缓存在运行Docker build命令的本地机器上,建议固定使用某台机器来进行Docker build,以便利用cache。
下面是构建Spring Boot应用镜像的例子,用来说明如何分层。其他类型的应用,比如Java WAR包,Nodejs的npm模块等,可以采取类似的方式。
1、在Dockerfile所在目录,解压缩maven生成的jar包。
$ unzip <path-to-app-jar>.jar -d app
2、Dockerfile我们把应用的内容分成4个部分COPY到镜像里面:其中前面3个基本不变,第4个是经常变化的自有代码。最后一行是解压缩后,启动spring boot应用的方式。
FROM openjdk:8-jre-alpine LABEL maintainer "opl-xws@xiaomi.com"
COPY app/BOOT-INF/lib/ /app/BOOT-INF/lib/
COPY app/org /app/org
COPY app/META-INF /app/META-INF
COPY app/BOOT-INF/classes /app/BOOT-INF/classes
EXPOSE 8080
CMD ["/usr/bin/java", "-cp", "/app", "org.springframework.boot.loader.JarLauncher"]
这样在构建镜像时候可大大提高构建速度。
五、其他优化办法
1、RUN命令中执行apt、apk或者yum类工具技巧
如果在RUN命令中执行apt、apk或者yum类工具,可以借助这些工具提供的一些小技巧来减少镜像层数量及镜像大小。举几个例子:
(1)在执行apt-get install -y 时增加选项— no-install-recommends ,可以不用安装建议性(非必须)的依赖,也可以在执行apk add 时添加选项--no-cache 达到同样效果;
(2)执行yum install -y 时候, 可以同时安装多个工具,比如yum install -y gcc gcc-c++ make …。将所有yum install 任务放在一条RUN命令上执行,从而减少镜像层的数量;
(3)组件的安装和清理要串联在一条指令里面,如 apk --update add php7 && rm -rf /var/cache/apk/* ,因为Dockerfile的每条指令都会产生一个文件层,如果将apk add … 和 rm -rf … 命令分开,清理无法减小apk命令产生的文件层的大小。 Ubuntu或Debian可以使用 rm -rf /var/lib/apt/lists/* 清理镜像中缓存文件;CentOS等系统使用yum clean all 命令清理。
2、压缩镜像
Docker自带的一些命令还能协助压缩镜像,比如export和import。
$ docker run -d test/test:0.2
$ docker export 747dc0e72d13 | docker import - test/test:0.3
使用这种方式需要先将容器运行起来,而且这个过程中会丢失镜像原有的一些信息,比如:导出端口,环境变量,默认指令。
查看这两个镜像history信息,如下,可以看到test/test:0.3 丢失了所有的镜像层信息:
root@k8s-master:/tmp/iops# docker history test/test:0.3
IMAGE CREATED CREATED BY SIZE COMMENT
6fb3f00b7a72 15 seconds ago 84.7MB Imported from -
root@k8s-master:/tmp/iops# docker history test/test:0.2
IMAGE CREATED CREATED BY SIZE COMMENT
58468c0222ed 2 hours ago /bin/sh -c #(nop) CMD ["redis-server"] 0B
1af7ffe3d163 2 hours ago /bin/sh -c echo "==> Install curl and helper… 15.7MB
8bac6e733d54 2 hours ago /bin/sh -c #(nop) ENV TARBALL=http://downlo… 0B
793282f3ef7a 2 hours ago /bin/sh -c #(nop) ENV VER=3.0.0 0B
74f8760a2a8b 8 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 8 days ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 8 days ago /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$… 2.76kB
<missing> 8 days ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B
<missing> 8 days ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 745B
<missing> 8 days ago /bin/sh -c #(nop) ADD file:5fabb77ea8d61e02d… 82.4MB
root@k8s-master:/tmp/iops#
社区里还有很多压缩工具,比如Docker-squash ,用起来更简单方便,并且不会丢失原有镜像的自带信息,大家有兴趣可以试试。
精简Docker镜像的五种通用方法的更多相关文章
- JS中的五种去重方法
JS中的五种去重方法 第一种方法: 第二种方法: 第三种方法: 第四种方法: 第五种方法:优化遍历数组法 思路:获取没重复的最右一值放入新数组 * 方法的实现代码相当酷炫,* 实现思路:获取没重复的 ...
- 精简Docker镜像的几个方法
一.使用更精简的镜像 常用的Linux系统镜像一般有 Debian.Ubuntu.CentOS和Alpine,其中Alpine是面向安全的轻量级Linux发行版本.Docker的Alpine镜像仅有不 ...
- SpringBoot 构建 Docker 镜像的 3 种方式
本文将介绍3种技术,通过 Maven 把 SpringBoot 应用构建成 Docker 镜像. (1)使用 spring-boot-maven-plugin 内置的 build-image. (2) ...
- spring与mybatis五种整合方法
1.采用数据映射器(MapperFactoryBean)的方式 不用写mybatis映射文件,采用注解方式提供相应的sql语句和输入参数. (1)Spring配置文件: <!-- 引入jdbc ...
- 制作Docker镜像的两种方式
此文已由作者朱笑天授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 一.使用docker commit命令制作docker镜像 1. pull一个centos6.6的基础镜像, ...
- java 中的几种 "通用方法“
前言 Java中,除了基本的数值类型,其他所有数据类型(包括数组)都是对象. 而Object这个类是所有类的超类,它提供的方法,自然能够使用于它的所有子类(所有非基本数值类型). 本文介绍了Objec ...
- Array 的五种迭代方法 -----every() /filter() /forEach() /map() /some()
ES5定义了五个迭代方法,每个方法都接收两个参数:要在每一项上运行的函数和运行该函数的作用域对象(可选的),作用域对象将影响this的值.传入这些方法中的函数会接收三个参数:数组的项的值.该项在数组中 ...
- Docker 镜像(五)
我们都知道,操作系统分为内核和用户空间.对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持.而 Docker 镜像(Image),就相当于是一个 root 文件系统.比 ...
- JS 中对变量类型的五种判断方法
5种基本数据类型:undefined.null.boolean.unmber.string 复杂数据类型:object. object:array.function.date等 方法一:使用typeo ...
随机推荐
- 前端WEB编辑器-------webstrom
欲先善其事,必先利其器,如题.看到网上一篇介绍webstrom的文章,觉得功能确实强大,也知道为什么阿里巴巴的前端传到github上的文件为啥都有一个 .idea 文件,(传说淘宝内部推荐写js用we ...
- [SVN]TortoiseSVN报“500 Internal Server Error”错误的解决方法
背景:由于某种原因,本人重启了一下SVN服务的物理机,重启过后发现所有人访问SVN都报如下错误. 由于重启之前是没有做过任何改动,所以面对这个错误有点摸不着头脑,防火墙.网管.清缓存等方法都试过,还没 ...
- linux 命令——41 ps(转)
Linux中的ps命令是Process Status的缩写.ps命令用来列出系统中当前运行的那些进程.ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信 ...
- java 串口通信实现流程
1.下载64位rxtx for java 链接:http://fizzed.com/oss/rxtx-for-java 2.下载下来的包解压后按照说明放到JAVA_HOME即JAVA的安装路径下面去 ...
- OpenLayers项目分析——(一)项目介绍
OpenLayers 是由MetaCarta公司开发的,用于WebGIS客户端的JavaScript包,目前的最高版本是2.7 V,通过BSD License 发行.它实现访问地理空间数据的方法都符合 ...
- URL Schemes 不能识别和不能跳转的原因
在app跳转的过程中 需要设置url schemes后,但是设置完后,却不能识别, (测试方式:URL scheme + ://)在浏览器中打开,如果能打开app,就是能跳转 今天遇到了一个坑爹的问题 ...
- 2018.6.16 PHP小实验
PHP实验 实验一 <?php /** * Created by PhpStorm. * User: qichunlin * Date: 2018/5/17 * Time: 下午5:35 */ ...
- Windows下配置Jmeter环境变量
一.安装SDK 1.下载并安装sdk,安装目录为D:\Program Files (x86)\Java\jdk1.7.0_01 2.配置环境变量 1)新建系统变量:JAVA_HOME = D:\Pro ...
- Bootstrap 按钮(Button)插件加载状态
通过按钮(Button)插件,您可以添加进一些交互.比如控制按钮的状态.或者为其它组件(工具栏)创建按钮组. 加载状态 如需向按钮添加加载状态,只需要简单地向 button 元素添加 data-loa ...
- 处理侧滑返回与 ScrollView 手势冲突
与处理双击.单击手势互斥原则一样: // 手势互斥(侧滑返回手势失效后才响应UITableView的滑动手势) [tableView.panGestureRecognizer requireGestu ...