优化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
对比 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 .
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 .
指令合并的方法是通过在同一层中将缓存和不用的工具软件清理掉,以达到减小镜像体积的目的。
多阶段构建
多阶段构建方法是官方打包镜像的最佳实践,它是将精简层数做到极致的方法。通俗点讲它是将打包镜像分成两个阶段,一个阶段用于开发,打包,该阶段包含构建应用程序所需的所有内容;一个用于生产运行,该阶段只包含你的应用程序以及运行它所需的内容。多阶段构建的想法很简单:“我不想在最终的镜像中包含一堆 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包)有用,其它的都无用
- 第二阶段直接从第一阶段拷贝目标文件
使用合适的基础镜像
基础镜像,推荐使用 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),且拥有非常友好的包管理机制