一、镜像基础概念:从零理解容器镜像
说实话,我刚接触容器技术那会儿,也被「镜像」这个词搞得有点懵。什么镜像不镜像的,不就是个文件包吗?后来踩了不少坑才明白——镜像远不止是个安装包那么简单。
今天咱们就把镜像这个概念彻底掰开揉碎。我保证,看完这一章,你对镜像的理解会比大多数人都深。
1.1 什么是镜像
镜像,说白了就是一个只读的、分层存储的模板文件。它包含了运行某个应用所需的一切:代码、运行时环境、系统库、环境变量、配置文件……几乎就差一个操作系统内核了。
你可以把镜像想象成一个「快照」——把某个时刻的完整文件系统拍下来,存成一份不可修改的静态文件。我习惯把它比作光盘:你拿到一张光盘,可以反复用它启动系统,但没法直接修改光盘里的内容。
核心定义:镜像是容器的静态模板,容器是镜像的运行实例。没有镜像,就没有容器。
1.2 镜像与容器的关系
很多新手会混淆镜像和容器。我当年也犯过这个错——以为 docker run 就是直接跑了个镜像。其实不是。
它们的关系,用一句话就能说清:
- 镜像是「类」(Class)——定义好了结构,但还没实例化
- 容器是「对象」(Object)——镜像的一个具体运行实例
举个例子:
# 查看本地镜像
docker images
# 从镜像启动一个容器
docker run -it ubuntu:22.04 /bin/bash
# 查看运行中的容器
docker ps
同一个镜像可以启动多个容器,每个容器都有自己的读写层。你改了一个容器里的文件,其他容器完全不受影响。这就是「镜像只读、容器可写」的精髓。
我的经验:我曾经在生产环境同时用同一个 Nginx 镜像启动了 50 个容器做负载均衡。每个容器只改了配置文件,镜像本身没动。这种「一次构建,到处运行」的能力,就是镜像最大的价值。
1.3 镜像分层原理(UnionFS)
这是镜像最核心、也最容易被忽略的部分。为什么镜像能那么小?为什么拉取镜像时可以复用已有层?答案都在 UnionFS 里。
UnionFS(联合文件系统)是一种将多个目录挂载到同一个挂载点的文件系统技术。Docker 用它把镜像的每一层叠加起来,对外呈现为一个完整的文件系统。
来看一个典型的镜像分层结构:
+---------------------------+
| 容器读写层(可写) | ← 容器启动后创建
+---------------------------+
| 镜像层 3:CMD/ENTRYPOINT | ← 应用配置
+---------------------------+
| 镜像层 2:应用代码 | ← 你的业务代码
+---------------------------+
| 镜像层 1:依赖库 | ← apt install 的包
+---------------------------+
| 基础层:Ubuntu 22.04 | ← 操作系统基础
+---------------------------+
↓ 叠加后
容器看到的完整文件系统
每一层都是只读的。容器运行时,UnionFS 把各层「叠」在一起,最上面加一个可写层。你修改文件时,实际上是在可写层做了一份拷贝(Copy-on-Write)。
避坑指南:我曾经在 Dockerfile 里写了大量 RUN 命令,每一条都生成一个新层。结果镜像体积飙到 2GB。后来我把多个 RUN 合并成一条,用 && 连接,体积直接降到 800MB。记住:层数越少,镜像越小。
常见的 UnionFS 实现有:
| 实现 | 特点 | 适用场景 |
|---|---|---|
| OverlayFS | 性能好,内核原生支持 | Docker 默认驱动(Linux) |
| AUFS | 稳定,但未合入内核主线 | 早期 Docker 默认驱动 |
| Device Mapper | 基于块设备,性能一般 | CentOS/RHEL 默认 |
| ZFS | 支持快照、压缩 | 需要高级存储特性时 |
现在 Docker 在 Linux 上默认用 OverlayFS。你可以在宿主机上查看镜像层的实际存储:
# 查看镜像的层信息
docker image inspect ubuntu:22.04 | jq '.[].RootFS'
# 查看 OverlayFS 挂载点
mount | grep overlay
输出大概长这样:
{
"Type": "layers",
"Layers": [
"sha256:2dc39ba059dcd42ade30aae30147b5692777ba9ff0779a62ad93a74de02e3e1f",
"sha256:5b3b991d6d3e3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f"
]
}
每一层对应一个 SHA256 哈希值。Docker 用这些哈希值做缓存——如果你拉取的镜像某层已经存在,就直接复用,不用重新下载。
1.4 镜像仓库介绍
镜像做好了,总得有个地方存吧?这就是镜像仓库干的事。
镜像仓库(Registry)是存储和分发镜像的服务。最常用的当然是 Docker Hub,但生产环境我建议你用私有仓库。
仓库的命名规则很简单:
[仓库地址]/[命名空间]/[镜像名]:[标签]
# 例子
docker.io/library/nginx:1.25
myregistry.example.com/myteam/myapp:v2.0.1
常见的镜像仓库:
| 仓库 | 类型 | 特点 |
|---|---|---|
| Docker Hub | 公共 | 官方镜像多,但拉取有限速 |
| Harbor | 私有 | 企业级,支持 RBAC、漏洞扫描 |
| GitHub Container Registry | 公共/私有 | 与 GitHub 集成好 |
| Amazon ECR | 私有 | AWS 生态,安全 |
| Azure Container Registry | 私有 | Azure 生态,支持地理复制 |
我的建议:个人项目用 Docker Hub 没问题。但公司项目,一定要搭私有仓库。我见过不止一次因为 Docker Hub 限速导致 CI/CD 流水线卡死的惨案。Harbor 是我用得最多的,功能全、社区活跃。
拉取和推送镜像的基本操作:
# 登录仓库
docker login myregistry.example.com
# 拉取镜像
docker pull myregistry.example.com/myapp:latest
# 给镜像打标签
docker tag myapp:latest myregistry.example.com/myapp:v1.0
# 推送镜像
docker push myregistry.example.com/myapp:v1.0
1.5 本章知识体系总览
为了让你对本章内容有个整体印象,我画了一张结构图:
嗯,这张图基本把本章的知识点串起来了。你仔细看看,每个分支之间其实都有联系——比如分层原理直接影响了镜像仓库的存储效率,镜像和容器的关系又决定了你如何设计 CI/CD 流程。
好了,镜像基础就聊到这儿。记住一句话:镜像就是容器的蓝图,分层是镜像的灵魂,仓库是镜像的家。后面几章,咱们会一步步深入镜像的加载、校验和分发实战。到时候你会发现,今天打下的基础,全都能用上。