本文概述
简化Docker镜像有很多好处, 因为它不仅可以节省存储空间和带宽, 还可以降低安全风险。有多种方法可以优化镜像的大小, 具体取决于服务使用的基础开发语言。在本文中, 我将介绍几种简化Docker镜像的通用方法。
简化Docker镜像大小的重要性
Docker镜像包含许多层(最多127层)。镜像层依赖于一系列基础技术, 例如文件系统, 写时复制, 联合安装等。你可以查看Docker社区文档以了解更多信息。通常, Dockerfile中的每条指令都会创建一个镜像层, 这将增加整个镜像的大小。
以下是简化Docker镜像大小的好处:
减少构建时间减少磁盘使用量减少下载时间由于包含的文件较少而提高了安全性提高了部署速度
减少Docker镜像大小的五个建议
1.优化基础镜像
优化基础镜像的方法是选择合适的较小基础镜像。通常, Ubuntu, CentOs和Alpine都是常用的Linux系统镜像, 其中推荐使用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的基本镜像, 因此强烈建议使用。
查看上面的比较, 你可以看到最小的大小是4.41M。有什么方法可以建立较小的镜像?答案是肯定的, 例如gcr.io/google_containers/pause-amd64:3.1的镜像只有742KB。为什么镜像尺寸这么小?首先让我们看一下两个基本镜像:
1.1刮擦镜像
Scratch是一个空镜像, 只能用于构建其他镜像。例如, 如果要运行包含所有依赖项的二进制文件(例如Golang程序), 则可以将草稿用作基础镜像。现在, 让我们向你展示上面提到的Google暂停镜像Dockerfile:
FROM scratch
ARG ARCH
ADD bin/pause-${ARCH} /pause
ENTRYPOINT ["/pause"]
Google暂停镜像使用草稿作为基本镜像。镜像本身并不占用空间。使用草稿作为基础镜像的镜像大小几乎与二进制文件本身一样小, 因此该镜像非常小。当然, 它也会在我们的Golang程序中使用。对于某些Golang / C程序, 你可能需要依赖一些动态库。你可以使用自动提取动态库工具(例如ldd, linuxdeployqt等)提取所有动态库, 然后将二进制文件以及相关的动态库打包到镜像中。
1.2 busybox镜像
刮擦是空白镜像。如果你希望镜像包含一些常用的Linux工具, 那么busybox镜像是一个不错的选择。镜像本身只有1.16M, 这对于构建小镜像非常方便。
2.串联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#s
可以看出, 通过对多个RUN命令进行着色而构成的镜像大小是对每个命令使用RUN指令的三分之一。
注意:为了处理镜像中的这么多镜像层, Docker 1.13版及更高版本提供了压缩镜像的功能, 该功能会将Dockerfile中的所有操作压缩到一个层中。该功能仍处于试验阶段, 默认情况下未在Docker中启用。如果要启用它, 则需要在启动Docker时添加-experimental选项, 并在Docker内部版本构建镜像时添加–squash。我们不推荐这种方法, 你最好在编写Dockerfile时遵循最佳实践, 而不要尝试以这种方式压缩镜像。
3.使用多阶段构建
Dockerfile中的每条指令都会在镜像上添加一个镜像层, 你需要在移至下一个镜像层之前清除不需要的组件。实际上, 有一个用于开发的Dockerfile(包含构建应用程序所需的一切)和一个用于生产的瘦客户端(仅包含你的应用程序以及运行它所需的内容)。 Docker 17.05.0-ce之后支持多阶段构建。你可以通过多阶段构建在Dockerfile中使用多个FROM语句。而且, 每个FROM指令可以使用不同的基本镜像, 因此你可以有选择地将服务组件从一个阶段复制到另一个阶段, 而仅保留最终镜像中需要的内容。
这是一个使用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和用于生产的瘦客户端)之前, 现在可以将其替换为多阶段构建。
4.建立业务服务形象的技巧
在Docker中构建镜像时, 如果与命令相关的内容未更改, 它将使用最后一个缓存的文件层。构建企业形象时, 应注意以下几点:
- 较大的未更改或很少更改的从属库应与经常修改的代码分开;
- 由于缓存是在运行Docker build命令的本地计算机上缓存的, 因此建议使用用于Docker构建的计算机来利用缓存。
以下是构建Spring Boot应用程序镜像的示例, 以说明如何分层。其他类型的应用程序, 例如Java WAR软件包, Nodejs的npm模块等, 也可以通过类似的方法来实现。
4.1将maven生成的jar包解压缩到Dockerfile所在的目录中。
$ unzip <path-to-app-jar>.jar -d app
4.2让我们将应用程序的内容分为4部分, 然后将其复制到镜像中:前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.
构建镜像时, 可以大大提高构建速度。
5.其他优化方法
5.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安装任务放在一个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命令进行清理。
5.2压缩影像
Docker随附的某些命令(例如导出和导入)也可以帮助压缩镜像。
$ docker run -d test/test:0.2
$ docker export 747dc0e72d13 | docker import - test/test:0.3
要使用此方法, 你需要首先运行容器, 原始镜像的某些信息将在此过程中丢失, 例如导出端口, 环境变量和默认指令。
查看两个镜像的历史信息, 可以看到所有镜像层信息在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镜像的简化方法值得深入讨论和实践。希望你能从本文中学到一些东西。并且, 如果你有更好的方法和建议, 可以随时发表评论。
评论前必须登录!
注册