如何 build 出尽可能小的 docker image?
1. 简介
我们自己构建 Docker image 时都希望结果镜像越小越好,那么如何才能尽量变小呢?
下面我们通过一个简单的案例,了解下优化思路,看一个简单的镜像如何从 743MB 减到 536MB
2. 案例
目标
非常简单,build 一个 java 镜像
基于 centos 基础镜像,和下载好的 jdk-8u101-linux-x64.tar.gz
过程
1)第一版
Dockerfile 内容:
FROM centos
COPY jdk-8u101-linux-x64.tar.gz /usr/local/
WORKDIR /usr/local
RUN tar -zxf /usr/local/jdk-8u101-linux-x64.tar.gz && rm /usr/local/jdk-8u101-linux-x64.tar.gz
ENV JAVA_HOME /usr/local/jdk1.8.0_101
ENV PATH $JAVA_HOME/bin:$PATH
build 后镜像大小为:743 MB
centos 基础镜像的大小是 197MB,jdk 压缩包是 173MB,而最终结果 743MB 有点太大了
这个 Dockerfile 的问题在于:虽然 tar 解压之后立即使用 rm 删除了压缩包,但与上面的 COPY 不在一层,所以删了也无法减小最终的体积
2)第二版
为了解决上一版中压缩包的问题,可以使用 ADD
命令,直接把解压后的内容放入镜像,而不是把压缩包放入进行内再解压
FROM centos
ADD jdk-8u101-linux-x64.tar.gz /usr/local/
ENV JAVA_HOME /usr/local/jdk1.8.0_101
ENV PATH $JAVA_HOME/bin:$PATH
build 后镜像大小为:562 MB,少了压缩包的大小,这样就好了很多
如果压缩包不在本地,需要通过网络下载,然后解压,那么就要让 下载、解压、删除 这3个动作在同一层操作,例如:
RUN wget http://xxx.com/app && tar -xzf app.tar.gz && rm app.tar.gz
这样就可以避免压缩包占用镜像空间
3)第三版
上一版已经很简洁了,还有什么可以优化的呢?
我们先在本地把 jdk-8u101-linux-x64.tar.gz 解压看一下
解压后目录是 jdk1.8.0_101,大小 352M
进入目录可以看到 javafx-src.zip(4.9M) 和 src.zip(21M) 这两个源码包文件,我们在实际环境中可能并不需要他们,那么拷贝到镜像中就比较多余了
Docker 有一个过滤功能,可以让我们排除掉不需要的文件,方法是编写 .dockerignore
文件
Dockerfile内容:
FROM centos
COPY jdk1.8.0_101 /usr/local/jdk1.8.0_101
ENV JAVA_HOME /usr/local/jdk1.8.0_101
ENV PATH $JAVA_HOME/bin:$PATH
.dockerignore内容:
*/*.zip
位置关系:
├── .dockerignore
├── Dockerfile
└── jdk-8u101-linux-x64.tar.gz
build 后镜像大小为:536 MB,比第一版的 743MB 少了 207MB
.dockerignore
的作用很大,一般的开源项目包中会有源码、文档,例如 hadoop 包中的 doc
就有 96MB,还有我们自己项目中也可能会有不必要的文件,例如 .git
、.svn
、tmp
等等,都可以通过 .dockerignore
进行排除
3. 小结
上面的案例中,优化的思路就是尽量少往镜像里放东西
还有一点比较重要,一定要选用最合适的基础镜像,例如:
- 可以看看只有 5MB 的 alpine 镜像是否能满足自己的需求,ubuntu、centos 这些基础镜像都是100多MB的,如果能使用 alpine 那么就减小了很多
- 通常公司内会构建自己的基础镜像,例如构建一个
Ruby+Rails+...
通用镜像,需要其他镜像时就以此为基础,但如果有的 service 只需要 Ruby 而不要 Rails 等其他东西,这时就多余了,有精力的话可以把基础镜像拆分得更细一些
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 别再问我Python怎么操作Word了!
- Vaex :突破pandas,快速分析100GB大数据集
- 【适合收藏】为了多点时间陪女朋友,我向BAT大佬跪求了这15条JS技巧
- Flask框架教程汇总(1)---视图/重定向/传参
- OkHttp请求耗时统计
- Ubuntu 18.04 通过 Docker 快速部署 Smokeping 2.6.11 教程
- MySQL 8.0新特性 — 函数索引
- Docker快速上手指北(一)【技术创作101训练营】
- leetcode树之二叉搜索树的最近公共祖先
- 【技术创作101训练营】技术角 | 在CentOS 8上使用Nginx 1.18: 基本配置
- Java诊断应用之Arthas实战(技术创作101训练营)
- 突击并发编程JUC系列-数组类型AtomicLongArray
- 个人量化投资体系搭建(一)
- 服务端的 WebAssembly 与 Rust 入门篇
- pImpl