本文介绍一种docker image瘦身的技巧,它基于Chiselled Ubuntu来实现,希望能帮助大家将自己的docker image体积缩小约50%(或者至少上百MB)。
Chiselled Ubuntu 容器是一个生产就绪的、安全的超小型容器镜像,侧重于效率和安全性。这些容器镜像允许用户构建的镜像仅包含其应用程序及运行时依赖,而不包含不必要的操作系统级包、实用程序或库。另外,Canonical 还承诺提供安全维护与支持。Chiselled Ubuntu 容器也是旨在缩小容器基础镜像。它也带来了同样的好处,比如最小化依赖,减少膨胀和资源使用,加快启动速度,并通过减少镜像中不需要的文件来增强安全性。
以Java为例介绍Docker image瘦身效果
我在以前通过Docker 镜像瘦身技巧介绍了一些常见后端语言编写的程序的整体镜像瘦身技术点,并通过Docker 镜像 OpenJDK 基础镜像选型详细列举了Java语言具体的基础镜像选择的最佳实践,但是一般来讲Java程序相关的Docker image体积至少都是200MB。那么我们是否可以进一步的降低image体积呢?我们可以尝试下Chiselled Ubuntu 容器镜像,验证是否如ubuntu的宣称的这样。
我们可以使用任意的java程序进行测试。我这里使用spring boot web最小项目制作的jar包,将这个jar文件放置到image里面制作成一个可运行的image。其dockerfile为:
Dockerfile
# 使用ubuntu/jre,它已经是chiselled版本: https://hub.docker.com/r/ubuntu/jre
FROM ubuntu/jre:17-22.04_39
# 指定环境变量
ENV APP_NAME=demo
ENV PORT=8080
# 标签
LABEL "AUTHOR"="ThinkTik"
LABEL "MAIL_ADDRESS"="thinktik@outlook.com"
LABEL "DOMAIN_NAME"="omoz.cc"
LABEL "SERVICE_PATH"="${APP_NAME}"
# 切换工作目录
WORKDIR /home/app
# 将当前项目的可执行文件添加到工作目录:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#add-or-copy
ADD ./build/libs/${APP_NAME}-*.jar ./app.jar
# This image does not include bash nor a package manager nor the OpenJDK. Its purpose is to serve as a runtime, final-stage base image for compatible Java applications.
# 因为chiselled版本的jre没有bash、包管理器、OpenJDK以及其他的Linux工具组件。所以没有我们常见的mkdir、curl、useradd等几乎全部的Linux命令!也没有/tmp目录!
# 所以当java程序需要读写/tmp文件夹时,我们只能提前通过其他方式绕过去
ADD ./README.md /tmp/README.md
# 暴露的端口号
EXPOSE ${PORT}
# 使用该镜像来启动容器,指定启动命令
# 因为ubuntu/jre base image里面已经自带了ENTRYPOINT(目的是指定java命令的具体路径),这时我们只需要通过CMD来补充启动参数即可:https://docs.docker.com/engine/reference/builder/#dockerfile-reference
CMD ["-jar","./app.jar"]
作为对比我们使用amazon corretto jdk作为base image的话,做好Docker 镜像瘦身技巧和Docker 镜像 OpenJDK 基础镜像选型后,其dockerfile为:
Dockerfile
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
# https://docs.aws.amazon.com/AmazonECS/latest/bestpracticesguide/application.html
# 使用JDK21
FROM amazoncorretto:21-al2023-headless
# 指定环境变量
ENV USER=omoz
ENV HOME_PATH=/home/${USER}
ENV LOG_PATH=${HOME_PATH}/logs
ENV APP_NAME=demo
ENV PORT=8080
# 标签
LABEL "AUTHOR"="ThinkTik"
LABEL "MAIL_ADDRESS"="thinktik@outlook.com"
LABEL "DOMAIN_NAME"="omoz.cc"
LABEL "SERVICE_PATH"="${APP_NAME}"
# 创建omoz-developers用户组,创建用户omoz(自动创建home目录)并加入omoz-developers用户组
RUN dnf update -y && dnf install shadow-utils procps-ng -y
RUN groupadd -r ${USER}-developers && useradd -r -m -g ${USER}-developers ${USER}
# 切换用户(以非root用户启动服务)
USER ${USER}
# 指定工作目录
WORKDIR ${HOME_PATH}
# 将当前项目的可执行文件添加到工作目录
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#add-or-copy
ADD ./build/libs/${APP_NAME}-*.jar ${HOME_PATH}/app.jar
# 周期性健康检查
# https://docs.docker.com/engine/reference/builder/#healthcheck
HEALTHCHECK --start-period=180s --interval=60s --timeout=8s --retries=3 CMD curl -s -f -k https://localhost:${PORT}/ping || exit 1
# 暴露的端口号
EXPOSE ${PORT}
# 使用该镜像来启动容器,指定启动命令
ENTRYPOINT ["java","-jar","app.jar"]
我分别使用上面2种dockerfile制作了2个版本的docker image并都做了最大程度的体积优化,最后的结果是两者对比之下,就我的简单测试而言Chiselled Ubuntu 容器作为base image可以把java docker image体积缩小为原先的25%左右或者至少100MB,达到了官方所宣传的效果!!!
总结
Chiselled Ubuntu 容器的适用场景和建议
- 目前直接支持的解释型语言还不是太多,主要是python,java,dotnet-runtime
- 你可以自己为一些不直接支持的解释型语言制作Chiselled版image,但是技术要求比较高,这里不详细展开
- 编译型语言比如go,rust可以直接使用debian base image 和ubuntu base image
由于Chiselled Ubuntu删除了除运行所必须的组件外的其他非必需组件,所以达到了更好的瘦身效果,但是这也对一些我们在容器运行中的debug、linux命令行的使用有明显的影响。
- 建议搭配分布式的日志服务来接收程序产生的日志而不是使用本地卷。如前面讲到的,有些非必要的目录可能都没有,比如/tmp文件夹
- 建议在image发布到正式环境前做好详细的功能测试和验证(因为一旦需要debug,在bash都没有的情况下,很难docker exec进入到容器)
总之,相比alpine linux,Chiselled Ubuntu是标准的Linux,兼容性更好、适用范围更大并且体积有时还更低,因为大多数Linux发行版都使用GNU版本的标准C库(glibc),但Alpine Linux使用的是musl,那些二进制安装包是针对glibc编译的;相比传统的其他方案,Chiselled Ubuntu的体积明显有优势。
Docker image 瘦身的好处
- 节约网络和磁盘空间:更小体积的image会极大的降低服务器拉取image时的网络带宽开销和时间开销,同时也会占用服务器更少的本地磁盘空间。
- 更快的弹性速度:更快的image拉取速度和更快的image体积可以让containerd更快的完成image读取任务,更快的启动容器。
- 更安全:只包含需要的组件。全部的不需要的Linux组件和全部的不需要的依赖被正确的删除,这完全的消除这些Linux组件带来的的漏洞,这带来了更好的软件纯粹性和软件安全性。
- 更少的CPU和内存开销:只运行必须的组件和应用程序,其他不必要的组件因为被删除了所有不需要运行,不会占用资源。