mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-03-11 04:14:38 +00:00
style: apply global formatting fixes (struct, spacing, zhlint)
This commit is contained in:
@@ -116,6 +116,10 @@ flowchart TD
|
||||
| `--platform` | 指定平台架构 | `docker pull --platform linux/arm64 nginx` |
|
||||
| `--quiet, -q` | 静默模式 | `docker pull -q nginx` |
|
||||
|
||||
#### 概述
|
||||
|
||||
总体概述了以下内容。
|
||||
|
||||
#### 指定平台
|
||||
|
||||
在 Apple Silicon Mac 上拉取 x86 镜像:
|
||||
@@ -180,13 +184,13 @@ $ sudo systemctl restart docker # Linux
|
||||
|
||||
```
|
||||
|
||||
详见 [镜像加速器](../03_install/3.9_mirror.md) 章节。
|
||||
详见[镜像加速器](../03_install/3.9_mirror.md)章节。
|
||||
|
||||
---
|
||||
|
||||
### 验证镜像完整性
|
||||
|
||||
为了确保下载的镜像没有被篡改且内容一致,我们可以校验镜像的摘要(Digest)。
|
||||
为了确保下载的镜像没有被篡改且内容一致,我们可以校验镜像的摘要 (Digest)。
|
||||
|
||||
#### 查看镜像摘要
|
||||
|
||||
@@ -214,13 +218,13 @@ $ docker pull ubuntu@sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9
|
||||
|
||||
在使用 `docker pull` 过程中,可能会遇到下载速度慢、镜像不存在或磁盘空间不足等问题。以下是一些常见问题的排查思路。
|
||||
|
||||
#### Q: 下载速度很慢
|
||||
#### Q:下载速度很慢
|
||||
|
||||
1. 配置镜像加速器
|
||||
2. 检查网络连接
|
||||
3. 尝试拉取更小的镜像版本(如 `alpine` 变体)
|
||||
3. 尝试拉取更小的镜像版本 (如 `alpine` 变体)
|
||||
|
||||
#### Q: 提示镜像不存在
|
||||
#### Q:提示镜像不存在
|
||||
|
||||
运行以下命令:
|
||||
|
||||
@@ -230,10 +234,10 @@ Error: pull access denied, repository does not exist
|
||||
|
||||
可能原因:
|
||||
- 镜像名拼写错误
|
||||
- 私有镜像未登录(需要 `docker login`)
|
||||
- 私有镜像未登录 (需要 `docker login`)
|
||||
- 镜像确实不存在
|
||||
|
||||
#### Q: 磁盘空间不足
|
||||
#### Q:磁盘空间不足
|
||||
|
||||
运行以下命令:
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ ubuntu noble 329ed837d508 3 days ago 78MB
|
||||
| **CREATED** | 创建时间 |
|
||||
| **SIZE** | 本地占用空间 |
|
||||
|
||||
#### 概述
|
||||
|
||||
总体概述了以下内容。
|
||||
|
||||
#### 同一镜像多个标签
|
||||
|
||||
注意上面的 `ubuntu:24.04` 和 `ubuntu:noble` 拥有相同的 IMAGE ID——它们是同一个镜像的不同标签,只占用一份存储空间。
|
||||
@@ -131,7 +135,7 @@ $ docker images -f label=maintainer=example@email.com
|
||||
|
||||
---
|
||||
|
||||
### 虚悬镜像(Dangling Images)
|
||||
### 虚悬镜像
|
||||
|
||||
在镜像列表里,你可能会看到一些仓库名和标签都为 `<none>` 的镜像,这类镜像被称为虚悬镜像。
|
||||
|
||||
@@ -170,7 +174,11 @@ $ docker image prune
|
||||
|
||||
除了虚悬镜像,`docker image ls` 默认列出的只是顶层镜像。还有一种镜像是为了加速镜像构建、重复利用资源而存在的中间层镜像。
|
||||
|
||||
#### 查看所有镜像(包含中间层)
|
||||
#### 概述
|
||||
|
||||
总体概述了以下内容。
|
||||
|
||||
#### 查看所有镜像 (包含中间层)
|
||||
|
||||
运行以下命令:
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ $ docker rmi nginx@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b1
|
||||
|
||||
执行删除命令后,Docker 会输出一系列的操作记录,理解这些信息有助于我们掌握镜像删除的机制。
|
||||
|
||||
删除镜像时会看到两类信息:**Untagged**和**Deleted**
|
||||
删除镜像时会看到两类信息:**Untagged** 和 **Deleted**
|
||||
|
||||
```bash
|
||||
$ docker rmi redis:alpine
|
||||
@@ -126,7 +126,7 @@ flowchart TD
|
||||
|
||||
#### 删除所有虚悬镜像
|
||||
|
||||
虚悬镜像(dangling):没有标签的镜像,通常是旧版本被新版本覆盖后产生的
|
||||
虚悬镜像 (dangling):没有标签的镜像,通常是旧版本被新版本覆盖后产生的
|
||||
|
||||
```bash
|
||||
## 查看虚悬镜像
|
||||
@@ -221,7 +221,7 @@ Untagged: ubuntu:24.04
|
||||
|
||||
```
|
||||
|
||||
#### 原因三:被其他镜像依赖(中间层)
|
||||
#### 原因三:被其他镜像依赖 (中间层)
|
||||
|
||||
运行以下命令:
|
||||
|
||||
@@ -248,7 +248,7 @@ Error: image has dependent child images
|
||||
|
||||
### 清理策略
|
||||
|
||||
针对不同的环境(开发环境 vs 生产环境),我们应该采用不同的镜像清理策略。
|
||||
针对不同的环境 (开发环境 vs 生产环境),我们应该采用不同的镜像清理策略。
|
||||
|
||||
#### 开发环境
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
## 4.4 利用 commit 理解镜像构成
|
||||
|
||||
> 注意:如果您是初学者,您可以暂时跳过后面的内容,直接学习 [容器](../05_container/) 一节。
|
||||
> 注意:如果您是初学者,您可以暂时跳过后面的内容,直接学习[容器](../05_container/)一节。
|
||||
|
||||
注意: `docker commit` 命令除了学习之外,还有一些特殊的应用场合,比如被入侵后保存现场等。但是,不要使用 `docker commit` 定制镜像,定制镜像应该使用 `Dockerfile` 来完成。如果你想要定制镜像请查看下一小节。
|
||||
注意:`docker commit` 命令除了学习之外,还有一些特殊的应用场合,比如被入侵后保存现场等。但是,不要使用 `docker commit` 定制镜像,定制镜像应该使用 `Dockerfile` 来完成。如果你想要定制镜像请查看下一小节。
|
||||
|
||||
镜像是容器的基础,每次执行 `docker run` 的时候都会指定哪个镜像作为容器运行的基础。在之前的例子中,我们所使用的都是来自于 Docker Hub 的镜像。直接使用这些镜像是可以满足一定的需求,而当这些镜像无法直接满足需求时,我们就需要定制这些镜像。接下来的几节就将讲解如何定制镜像。
|
||||
|
||||
@@ -16,7 +16,7 @@ $ docker run --name webserver -d -p 80:80 nginx
|
||||
|
||||
这条命令会用 `nginx` 镜像启动一个容器,命名为 `webserver`,并且映射了 80 端口,这样我们可以用浏览器去访问这个 `nginx` 服务器。
|
||||
|
||||
如果是在本机运行的 Docker,那么可以直接访问:`http://localhost` ,如果是在虚拟机、云服务器上安装的 Docker,则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址。
|
||||
如果是在本机运行的 Docker,那么可以直接访问:`http://localhost`,如果是在虚拟机、云服务器上安装的 Docker,则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址。
|
||||
|
||||
直接用浏览器访问的话,我们会看到默认的 Nginx 欢迎页面。
|
||||
|
||||
@@ -63,7 +63,7 @@ A /var/cache/nginx/uwsgi_temp
|
||||
|
||||
现在我们定制好了变化,我们希望能将其保存下来形成镜像。
|
||||
|
||||
要知道,当我们运行一个容器的时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 `docker commit` 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
|
||||
要知道,当我们运行一个容器的时候 (如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 `docker commit` 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
|
||||
|
||||
`docker commit` 的语法格式为:
|
||||
|
||||
@@ -120,12 +120,16 @@ docker run --name web2 -d -p 81:80 nginx:v2
|
||||
|
||||
至此,我们第一次完成了定制镜像,使用的是 `docker commit` 命令,手动操作给旧的镜像添加了新的一层,形成新的镜像,对镜像多层存储应该有了更直观的感觉。
|
||||
|
||||
### 概述
|
||||
|
||||
总体概述了以下内容。
|
||||
|
||||
### 慎用 `docker commit`
|
||||
|
||||
使用 `docker commit` 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中并不会这样使用。
|
||||
|
||||
首先,如果仔细观察之前的 `docker diff webserver` 的结果,你会发现除了真正想要修改的 `/usr/share/nginx/html/index.html` 文件外,由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,将会导致镜像极为臃肿。
|
||||
|
||||
此外,使用 `docker commit` 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为 **黑箱镜像**,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体的操作。这种黑箱镜像的维护工作是非常痛苦的。
|
||||
此外,使用 `docker commit` 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为**黑箱镜像**,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体的操作。这种黑箱镜像的维护工作是非常痛苦的。
|
||||
|
||||
而且,回顾之前提及的镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 `docker commit` 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
从刚才的 `docker commit` 的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
|
||||
|
||||
Dockerfile 是一个文本文件,其内包含了一条条的 **指令(Instruction)**,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
|
||||
Dockerfile 是一个文本文件,其内包含了一条条的**指令 (Instruction)**,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
|
||||
|
||||
### 使用 docker init 快速创建(推荐)
|
||||
### 使用 docker init 快速创建 (推荐)
|
||||
|
||||
Docker 提供了 `docker init` 命令,可以根据项目类型自动生成 Dockerfile、.dockerignore 和 compose.yaml 文件:
|
||||
Docker 提供了 `docker init` 命令,可以根据项目类型自动生成 Dockerfile、。dockerignore 和 compose.yaml 文件:
|
||||
|
||||
```bash
|
||||
$ docker init
|
||||
```
|
||||
|
||||
该命令会交互式地询问项目类型(如 Go、Node.js、Python、Rust 等),并生成符合最佳实践的配置文件。对于新项目,这是推荐的起步方式。
|
||||
该命令会交互式地询问项目类型 (如 Go、Node.js、Python、Rust 等),并生成符合最佳实践的配置文件。对于新项目,这是推荐的起步方式。
|
||||
|
||||
### 手动创建 Dockerfile
|
||||
|
||||
@@ -37,7 +37,7 @@ RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
|
||||
|
||||
### FROM 指定基础镜像
|
||||
|
||||
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 `nginx` 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 `FROM` 就是指定 **基础镜像**,因此一个 `Dockerfile` 中 `FROM` 是必备的指令,并且必须是第一条指令。
|
||||
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 `nginx` 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 `FROM` 就是指定**基础镜像**,因此一个 `Dockerfile` 中 `FROM` 是必备的指令,并且必须是第一条指令。
|
||||
|
||||
在 [Docker Hub](https://hub.docker.com/search?q=&type=image&image_filter=official) 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 [`nginx`](https://hub.docker.com/_/nginx/)、[`redis`](https://hub.docker.com/_/redis/)、[`mongo`](https://hub.docker.com/_/mongo/)、[`mysql`](https://hub.docker.com/_/mysql/)、[`httpd`](https://hub.docker.com/_/httpd/)、[`php`](https://hub.docker.com/_/php/)、[`tomcat`](https://hub.docker.com/_/tomcat/) 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 [`node`](https://hub.docker.com/_/node)、[`openjdk`](https://hub.docker.com/_/openjdk/)、[`python`](https://hub.docker.com/_/python/)、[`ruby`](https://hub.docker.com/_/ruby/)、[`golang`](https://hub.docker.com/_/golang/) 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。
|
||||
|
||||
@@ -52,7 +52,7 @@ FROM scratch
|
||||
|
||||
如果你以 `scratch` 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。
|
||||
|
||||
不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 `FROM scratch` 会让镜像体积更加小巧。使用 [Go 语言](https://golang.google.cn/) 开发的应用很多会使用这种方式来制作镜像,这也是有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
|
||||
不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 `FROM scratch` 会让镜像体积更加小巧。使用 [Go 语言](https://golang.google.cn/)开发的应用很多会使用这种方式来制作镜像,这也是有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
|
||||
|
||||
### RUN 执行命令
|
||||
|
||||
@@ -72,9 +72,9 @@ Dockerfile 中每一个指令都会建立一层,`RUN` 也不例外。每一个
|
||||
>
|
||||
> 每一个 `RUN` 指令都会产生一个新的镜像层。为了减少镜像体积和层数,我们通常会将多个命令合并到一个 `RUN` 指令中执行。
|
||||
>
|
||||
> 更多关于 `RUN` 指令的详细用法、最佳实践(如清理缓存、使用 pipefail 等)及 `Union FS` 的层数限制等内容,请参阅 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)**中的**[RUN 指令](../07_dockerfile/7.1_run.md)** 小节。
|
||||
> 更多关于 `RUN` 指令的详细用法、最佳实践 (如清理缓存、使用 pipefail 等) 及 `Union FS` 的层数限制等内容,请参阅**[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)**中的** [RUN 指令](../07_dockerfile/7.1_run.md)**小节。
|
||||
|
||||
要想编写优秀的 `Dockerfile`,必须了解每一条指令的作用和副作用。在 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)** 中,我们将对 `COPY`, `ADD`, `CMD`, `ENTRYPOINT` 等指令进行详细讲解。
|
||||
要想编写优秀的 `Dockerfile`,必须了解每一条指令的作用和副作用。在**[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)**中,我们将对 `COPY`,`ADD`,`CMD`,`ENTRYPOINT` 等指令进行详细讲解。
|
||||
|
||||
### 构建镜像
|
||||
|
||||
@@ -104,11 +104,11 @@ docker build [选项] <上下文路径/URL/->
|
||||
|
||||
在这里我们指定了最终镜像的名称 `-t nginx:v3`,构建成功后,我们可以像之前运行 `nginx:v2` 那样来运行这个镜像,其结果会和 `nginx:v2` 一样。
|
||||
|
||||
### 镜像构建上下文(Context)
|
||||
### 镜像构建上下文
|
||||
|
||||
如果注意,会看到 `docker build` 命令最后有一个 `.`。`.` 表示当前目录,而 `Dockerfile` 就在当前目录,因此不少初学者以为这个路径是在指定 `Dockerfile` 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定 **上下文路径**。那么什么是上下文呢?
|
||||
如果注意,会看到 `docker build` 命令最后有一个 `.`。`.` 表示当前目录,而 `Dockerfile` 就在当前目录,因此不少初学者以为这个路径是在指定 `Dockerfile` 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定**上下文路径**。那么什么是上下文呢?
|
||||
|
||||
首先我们要理解 `docker build` 的工作原理。Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 [Docker Remote API](https://docs.docker.com/develop/sdk/),而如 `docker` 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 `docker` 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
|
||||
首先我们要理解 `docker build` 的工作原理。Docker 在运行时分为 Docker 引擎 (也就是服务端守护进程) 和客户端工具。Docker 的引擎提供了一组 REST API,被称为 [Docker Remote API](https://docs.docker.com/develop/sdk/),而如 `docker` 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 `docker` 功能,但实际上,一切都是使用的远程调用形式在服务端 (Docker 引擎) 完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
|
||||
|
||||
当我们进行镜像构建的时候,并非所有定制都会通过 `RUN` 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 `COPY` 指令、`ADD` 指令等。而 `docker build` 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
|
||||
|
||||
@@ -120,7 +120,7 @@ docker build [选项] <上下文路径/URL/->
|
||||
COPY ./package.json /app/
|
||||
```
|
||||
|
||||
这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`,也不是复制 `Dockerfile` 所在目录下的 `package.json`,而是复制 **上下文(context)** 目录下的 `package.json`。
|
||||
这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`,也不是复制 `Dockerfile` 所在目录下的 `package.json`,而是复制**上下文 (context)** 目录下的 `package.json`。
|
||||
|
||||
因此,`COPY` 这类指令中的源文件的路径都是*相对路径*。这也是初学者经常会问的为什么 `COPY ../package.json /app` 或者 `COPY /opt/xxxx /app` 无法工作的原因,因为这些路径已经超出了上下文的范围,Docker 引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。
|
||||
|
||||
@@ -146,6 +146,8 @@ Sending build context to Docker daemon 2.048 kB
|
||||
|
||||
### 其它 `docker build` 的用法
|
||||
|
||||
本节涵盖了相关内容与详细描述,主要探讨以下几个方面:
|
||||
|
||||
#### 直接用 Git repo 进行构建
|
||||
|
||||
或许你已经注意到了,`docker build` 还支持从 URL 构建,比如可以直接从 Git repo 中构建:
|
||||
|
||||
@@ -41,6 +41,10 @@ f477a6e18e98 About a minute ago 214.9 MB
|
||||
|
||||
Docker 还提供了 `docker save` 和 `docker load` 命令,用以将镜像保存为一个文件,然后传输到另一个位置上,再加载进来。这是在没有 Docker Registry 时的做法,现在已经不推荐,镜像迁移应该直接使用 Docker Registry,无论是直接使用 Docker Hub 还是使用内网私有 Registry 都可以。
|
||||
|
||||
#### 概述
|
||||
|
||||
总体概述了以下内容。
|
||||
|
||||
#### 保存镜像
|
||||
|
||||
使用 `docker save` 命令可以将镜像保存为归档文件。
|
||||
@@ -63,7 +67,7 @@ filename: POSIX tar archive
|
||||
|
||||
这里的 filename 可以为任意名称甚至任意后缀名,但文件的本质都是归档文件
|
||||
|
||||
**注意:如果同名则会覆盖(没有警告)**
|
||||
**注意:如果同名则会覆盖 (没有警告)**
|
||||
|
||||
若使用 `gzip` 压缩:
|
||||
|
||||
|
||||
@@ -8,22 +8,22 @@ Docker 镜像是怎么实现增量的修改和维护的?为什么容器启动
|
||||
|
||||
Docker 镜像并不是一个单纯的文件,而是由一组文件系统叠加构成的。
|
||||
|
||||
最底层的镜像称为 **基础镜像(Base Image)**,通常是各种 Linux 发行版的 root 文件系统,如 Ubuntu、Debian、CentOS 等。
|
||||
最底层的镜像称为**基础镜像 (Base Image)**,通常是各种 Linux 发行版的 root 文件系统,如 Ubuntu、Debian、CentOS 等。
|
||||
|
||||
当我们在基础镜像之上构建新的镜像时(例如安装了 Nginx),Docker 并不是复制一份基础镜像,而是在基础镜像之上,**新建一个层(Layer)**,并在该层中仅记录为了安装 Nginx 而发生的文件变更(添加、修改、删除)。
|
||||
当我们在基础镜像之上构建新的镜像时 (例如安装了 Nginx),Docker 并不是复制一份基础镜像,而是在基础镜像之上,**新建一个层 (Layer)**,并在该层中仅记录为了安装 Nginx 而发生的文件变更 (添加、修改、删除)。
|
||||
|
||||
这种分层存储结构使得镜像的复用、分发变得非常高效:
|
||||
|
||||
* **复用**:如果多个镜像都基于同一个基础镜像(例如都基于 `ubuntu:24.04`),那么宿主机只需要下载一份 `ubuntu:24.04`,所有镜像都可以共享它。
|
||||
* **复用**:如果多个镜像都基于同一个基础镜像 (例如都基于 `ubuntu:24.04`),那么宿主机只需要下载一份 `ubuntu:24.04`,所有镜像都可以共享它。
|
||||
* **轻量**:镜像仅仅记录了与基础镜像的差异,因此体积非常小。
|
||||
|
||||
### 容器层与读写
|
||||
|
||||
我们要理解的一个关键概念是:**镜像的每一层都是只读的(Read-only)**。
|
||||
我们要理解的一个关键概念是:**镜像的每一层都是只读的 (Read-only)**。
|
||||
|
||||
那么,既然镜像只读,容器为什么能写文件呢?
|
||||
|
||||
当容器启动时,Docker 会在镜像的最上层,添加一个新的**可写层(Writable Layer)**,通常被称为**容器层**。
|
||||
当容器启动时,Docker 会在镜像的最上层,添加一个新的**可写层 (Writable Layer)**,通常被称为**容器层**。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
@@ -39,27 +39,27 @@ flowchart TD
|
||||
Note["所有的写操作都在容器层这里"] -.-> L4
|
||||
```
|
||||
|
||||
* **读取文件**:当容器需要读取文件时,Docker 会从最上层(容器层)开始向下层(镜像层)寻找,直到找到该文件为止。
|
||||
* **修改文件**:当容器需要修改某个文件时,Docker 会从下层镜像中将该文件复制到上层的容器层,然后对副本进行修改。这被称为**写时复制(Copy-on-Write, CoW)** 策略。
|
||||
* **删除文件**:当容器删除某个文件时,Docker 并不是真的去下层删除它(因为下层是只读的),而是在容器层创建一个特殊的“白障(Whiteout)”文件,用来标记该文件已被删除,从而在容器视图中隐藏它。
|
||||
* **读取文件**:当容器需要读取文件时,Docker 会从最上层 (容器层) 开始向下层 (镜像层) 寻找,直到找到该文件为止。
|
||||
* **修改文件**:当容器需要修改某个文件时,Docker 会从下层镜像中将该文件复制到上层的容器层,然后对副本进行修改。这被称为**写时复制 (Copy-on-Write,CoW)** 策略。
|
||||
* **删除文件**:当容器删除某个文件时,Docker 并不是真的去下层删除它 (因为下层是只读的),而是在容器层创建一个特殊的 “白障 (Whiteout)” 文件,用来标记该文件已被删除,从而在容器视图中隐藏它。
|
||||
|
||||
这就是为什么:
|
||||
|
||||
1. **容器删除后数据会丢失**:因为所有的数据修改都保存在最上层的容器层中,容器销毁时,这个层也就随之销毁了。(除非使用了数据卷,详见[数据管理](../08_data_network/README.md))。
|
||||
1. **容器删除后数据会丢失**:因为所有的数据修改都保存在最上层的容器层中,容器销毁时,这个层也就随之销毁了。(除非使用了数据卷,详见[数据管理](../08_data_network/README.md))。
|
||||
2. **镜像不可变**:无论我们在容器里删除了多少文件,基础镜像的体积并不会减小,因为它们依然存在于底层的只读层中。
|
||||
|
||||
### 内容寻址与镜像 ID
|
||||
|
||||
Docker 镜像的每一层都有一个唯一的 ID,这个 ID 是根据该层的内容计算出来的哈希值(SHA256)。这意味着:
|
||||
Docker 镜像的每一层都有一个唯一的 ID,这个 ID 是根据该层的内容计算出来的哈希值 (SHA256)。这意味着:
|
||||
|
||||
* **内容即 ID**:只要层的内容有一丁点变化,ID 就会变。
|
||||
* **安全性**:确保了镜像内容的完整性,下载过程中如果数据损坏,ID 校验就会失败。
|
||||
* **去重**:如果两个不同的镜像(甚至是不同来源的镜像)包含相同的层(ID 相同),Docker 引擎在本地只会存储一份,绝不重复下载。
|
||||
* **去重**:如果两个不同的镜像 (甚至是不同来源的镜像) 包含相同的层 (ID 相同),Docker 引擎在本地只会存储一份,绝不重复下载。
|
||||
|
||||
### 联合文件系统
|
||||
|
||||
Docker 使用联合文件系统(Union FS)来实现这种分层挂载。常见的驱动包括 `overlay2`(目前推荐)、`aufs`(早期使用)、`btrfs`、`zfs` 等。
|
||||
Docker 使用联合文件系统 (Union FS) 来实现这种分层挂载。常见的驱动包括 `overlay2` (目前推荐)、`aufs` (早期使用)、`btrfs`、`zfs` 等。
|
||||
|
||||
虽然实现细节不同,但它们都遵循上述的 **分层 + CoW** 模型。
|
||||
虽然实现细节不同,但它们都遵循上述的**分层 + CoW** 模型。
|
||||
|
||||
> 想要深入了解 Overlay2 等文件系统的具体实现原理,包括 WorkDir、UpperDir、LowerDir 等底层细节,请阅读 **[第十四章 底层实现](../14_implementation/README.md)**中的**[联合文件系统](../14_implementation/14.4_ufs.md)** 章节。
|
||||
> 想要深入了解 Overlay2 等文件系统的具体实现原理,包括 WorkDir、UpperDir、LowerDir 等底层细节,请阅读**[第十四章底层实现](../14_implementation/README.md)**中的**[联合文件系统](../14_implementation/14.4_ufs.md)**章节。
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
# 第四章 使用镜像
|
||||
# 第四章使用镜像
|
||||
|
||||
在之前的介绍中,我们知道镜像是 Docker 的三大组件之一。
|
||||
|
||||
Docker 运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker 会从镜像仓库下载该镜像。
|
||||
|
||||
## 概述
|
||||
|
||||
总体概述了以下内容。
|
||||
|
||||
## 本章内容
|
||||
|
||||
本章将介绍更多关于镜像的内容,包括:
|
||||
|
||||
Reference in New Issue
Block a user