ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Docker 镜像(Image)

Docker 镜像(Image) 从 Layer 到 Registry理解镜像的构建、存储与分发本文是对 Docker Image 机制的一次系统学习总结。不只是介绍docker images、docker pull这些命令而是从一个实际问题出发逐步理解 Docker Image 为什么存在、为什么要分层、Layer 为什么可以共享、Digest 和 Manifest 又分别解决什么问题以及 Image 最终是如何被构建、存储和分发的。一、先从一个问题开始换一台 Linux怎么运行同一个项目假设我们现在有一个 Go 项目。在自己的电脑上运行没有问题。但是如果换一台 Linux 服务器呢可能需要重新上传代码 ↓ 安装 Go ↓ 安装项目依赖 ↓ 修改配置 ↓ 启动项目如果服务器环境和开发环境又存在差异就可能出现“在我电脑上明明可以运行为什么换台机器就不行了”Docker 解决这个问题的核心思路之一就是把运行应用所需要的内容打包起来。而这个“打包后的东西”就是我们今天要重点讨论的Docker Image二、Docker Image 到底是什么可以先把 Image 理解成一个用于创建容器的模板其中包含运行应用所需要的文件和配置。简单来说应用程序 运行环境 依赖 配置 ↓ Docker Image例如一个 Go 应用可以构建成一个 Image。然后把这个 Image 拿到另一台 LinuxDocker Image ↓ 另一台 Linux ↓ 运行应用这样就不需要每次都重新从头配置整个运行环境。不过这里马上会产生一个问题Image 是模板那真正运行起来的东西是什么这就要引出 Docker 中另外一个非常重要的概念Container三、Image 和 Container 到底有什么区别两者可以简单理解为Image │ │ docker run ↓ Container1. Image镜像Image 可以理解成一个只读模板。里面包含应用 依赖 文件系统 配置它本身并不是一个正在运行的程序。2. Container容器Container 是基于 Image 创建出来的运行实例。可以简单理解成Container Image 内容 Writable Layer 运行时状态 / 配置所以Image 是“模板”Container 是“运行起来的实例”。例如docker run nginx可以理解成nginx Image ↓ 创建 ↓ nginx Container3. 一个 Image 可以创建多个 Container例如nginx Image / | \ ↓ ↓ ↓ Container Container Container所以如果启动 10 个 nginx Container并不意味着需要准备 10 份完全独立的 nginx Image。多个 Container 可以共享 Image 的只读内容。这就产生了一个新的问题如果多个 Container 可以共享 Image那么 Image 本身到底是怎么保存的四、Image 为什么要分层如果我们只修改其中一个文件修改一个配置文件 ↓ 整个 Image 都重新处理显然会比较浪费。所以 Docker 使用了Layer镜像层一个 Image 不再简单理解成一个整体而是由多个 Layer 组成五、Layer 到底是什么Layer 可以理解成一组文件系统变化。例如Layer 1 → 基础文件 Layer 2 → 新增 / 修改文件 Layer 3 → 添加配置 Layer 4 → 添加应用文件这里需要特别注意Layer 本质上不是“应用层”“依赖层”“运行时层”。这些只是为了方便理解而进行的示意。更准确地说Layer ↓ 文件系统变化 ↓ 新增 / 修改 / 删除文件也就是说每一层描述的是在原来的文件系统基础上又发生了什么变化。六、Layer 为什么可以共享既然 Image 是由 Layer 组成的那么就会产生一个非常有意思的问题如果两个 Image 中存在相同的 Layer需要保存两份吗例如两个 Image 都使用了Layer A1 Layer A2那么本地并不需要简单地理解成保存 A1 保存 A2 再保存一份 A1 再保存一份 A2而是可以对相同 Layer 进行复用。这就是 Docker Image 分层非常重要的一个价值Layer 可以共享和复用带来的好处包括减少本地存储减少重复下载提高镜像分发效率支持 Docker Build Cache例如那么Layer 1 Layer 2可以被两个 Image 共同使用。但是问题又来了Docker 怎么知道两个 Layer 是不是相同的七、Digest给内容一个“指纹”Docker 使用 Digest 来帮助进行内容寻址。可以简单理解成Layer 内容 ↓ SHA256 ↓ Digest例如Layer A ↓ sha256:abc123...另一个 Image 中如果存在相同内容Layer B ↓ sha256:abc123...那么就可以通过 Digest 判断它们对应的是相同内容。简单理解内容相同 ↓ Digest 相同 ↓ 可以识别 / 复用如果内容发生变化原始内容 ↓ sha256:abc123 内容发生变化 ↓ sha256:def456Digest 也会发生变化。因此可以把 Digest 理解成内容的“指纹”。八、Tag 和 Digest 有什么区别我们平时经常看到nginx:latest这里的latest就是 Tag。而另外一种引用形式是nginxsha256:xxxx这里使用的是 Digest。两者的核心区别可以简单理解为TagDigest本质名字 / 引用内容摘要是否可以变化可以内容变化时改变示例nginx:latestsha256:xxxx例如nginx:latest今天可能指向 Image Alatest → Image A以后 Registry 中的latest可能指向新的 Image Blatest → Image B所以 Tag 更像“这个镜像现在叫什么。”而 Digest 更接近“这个具体内容是谁。”九、一个 Image 到底由哪些东西组成到这里我们已经知道Image ↓ 很多 Layer同时又知道Layer ↓ Digest但是又出现了一个问题Docker 怎么知道一个 Image 到底应该使用哪些 Layer这时候就需要理解Manifest可以把 Manifest 理解成Image 的“组成清单”。例如Manifest 会描述 / 引用ConfigLayerDigestSizeMedia Type因此Manifest 不是用来保存文件内容的而是用来描述 Image 由什么组成。十、现在回头看一个 Image 到底是什么把前面的知识串起来再展开所以可以形成一个比较完整的认识Tag / Digest ↓ 找到 Image ↓ Manifest ↓ 知道有哪些 Config Layers ↓ Config Layer 内容一句话总结引用找到镜像Manifest 描述组成Layers 保存文件系统内容。十一、这些 Layer 是怎么来的前面解决了Image 是什么又解决了Image 为什么分 Layer接下来就要问这些 Layer 到底是谁创建出来的答案就是Dockerfile Docker Build基本过程Dockerfile ↓ docker build ↓ Layers Config ↓ Manifest ↓ Image例如一个简单的 Go DockerfileFROM golang:1.25 WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -o app其中一些指令会产生文件系统变化。例如RUN / COPY / ADD ↓ 文件系统变化 ↓ Layer而一些指令主要影响 Image 的配置或元数据例如ENV CMD EXPOSE ↓ Config / Metadata因此可以建立一个非常重要的认识Dockerfile 描述构建过程Build 最终产生 Image 所需要的 Layers 和 Config。十二、为什么 Dockerfile 要分开写既然 Layer 可以复用那么它不仅可以帮助节省存储。还可以帮助Docker Build Cache例如COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -o app假设我们只是修改了业务代码go.mod 没变 ↓ 依赖没有变化 ↓ 之前的依赖相关结果可以复用但是源代码变化 ↓ COPY . . 发生变化 ↓ 后面的步骤需要重新执行所以Layer ↓ 缓存 ↓ 复用构建结果 ↓ 减少重复构建这也是为什么 Dockerfile 中经常会看到COPY go.mod go.sum ./ RUN go mod download COPY . .而不是一开始就COPY . . RUN go mod download前一种写法更有利于利用缓存。十三、Multi-stage Build构建环境和运行环境分开再思考一个问题如果一个 Go 程序已经编译好了运行的时候还需要 Go 编译器吗答案显然是不需要。如果把所有东西都放进最终 Image最终 Image Go Compiler Source Code Dependencies Build Tools Application最终镜像可能包含很多运行时根本不需要的内容。因此可以使用Multi-stage Build基本思路Build Stage ┌──────────────────┐ │ Compiler │ │ Source Code │ │ Dependencies │ └────────┬─────────┘ │ Build ↓ Application Binary │ ↓ Runtime Stage ┌──────────────────┐ │ Minimal Runtime │ │ Application │ │ Binary │ └──────────────────┘也就是Build Stage 负责构建Runtime Stage 负责运行。这样可以减小最终 Image不携带编译器不携带源码减少运行环境中的无关内容十四、一个 Tag为什么可以支持不同平台现在又有一个新的问题如果我的电脑是 amd64另一台机器是 arm64能不能使用同一个镜像 Tag这就涉及Multi-platform Image例如nginx:latest ↓ Image Index ┌──┴──┐ ↓ ↓ amd64 arm64 ↓ ↓ Manifest Manifest ↓ ↓ Config Config Layers Layers当执行docker pull nginxDocker 会根据当前平台选择对应的镜像变体。可以简单理解成当前平台 ↓ Image Index ↓ 选择对应 Manifest ↓ 下载对应 Config Layers这里需要区分Multi-stageMulti-platform解决构建问题平台问题结构Build → Runtimeamd64 → arm64关注怎么构建给谁运行一句话Multi-stage 解决“怎么构建”Multi-platform 解决“给谁运行”。十五、Image 做好了怎么把它带到另一台机器现在回到文章最开始的问题换一台 Linux怎么运行同一个项目前面我们已经有了Dockerfile ↓ Build ↓ Image那么接下来就是怎么把 Image 从一台机器带到另一台机器这时候就需要Registry可以把 Registry 理解成Docker Image 的存储和分发中心。基本流程我的电脑 │ │ docker push ↓ Registry │ │ docker pull ↓ 另一台 Linux十六、Docker Image Reference 怎么看例如docker.io / library / nginx : latest可以拆成docker.io ↓ Registry library ↓ Namespace nginx ↓ Repository latest ↓ Tag所以Registry / Namespace / Repository / Tag例如Repository: nginx Tags: ├── latest ├── 1.29 └── 1.28这里要注意Repository 是nginx不同的 Tag 可以指向不同版本的镜像内容。十七、Docker CLI 拉取规则最常见docker pull nginx可以理解成默认使用docker.io/library/nginx:latest如果指定 Tagdocker pull nginx:1.29表示拉取nginx └── Tag: 1.29如果指定 Digestdocker pull nginxsha256:xxxx则是根据确定的 Digest 指定内容。因此可以记住三个简单规则不写 Registry → 使用默认 Registry 不写 Tag → 默认 latest 使用 Digest → 指定确定内容十八、一次docker pull到底发生了什么执行docker pull nginx背后并不是简单地服务器 ↓ 把一个 Image 文件下载下来而是一个逐步解析和获取的过程。可以简化为docker pull nginx ↓ 解析 Image Reference ↓ 请求 Registry ↓ 获取 Image Index / Manifest ↓ 选择当前平台 ↓ 找到 Config Layers ↓ 检查本地 Layer ┌────┴────┐ 已存在 不存在 ↓ ↓ 复用 下载 └────┬────┘ ↓ Local Image这里非常重要的一点就是Pull 不一定会重新下载所有 Layer。如果本地已经存在相同的 LayerLayer Digest ↓ 本地已经存在 ↓ 直接复用这就是前面讲的Layer 共享 Digest 内容寻址最终这些内容组成了本地 Image。然后Local Image ↓ docker run ↓ Container十九、Image 用完了怎么清理随着学习 Docker我们很容易遇到docker images看到REPOSITORY TAG IMAGE ID nginx latest abc123 none none def456这时候可能会看到很多none但需要注意none不一定等于 Dangling Image。可以专门查看 Dangling Imagedocker images -f danglingtrue清理 Dangling Image使用docker image prune默认主要清理Dangling Images更彻底的清理docker image prune -a它的范围更大删除没有被任何 Container 使用的 Image包括有 Tag 但没有被使用的 Image。所以docker image prune ↓ 范围较小 docker image prune -a ↓ 范围更大执行prune -a前要确认是否真的不再需要这些 Image。二十、最后把整个 Image 机制串起来学习到这里可以把 Docker Image 的完整链路串起来Dockerfile ↓ Build ↓ Layers Config ↓ Manifest ↓ Image ↓ push ↓ Registry ↓ pull ↓ Local Image ↓ docker run ↓ Container ↓ Writable Layer而 Image 内部又可以理解为Image Reference (Tag / Digest) ↓ Image Index多平台时 ↓ Image Manifest ↓ ┌──────────────────┐ │ Config │ │ │ │ Layer 1 │ │ Layer 2 │ │ Layer 3 │ └──────────────────┘其中Image用于创建 Container 的只读模板。Layer文件系统变化的集合也是镜像共享和构建缓存的重要基础。Digest基于内容计算得到的摘要可用于内容寻址和识别相同内容。Manifest描述 Image 所使用的 Config 和 Layers。RegistryImage 的存储和分发中心。ContainerImage 创建出来的运行实例并拥有自己的可写层。二十一、总结如果只记住这几个关键词可以记成Image ↓ Layer ↓ 共享 ↓ Digest ↓ Manifest ↓ Build ↓ Registry ↓ Pull ↓ Container更完整地说Docker Image 并不是一个简单的大文件而是由 Config 和多个 Layer 组成并通过 Manifest 描述它们之间的关系。Layer 带来了内容复用镜像共享存储优化构建缓存增量分发Digest 则帮助 Docker 根据内容进行识别和寻址。而 Registry 则负责让这些 Image 可以在不同机器之间进行存储和分发。最终形成Dockerfile ↓ Build ↓ Layers Config ↓ Manifest ↓ Image ↓ Registry ↓ Pull ↓ Local Image ↓ Container理解 Docker Image 的关键就是理解“分层、内容寻址、Manifest 描述、Registry 分发”这四件事。
返回列表