一、镜像概念与基础

什么是镜像

镜像,说白了就是一个「静态的、只读的文件包」。它打包了运行某个应用所需的一切——代码、运行时、系统库、环境变量、配置文件等等。

我经常跟新人这么解释:镜像就是应用的「快递包裹」。你收到包裹,拆开就能用,不用自己去买零件组装。镜像也一样,拉下来就能跑,不用操心依赖环境。

举个例子,一个 Nginx 镜像里有什么?

  • 一个精简的 Linux 文件系统(比如 Alpine)
  • Nginx 二进制文件
  • 默认的配置文件
  • 日志目录、PID 文件等运行时需要的结构

你运行这个镜像,它就变成一个 Nginx 容器。嗯,就这么简单。

核心要点:镜像是只读的,永远不会被修改。你改不了镜像本身,只能基于它创建容器,在容器层做变更。

镜像与容器的关系

很多人搞不清镜像和容器的区别。我习惯用一个类比:镜像 = 类,容器 = 实例。

你想想看,在面向对象编程里,类定义了属性和方法,但真正干活的是实例。镜像定义了文件系统和启动参数,但真正运行的是容器。

具体来说:

  • 镜像:静态的、分层的、只读的模板
  • 容器:动态的、可写的、运行中的实例

一个镜像可以启动多个容器,每个容器互不干扰。我在项目中经常这么用:同一个应用镜像,启动三个容器做负载均衡。它们共享镜像层,但各自有独立的可写层。

小技巧:用 docker image ls 看镜像列表,用 docker ps 看运行中的容器。镜像占磁盘,容器占内存和 CPU。

镜像的层级结构(Layer)

这是 Docker 最巧妙的设计之一。镜像不是一个大文件,而是由多个只读层堆叠而成。

每一层对应 Dockerfile 里的一条指令。比如:

FROM ubuntu:22.04          # 第1层:基础层
RUN apt-get update         # 第2层:更新包列表
RUN apt-get install -y nginx  # 第3层:安装 Nginx
COPY index.html /var/www/  # 第4层:复制文件
CMD ["nginx", "-g", "daemon off;"]  # 元数据,不算层

这个镜像就有 4 层。每一层都是只读的,并且带有唯一的哈希值。Docker 用这个哈希值做缓存——如果某层没变,直接复用。

我曾经踩过一个坑:Dockerfile 里把 COPY 写在了 RUN apt-get 前面。结果每次改代码,整个 apt-get 层都要重跑。后来我把不变的层放前面,变的放后面,构建速度快了 3 倍。

注意:层的数量不是越多越好。每层都会占用额外元数据空间。我建议控制在 10 层以内,太深了反而影响性能。

UnionFS(联合文件系统)原理

UnionFS 是镜像分层技术的底层支撑。它的核心思想是:把多个目录「联合」挂载成一个虚拟文件系统。

你想想看,镜像的每一层都是一个目录。UnionFS 把它们叠在一起,对外呈现为一个完整的文件系统。如果上层有文件,就覆盖下层的同名文件——这叫「写时复制」。

Docker 默认用的是 OverlayFS,它是 UnionFS 的一种现代实现。结构大概是这样的:

/var/lib/docker/overlay2/
├── l/                    # 符号链接层
├── 层1哈希/              # 基础层
├── 层2哈希/              # 上层
├── 层3哈希/              # 再上层
└── merged/               # 联合挂载后的视图

当容器运行时,Docker 会在所有只读层之上再加一个可写层。容器里所有的写操作——创建文件、修改配置、写日志——都发生在这个可写层。

这就是为什么容器停止后,数据会丢失。可写层随着容器销毁而消失。我建议你把持久化数据挂载到宿主机目录或 volume 里,别指望容器自己的可写层。

一句话总结:镜像层是只读的、共享的;容器层是可写的、临时的。UnionFS 把它们粘在一起,让你感觉就像在操作一个普通文件系统。

核心逻辑图

下面这张 SVG 图展示了镜像分层和 UnionFS 的核心逻辑:

镜像分层与 UnionFS 原理 镜像(只读层) Layer 4: COPY index.html 哈希: a1b2c3d4 Layer 3: RUN apt-get install 哈希: e5f6g7h8 Layer 2: RUN apt-get update 哈希: i9j0k1l2 Layer 1: FROM ubuntu:22.04 哈希: m3n4o5p6 UnionFS 容器(可写层 + 镜像) 可写层(容器运行时) 写操作、日志、临时文件 Layer 4: COPY index.html Layer 3: RUN apt-get install Layer 2: RUN apt-get update Layer 1: FROM ubuntu:22.04

这张图左边是镜像的 4 个只读层,右边是容器运行时的视图——可写层在最上面,下面叠着镜像的所有只读层。UnionFS 把它们合并成一个完整的文件系统,容器里的进程根本感知不到分层的存在。

我的经验:用 docker history 镜像名 可以查看镜像的每一层和对应指令。这是排查镜像体积问题的利器——哪层最大,一目了然。
可可精品资料库