docker镜像的优化

一、时间优化

时间优化,就是优化 Docker 镜像构建速度,是优化构建的时间。从实际情况而言,镜像时间的优化可能更重要些,因为Devops 流水线中最多的问题也是镜像构建的时间,如果构建时间长,那就会影响功能的部署,从而影响交付的时间。

1、网络优化

优化网络主要是为了让镜像下载或者依赖的安装、代码下载在网络通道上更加通畅。一般就是调整构建机器网络质量、配置就近的加速仓库地址等等。

2、构建缓存

  • 本地缓存

    Docker 构建时会下载基础镜像文件,并进行缓存,所以构建缓存优化时,为了减少镜像的传输下载时间,建议使用固定的机器来专门进行镜像的构建。

  • 镜像分层缓存

    Docker 的一大特色就是镜像的存储分层,在 dockerfile 中的每一个指令会对应到镜像的每一层,并且默认启用缓存,所以构建时每一层是否会缓存取决于三个关键因素。

    • 镜像父层没有发生变化
    • 构建指令不变
    • 添加文件校验和一致

    构建指令满足这三个条件,这一层镜像构建就不会再执行,而是直接利用之前构建的结果,也就是会利用缓存。

LABEL maintainer

COPY app/ /app

EXPOSE 8080

CMD ["/usr/bin/java", "-cp", "/app", "org.springframework.boot.loader.JarLauncher"]

针对这个特性,可以优化上例中的 dockerfile ,将 COPY app/ /app 指令做一个拆解,将不变化文件(如固定不变的依赖库、固定不变的静态文件或下载文件等)单独摘出来作为一个独立指令,拆解为如下:

WORKDIR /app 

COPY target/BOOT-INF/lib ./lib

COPY target/org/ ./org 

COPY target/META-INF/ ./META-INF

COPY target/BOOT-INF/classes ./classes

3、编写.dockerignore

编写.dockerignore文件用于过滤构建过程中不必要的文件,或者创建单独的目录(如 .git 文件夹,markdown 文档等),从而减小目标镜像大小,加快构造速度。

二、空间优化

空间优化,也就是优化 Docker 镜像体积(也称呼为“镜像瘦身”)。

1、镜像的体积优化

为了优化 Docker 体积,通常建议使用 Alpine 类型的版本,因为 Alpine 镜像和类似的其他镜像都经过了优化,其中仅包含最少、必须的软件包,所以它能够节省很多体积。

但是在使用Alpine 镜像时,最好评估好 Alpine 的弊端和风险。如果一定需要使用 Alpine 镜像,最好的方式是先使用 Alpine 镜像做基础镜像,在项目直接使用 Alpine 镜像之前进行一次初始化,然后再用初始化后的版本作为通用的基础镜像

因为Alpine镜像体积小,其安装的依赖也少,所以会存在各种问题。如下:

  • 使用 Alpine 镜像程序容易报错

    Alpine 为了追求精简,很多依赖库都没有,需要一些依赖动态链接库的程序运行时就容易报错,比如 Go 的 cgo 调用。

  • 域名解析行为跟 glibc 有差异

    Alpine 镜像的底层库是 musl libc,域名解析行为跟标准 glibc 有差异,需要特殊作一些修复配置,并且有部分选项在 resolv.conf 中配置不支持。

  • 运行 bash 脚本不兼容

    因为没有内置 bash,所以运行 bash 的 shell 脚本会不兼容。

  • 使用 Alpine 镜像还会导致时区不一致、无法通过 lxcfs 提升容器资源可见性等问题。

2、规划镜像的层数

在编写 dockerfile 时,根据实际情况去合并一些指令,尽量减少镜像层级,以此来优化体积。

在 dockerfile 中每执行一条指令,就会提交一次修改,这次修改会保存成一个只读层挂载到联合文件系统,上面层的文件如果和下面层有冲突或不同,会覆盖隐藏底层的文件,所以每增加一层,镜像大小就会增加,但是在 Docker1.10 后有所改变,只有 RUN、COPY、ADD 指令会创建层,其他指令会创建临时的中间镜像,不会直接增加构建的镜像大小 。

3、多阶段构建镜像

对于镜像优化可以换一个角度去思考,将 dockerfile 的构建配置环境分为软件的编译环境运行环境,这种方式就是多阶段构建思路。

在 dockerfile 中使用多个 FROM 语句,每个 FROM 指令都可以使用不同的基础镜像,并且配置出独立的子构建阶段,达到构建安全、构建速度快并且镜像文件体积小的目的。

比如:构建一个 Java 程序的 Docker 镜像所配置的 dockerfile

\# 第一阶段 编译基础环境
FROM maven:3.6.3-openjdk-8 as builder

\# 打包pom.xml、src源代码
WORKDIR /usr/src/test

COPY ./ /usr/src/test

RUN [ "mvn" , "clean" , "install" ]

\# 编译
RUN mvn clean package



\# 第二阶段构建,运行程序
FROM openjdk:8

COPY --from=builder /usr/src/test/target /usr/src/test

WORKDIR /usr/src/test

CMD ["java", "-jar" , "app-v1.jar"]

可以看到如上这个 dockerfile 中使用了二阶段构建。

  • 第一阶段:选择 Maven 基础镜像
  • 第二阶段:拷贝第一阶段生成的 Jar 包到 OpenJDK 镜像中,运行服务

最终构建的镜像将不包含任何源代码信息,这样保障了数据安全性,因为构建的镜像仅包含基础镜像和编译制品 app-v1.jar,所以体积也会减少。假如项目还有制作多系统架构的 Docker 镜像等需求,就可以结合启用 BuildKit 等方式做到多阶段镜像并发执行多个构建流程,从而缩短构建消耗时间。

posted @ 2022-07-22 16:49  xyztank  阅读(585)  评论(0编辑  收藏  举报