Files
docker_practice/02_basic_concept/2.1_image.md
T
yeasy ef2625531d style: 含中文的括号统一为全角(336 处 / 110 文件)
全书此前是真·五五开:含中文的括号 336 处半角 : 336 处全角,而且**在同一文件
内混用**——`18_security/18.1_kernel_ns.md` 第 11/17/20 行写 `(如 runC)`,
第 31 行写 `(用户命名空间映射)`。这不是体例问题,是它自己跟自己不一致。

集群里其余 13 本都一边倒(llm_internals 5 : 1049、oc_guide 2 : 713、
forward_deployed_engineering_guide 0 : 108),blockchain_guide 已在 2026-08-05
按同一方向统一过 97 处,故取全角,而非另立新规。全角括号前后不留空格,
原先 `概念 (镜像、容器、仓库) 和` 两侧的空格一并收掉。

**纯拉丁内容的半角括号一个没动**——`新建一个层 (Layer)`、`项目 (project)`
保持原样:改动前后「半角且内容纯拉丁」的数量都是 **1163**,而「半角且含中文」
从 **336 变成 0**。

其余验证:改动前后各自抹掉全部空白与圆括号后**逐字节相同**(两侧均 19559 字符);
左右括号总数改动前后都是 399 : 399,证明没有吞括号也没有配错对。
`check_project_rules` 202/202、`check_emphasis` 202/202、37 tests OK、
根 `format_checker` 与 `validate_codeblocks` 干净、`git diff --check` 干净。
2026-08-24 13:13:23 -07:00

7.4 KiB
Raw Blame History

2.1 镜像

版本说明:本节示例基于 Docker v29.x 编写。示例中使用的镜像标签(如 ubuntu:24.04、nginx:latest)为演示用途,建议查阅 Docker Hub 或相应镜像的官方页面确认最新可用版本。

Docker 镜像作为容器运行的基石,其设计理念和实现机制至关重要。本节将深入探讨镜像的本质、与操作系统的关系、内容构成以及核心的分层存储机制。

2.1.1 一句话理解镜像

Docker 镜像是一个只读的模板,包含了运行应用所需的一切:代码、运行时、库、环境变量和配置文件。 如果用一个类比:镜像就像是一张光盘或 ISO 文件。你可以用同一张光盘在不同电脑上安装系统,而光盘本身不会被修改。同样,一个镜像可以创建多个容器,而镜像本身保持不变。

2.1.2 镜像与操作系统的关系

我们都知道,操作系统分为 内核 和 用户空间:

flowchart TD
    subgraph UserSpace ["用户空间"]
        direction TB
        App["应用程序、工具、库、配置文件...<br/>(这部分被打包成 Docker 镜像)"]
    end

    subgraph KernelSpace ["Linux 内核"]
        direction TB
        Kernel["容器共享宿主机的内核"]
    end

    UserSpace --- KernelSpace

对于 Linux 而言,内核启动后会挂载 root 文件系统来提供用户空间支持。Docker 镜像 本质上就是一个 root 文件系统。

例如,官方镜像 ubuntu:24.04 包含了一套完整的 Ubuntu 24.04 最小系统的 root 文件系统——但 不包含 Linux 内核(因为容器共享宿主机的内核)。

2.1.3 镜像包含什么?

Docker 镜像是一个特殊的文件系统,包含:

内容类型 示例
程序文件 应用二进制文件、Python/Node 解释器
库文件 libc、OpenSSL、各种依赖库
配置文件 nginx.conf、my.cnf 等
环境变量 PATH、LANG 等预设值
元数据 启动命令、暴露端口、数据卷定义

关键特性:

  • ✅ 镜像是 只读 的
  • ✅ 镜像 不包含 动态数据
  • ✅ 镜像构建后 内容不会改变

2.1.4 分层存储:镜像的核心设计

镜像的分层存储机制是 Docker 最具创新性的特性之一。通过 Union FS 技术,Docker 能够高效地构建和管理镜像。

为什么需要分层?

笔者认为,分层存储是 Docker 最巧妙的设计之一。

假设你有三个应用,都基于 Ubuntu 运行:

flowchart TD
    subgraph Traditional ["传统方式(不分层)总计: 1.5GB ❌"]
        direction LR
        AppA_Trad["App A<br/>Ubuntu 500MB"]
        AppB_Trad["App B<br/>Ubuntu 500MB"]
        AppC_Trad["App C<br/>Ubuntu 500MB"]
    end

    subgraph DockerLayered ["Docker 分层方式 总计: 620MB ✅"]
        direction TB
        subgraph Apps ["应用层"]
            direction LR
            AppA["App A 50MB"]
            AppB["App B 30MB"]
            AppC["App C 40MB"]
        end
        Ubuntu["Ubuntu<br/>(共享)500MB"]

        AppA --> Ubuntu
        AppB --> Ubuntu
        AppC --> Ubuntu
    end

