Docker镜像优化打包速度思考

前言

image.png

当下主流的业务架构大部分会选择用容器进行部署,并结合一些容器编排技术k8s技术。由于公司业务调整,jenkins打包的工作交付到我这边来负责。

结果组长上来就给我一个当头一棒,打包速度太慢,尽量优化下。我寻思过年也没忘记给拜年啊,不行把送出去的特产要回来吧,没啥用啊,净给整这一出。

说归说闹归闹,别拿绩效开玩笑,毕竟这年终还没发呢,该忍还得忍。君让臣死,臣不得不死,硬着头皮上吧

压缩镜像大小

优化第一步,瘦身,docker images查看了下基础服务的镜像大小,好家伙没有小于300M的,一个普通的小服务都要300多,显然在镜像体积上可以做做文章。

在Docker官方的建议中有一个点,使用更小的基础镜像。而在小的基础镜像中全网的一致答案都是alpine了。

alpine是一个非常特别的Linux版本,大小才5M左右,最早适用于嵌入式系统,但随着容器的流行,这个才5M大小的Linux在Docker中流行起来了,因为太小了,非常节省空间。

这里就对比下同样属于Linux镜像centeros与alpine的大小

REPOSITORY TAG IMAGE ID CREATED SIZE
alpine 3.16.2 9c6f07244728 7 days ago 5.54MB
centeros latest df5de72bdb3b 2 weeks ago 77.8MB

解决完基础镜像再来研究下第三方包是不是有可优化的点,仔细分析Dockerfile发现在构建过程中去拉代码打包的,那么构建过程中就会存在大量运行时不需要的包。这部分清掉基本上就可以瘦身成功了。

构建过程中,通过apk安装软件包时,可以指定虚拟包.build-deps,这样git之类的工具归属到虚拟包下,由于仅是构建阶段用到的命令,如执行npm installpip install,当把项目构建完以后,通过apk del .build-deps清理掉所有临时命令即可。

这样就可以保证构建出的镜像最小了。

利用缓存加速打包速度

docker本身是有缓存机制的,也就是每次build的时候会检查Dockerfile是否发生了变更,这里要注意是Dockerfile发生了变更,并不是代码发生变更。

得到这个结论的时候其实我是很迷惑的,照这么来说的话,Dockefile基本不会变,每次走缓存为什么jenkins打包速度这么慢呢。

后来在build命令里发现了端倪,有人在docker build时加了--no-cache参数,因为代码是在镜像构建时拉取的,每次走了缓存那么代码就不会拉取了,每次都会走缓存。所以为了避免代码不对运维在命令里加了--no-cache

那怎么办呢,因为Dockefile执行从上到下依次执行。把代码放到最低端,通过定义随机数或时间戳的方式使拉代码的命令缓存失效而不影响上面的第三方包的安装。

42ce9b8428f6effa08aa41798c5e798.jpg

岂不美哉。

Multi-stage Build

Docker 提供了 Multi-stage Build(多阶段构建),可以实现镜像瘦身。

我们将镜像构建分成两个阶段:

在 ”build“ 阶段依然采用 JDK 作为基础镜像,并利用 Maven 进行应用构建; 在最终发布的镜像中,我们会采用 JRE 版本作为基础镜像,并从”build“ 镜像中直接拷贝出生成的 jar 文件。这意味着在最终发布的镜像中,只包含运行时所需必要内容,不包含任何编译时依赖,大大减少了镜像体积。

FROM adoptopenjdk/openjdk8 AS build
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/' /etc/apt/sources.list
RUN apt-get update
RUN apt-get install -y \
git \
maven
WORKDIR /tmp
RUN git clone https://github.com/spring-projects/spring-petclinic.git
WORKDIR /tmp/spring-petclinic
RUN mvn install
FROM adoptopenjdk/openjdk8:jre8u222-b10-alpine-jre
COPY --from=build /tmp/spring-petclinic/target/spring-petclinic-2.1.0.BUILD-SNAPSHOT.jar spring-petclinic-2.1.0.BUILD-SNAPSHOT.jar
CMD ["java","-jar","spring-petclinic-2.1.0.BUILD-SNAPSHOT.jar"]

Java打包测试

springboot背景下,在默认的maven打包插件加入分层打包配置

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <layers>
                        <enabled>true</enabled>
                    </layers>
                </configuration>
            </plugin>
        </plugins>
    </build>

再次进行打包分析操作mvn clean package,现在我们可以用下面命令来看分层打包编译的jar包结构 java -Djarmode=layertools -jar target/dockers-demo-0.0.1-SNAPSHOT.jar list

图片

可以看到layertools识别出jar包内将依赖打包到不同文件夹中,接下来我们改造下原有的dockerfile。

如果不分层打包的话,一次全量包会特别大。如果只更改部分代码的话。

FROM openjdk:8 as builder
RUN mvn clean package -DskipTests
# 声明端口并没有真正运行在这个端口
EXPOSE 8080
ADD ./target/*.jar ./app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM openjdk:8-jre
MAINTAINER ttzommed@foxmail.com
WORKDIR application
# 复制第三方依赖、SpringBoot内部配置、快照依赖
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

依靠docker的分层特征,分次加入文件即可达到分层加速打包的效果

作者:在下uptown

%s 个评论

要回复文章请先登录注册