Docker(十四)—镜像瘦身

 

优化Docker镜像的方法

要保证镜像尽可能小,可以从以下五个方面着手:

  • 优化基础镜像
  • 串联Dockerfile指令,保证层级尽量少
  • 去除不必要的内容
  • 复用镜像层
  • 分阶段构建

使用scratch制作自定义镜像

Docker镜像是由很多镜像层(Layers)组成的(最多127层), Dockerfile 中的每条指定都会创建镜像层,不过只有 RUN, COPY, ADD 会使镜像的体积增加。这个可以通过命令 docker history image_id 来查看每一层的大小。

使用空镜像scratch

[空镜像地址:https://hub.docker.com//scratch](https://hub.docker.com//scratch)

使用 scratch 空镜像的本质是让你的程序只调用 host 主机的 Linux 内核部分的功能,而不依赖容器内的操作环境功能。

由于 host 主机的 Linux 内核部分对 Docker 容器是共享的,因此其 scratch 空镜像的大小可以认为近似为 0

scratch 是一个 search 得到,但是 pull 不了的特殊镜像。

这里我们以官方的 alpine:3.12 为例看看它的镜像层情况。

wget https://dl-cdn.alpinelinux.org/alpine/v3.12/releases/x86_64/alpine-minirootfs-3.12.0-x86_64.tar.gz
vim Dockerfile
FROM scratch
ADD alpine-minirootfs-3.12.0-x86_64.tar.gz /
CMD ["/bin/sh"]
docker build -t test:1.0 .
docker history 0ec1d7d0c85a 

image

对比 Dockerfile 和镜像历史层数发现 ADD 命令层占据了 5.57M 大小,而 CMD 命令层并不占空间

镜像的层就像 Git 的每一次提交 Commit, 用于保存镜像的上一个版本和当前版本之间的差异。所以当我们使用 docker pull 命令从公有或私有的 Hub 上拉取镜像时,它只会下载我们尚未拥有的层。 这是一种非常高效的共享镜像的方式。

瘦身方法

了解了镜像构建中体积增大的原因,那么就可以对症下药:精简层数或精简每一层大小。

精简层数的方法有如下几种:

  • RUN指令合并
  • 多阶段构建

精简每一层的方法有如下几种:

  • 使用合适的基础镜像(首选alpine)
  • 删除RUN的缓存文件

镜像瘦身

FROM ubuntu:focal
ENV REDIS_VERSION=6.0.5
ENV REDIS_URL=http://download.redis.io/releases/redis-$REDIS_VERSION.tar.gz
RUN sed -i "s/archive.ubuntu.com/mirrors.aliyun.com/g; s/security.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list
RUN apt update
RUN apt install -y curl make gcc
RUN curl -L $REDIS_URL | tar xzv
WORKDIR redis-$REDIS_VERSION
RUN make
RUN make install
RUN rm  -rf /var/lib/apt/lists/*
CMD ["redis-server"]
docker build -t redis:1.0 .

image

RUN指令合并

指令合并是最简单也是最方便的降低镜像层数的方式。该操作节省空间的原理是在同一层中清理“缓存”和工具软件。

FROM ubuntu:focal
ENV REDIS_VERSION=6.0.5
ENV REDIS_URL=http://download.redis.io/releases/redis-$REDIS_VERSION.tar.gz
RUN sed -i "s/archive.ubuntu.com/mirrors.aliyun.com/g; s/security.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list &&\
apt update &&\
apt install -y curl make gcc &&\
curl -L $REDIS_URL | tar xzv &&\
cd redis-$REDIS_VERSION &&\
make &&\
make install &&\
apt remove -y --auto-remove curl make gcc &&\
apt clean &&\
rm  -rf /var/lib/apt/lists/*
CMD ["redis-server"]
docker build -t redis:2.0 .

image

指令合并的方法是通过在同一层中将缓存和不用的工具软件清理掉,以达到减小镜像体积的目的。

多阶段构建

多阶段构建方法是官方打包镜像的最佳实践,它是将精简层数做到极致的方法。通俗点讲它是将打包镜像分成两个阶段,一个阶段用于开发,打包,该阶段包含构建应用程序所需的所有内容;一个用于生产运行,该阶段只包含你的应用程序以及运行它所需的内容。多阶段构建的想法很简单:“我不想在最终的镜像中包含一堆 C 或 Go 编译器和整个编译工具链,我只要一个编译好的可执行文件!”多阶段构建可以由多个 FROM 指令识别,每一个 FROM 语句表示一个新的构建阶段,阶段名称可以用 AS 参数指定,

FROM ubuntu:focal AS build
ENV REDIS_VERSION=6.0.5
ENV REDIS_URL=http://download.redis.io/releases/redis-$REDIS_VERSION.tar.gz
RUN sed -i "s/archive.ubuntu.com/mirrors.aliyun.com/g; s/security.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list &&\
    apt update &&\
    apt install -y curl make gcc &&\
    curl -L $REDIS_URL | tar xzv &&\
    cd redis-$REDIS_VERSION &&\
    make &&\
    make install
FROM ubuntu:focal
ENV REDIS_VERSION=6.0.5
COPY --from=build /usr/local/bin/redis* /usr/local/bin/
CMD ["redis-server"]
docker build -t redis:3.0 .
  • 第一行多了As build, 为后面的COPY做准备
  • 第一阶段中没有了清理操作,因为第一阶段构建的镜像只有编译的目标文件(二进制文件或jar包)有用,其它的都无用
  • 第二阶段直接从第一阶段拷贝目标文件

image

image

使用合适的基础镜像

基础镜像,推荐使用 Alpine。Alpine 是一个高度精简又包含了基本工具的轻量级 Linux 发行版,基础镜像只有 4.41M,各开发语言和框架都有基于 Alpine 制作的基础镜像,强烈推荐使用它。进阶可以尝试使用scratch和busybox镜像进行基础镜像的构建

镜像区别

  • scratch 是一个空镜像,只能用于构建其他镜像,比如你要运行一个包含所有依赖的二进制文件,如 Golang 程序,可以直接使用 scratch 作为基础镜像。
  • BusyBox 是一个集成了一百多个最常用 Linux 命令和工具(如 cat、echo、grep、mount、telnet 等)的精简工具箱,它只需要几百KB 的大小,很方便进行各种快速验证,被誉为 Linux 系统的瑞士军刀
  • Alpine 采用了 musl libc 和 busybox 以减小系统的体积和运行时资源消耗,但功能上比 busybox 又完善的多,Alpine 还提供了自己的包管理工具 apk,可以通过 packages网站上查询包信息,也可以直接通过 apk 命令直接查询和安装各种软件
  • Alpine Docker 镜像也继承了 Alpine Linux 发行版的这些优势。相比于其他 Docker 镜像,它的容量非常小,仅仅只有 5 MB 左右(对比 Ubuntu 系列镜像接近 200MB),且拥有非常友好的包管理机制