分层是如何工作的?

笔者用一个更贴近最佳实践的 Dockerfile 来解释分层:

FROM ubuntu:24.04                              # 第 1 层:基础系统(约 78MB)
RUN apt-get update && apt-get install -y nginx # 第 2 层:更新索引并安装 nginx
COPY app.conf /etc/nginx/                      # 第 3 层:复制配置文件

这里特意把 apt-get update 和 apt-get install 放在同一个 RUN 里,避免构建缓存导致包索引过期。

构建后的镜像结构:

flowchart TD
    Layer3["第 3 层: COPY app.conf (只读)<br/>← 最新添加的层"]
    Layer2["第 2 层: nginx 安装文件与包索引 (只读)"]
    Layer1["第 1 层: Ubuntu 基础系统 (只读)<br/>← 基础镜像层"]

    Layer3 --> Layer2 --> Layer1

每一层的特点:

  • 只读:构建完成后不可修改
  • 可共享:多个镜像可以共享相同的层
  • 有缓存:未变化的层不会重新构建

分层存储的 “陷阱”

⚠️ 笔者特别提醒:理解这一点可以帮你避免构建出臃肿的镜像。关键原理:每一层的文件变化会被记录,但 删除操作只是标记,不会真正减小镜像体积。

## 错误示范 ❌

FROM ubuntu:24.04
RUN apt-get update
RUN apt-get install -y build-essential  # 安装编译工具(约 200MB)
RUN make && make install                  # 编译应用
RUN apt-get remove build-essential        # 试图删除编译工具

## 结果:镜像仍然包含 200MB 的编译工具!
## 正确做法 ✅

FROM ubuntu:24.04
RUN apt-get update && \
    apt-get install -y build-essential && \
    make && make install && \
    apt-get remove -y build-essential && \
    apt-get autoremove -y && \
    rm -rf /var/lib/apt/lists/*

## 在同一层完成安装、使用、清理

2.1.5 查看镜像的构建历史

## 查看镜像的历史(每层的构建记录)

$ docker history nginx:latest

IMAGE          CREATED       CREATED BY                                      SIZE
a6bd71f48f68   2 weeks ago   CMD ["nginx" "-g" "daemon off;"]                0B
<missing>      2 weeks ago   STOPSIGNAL SIGQUIT                              0B
<missing>      2 weeks ago   EXPOSE map[80/tcp:{}]                           0B
<missing>      2 weeks ago   ENTRYPOINT ["/docker-entrypoint.sh"]            0B
<missing>      2 weeks ago   COPY 30-tune-worker-processes.sh /docker-ent…   4.62kB
...

需要注意:docker history 展示的是镜像的构建历史。像 CMD、ENTRYPOINT、EXPOSE、STOPSIGNAL 这类 0B 项主要是镜像配置元数据,并不等同于新增了可见的文件系统内容;真正占用空间的通常是 RUN、COPY、ADD 等带来的文件变化。

2.1.6 镜像的标识

Docker 镜像有多种标识方式:

1. 镜像名称和标签

格式:[仓库地址/]仓库名[:标签]

## 完整格式

registry.example.com/myproject/myapp:v1.2.3

## 简写(使用 Docker Hub)

nginx:1.28
ubuntu:24.04

## 省略标签(默认使用 latest)

nginx  # 等同于 nginx:latest

2. 镜像 ID:Content-Addressable 标识

每个镜像有一个基于内容计算的唯一 ID:

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nginx        latest    a6bd71f48f68   2 weeks ago    187MB
ubuntu       24.04     ca2b0f26964c   3 weeks ago    78.1MB

3. 镜像摘要

更精确的标识,基于镜像内容的 SHA256 哈希:

$ docker images --digests
REPOSITORY  TAG     DIGEST                                                                    IMAGE ID
nginx       latest  sha256:6db391d1c0cfb30588ba0bf72ea999404f2764184d8b8d10d89e8a9c6... a6bd71f48f68

💡 笔者建议:在生产环境使用镜像摘要而非标签,因为标签可以被覆盖,但摘要是不可变的。

2.1.7 镜像的来源

Docker 镜像可以通过以下方式获取:

方式 说明 示例
从 Registry 拉取 最常用的方式 docker pull nginx
从 Dockerfile 构建 自定义镜像 docker build -t myapp .
从容器提交 保存容器状态(不推荐) docker commit
从文件导入 离线传输 docker load < image.tar