镜像是 Docker 运维的基本单元。 优化镜像体积,能够:

  1. 缩短部署时的下载时间;
  2. 提升安全性,因为可供攻击的目标更少;
  3. 减少故障恢复时间;
  4. 节省存储开销。

正确认识分层和共享

认清与理解 Docker 镜像的层次结构,是进行镜像优化的前提和基础。

分层

docker 镜像的存储结构是分层次的。 无论底层的文件系统(可选可配置)是基于快照还是分块。

镜像构建的过程里,每步操作都产生一个只读的层:可重用,可复制,但是不可修改。 层次叠加,形成了包含历史(history)结构的镜像。 镜像启动后,产生了容器(container)。容器也是一个单独的层,可读写,但是不持久化,易丢失。

共享

共享,以分层为基础。 以一个镜像为祖先,分别用多种方式做不同操作,各自生成新层,则形成多个新的镜像,祖先镜像则成为共享的部分。 继承自共同的祖先,实现了对共同内容的利用。

层次化存储结构

镜像优化

对单个镜像体积做优化,采取方式有两类思路:

  1. 选择尽可能小的基础镜像
  2. 打包尽可能少的内容入镜像
    • 如:去除或减少编译、测试等中间步骤内容
    • 使用单行命令

工程实践中,还要考虑多镜像复用,尽量:

  1. 将能复用的部分放进模版镜像。

选择最小化基础镜像,创建模版镜像

基础镜像需要满足的基本条件:

  • 正确的 init 系统。(discuss
  • 日志。Docker 的应用程序日志一般默认在标准输出,而系统进程仍然会往 /dev/log 写数据。需要有日志处理程序 syslog
  • 后台任务如 cron
  • 工具进程如 sshd(慎重选择)

这些需求,与在传统的服务器和虚拟机上做部署,是相似的。

如果仅仅基于这种相似性,就选择了使用 CentOS/Debian/Ubuntu 做为基础镜像,那么就有问题了。 首先,这些传统发行版的 Docker 镜像并不能符合基础需求; 其次,这些发行版的体积太大。

所有,仍然需要选择较小的发行版制作初始镜像

市面上可选的有:

使用共同的模版镜像

如果有多个业务需要运维,则在公共基础镜像基础上,构建私有的模版镜像。 共同的模版镜像,能够:

  • 实现镜像共享
  • 减少重复工作。解决边缘问题
  • 减少开发时间。专注于上层应用
  • 减少编译时间。
  • 减少部署时间。基于镜像的层共享

模版镜像的目标是抽取业务的公共部分。

持续的审查和优化

上线后,需要经常关注这些问题:

  • 某个组件放在模版镜像中,现在已经很少用到了?或者反之。
  • 新业务使用到某个公共镜像,效果还不错,要不要推而广之?

解决这些问题,还需要经常复审镜像服用的效率。 简单的方案,可以对各类环境中的模版使用情况进行统计,尽量合并相似的镜像,将使用率较高的组件吸收进模版镜像。

常用考评指标:

  • 开发效率
  • 编译效率
  • 传输效率
  • 存储效率

要不要使用 --flatten

--flatten 是现在还是一个测试特性,可以减少镜像总体积,然而与复用的原则有冲突。 需要更加实际情况去做考虑。

为什么选择最小的基础镜像(如alpine)之后,应用镜像反而变大了?

这是因为应用程序需要一系列依赖。 如果使用最小的基础镜像,在制作应用镜像时,比如需要去安装依赖。 安装步骤可能会引入更多不必要的元素。 所以反而体积会超过已包含所需依赖的公共镜像。

centos -> app

alpine -> dependencies -> app  # Too much dependencies