mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-08-10 08:27:25 +00:00
Clarify image build semantics
This commit is contained in:
+15
-13
@@ -1,21 +1,21 @@
|
|||||||
## 4.4 利用 commit 理解镜像构成
|
## 4.4 利用 commit 理解镜像构成
|
||||||
|
|
||||||
> 注意:如果您是初学者,您可以暂时跳过后面的内容,直接学习[容器](../05_container/)一节。
|
> 注意:如果你是初学者,可以暂时跳过后面的内容,直接学习[容器](../05_container/)一节。
|
||||||
|
|
||||||
注意:`docker commit` 命令除了学习之外,还有一些特殊的应用场合,比如被入侵后保存现场等。但是,不要使用 `docker commit` 定制镜像,定制镜像应该使用 `Dockerfile` 来完成。如果你想要定制镜像请查看下一小节。
|
`docker commit` 除了帮助理解镜像分层之外,在少数场景下也可用于留存现场,例如事后分析被入侵容器的状态;但是,日常定制镜像不应依赖 `docker commit`,而应使用下一节介绍的 `Dockerfile`。
|
||||||
|
|
||||||
镜像是容器的基础,每次执行 `docker run` 的时候都会指定哪个镜像作为容器运行的基础。在之前的例子中,我们所使用的都是来自于 Docker Hub 的镜像。直接使用这些镜像是可以满足一定的需求,而当这些镜像无法直接满足需求时,我们就需要定制这些镜像。接下来的几节就将讲解如何定制镜像。
|
镜像是容器的基础,每次执行 `docker run` 的时候都会指定哪个镜像作为容器运行的基础。在之前的例子中,我们所使用的都是来自于 Docker Hub 的镜像。直接使用这些镜像是可以满足一定的需求,而当这些镜像无法直接满足需求时,我们就需要定制这些镜像。接下来的几节就将讲解如何定制镜像。
|
||||||
|
|
||||||
回顾一下之前我们学到的知识,镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。
|
回顾一下之前我们学到的知识,镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,它以镜像层为只读基础,并在最上方增加一层供运行时写入的容器层。
|
||||||
|
|
||||||
现在让我们以定制一个 Web 服务器为例子,来讲解镜像是如何构建的。
|
现在让我们以定制一个 Web 服务器为例子,来讲解镜像是如何构建的。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker run --name webserver -d -p 80:80 nginx
|
$ docker run --name webserver -d -p 8080:80 nginx
|
||||||
```
|
```
|
||||||
这条命令会用 `nginx` 镜像启动一个容器,命名为 `webserver`,并且映射了 80 端口,这样我们可以用浏览器去访问这个 `nginx` 服务器。
|
这条命令会用 `nginx` 镜像启动一个容器,命名为 `webserver`。其中,`-p 8080:80` 表示把宿主机的 `8080` 端口映射到容器内的 `80` 端口,这样我们就可以用浏览器去访问这个 `nginx` 服务器。
|
||||||
|
|
||||||
如果是在本机运行的 Docker,那么可以直接访问:`http://localhost`,如果是在虚拟机、云服务器上安装的 Docker,则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址。
|
如果是在本机运行的 Docker,那么可以直接访问:`http://localhost:8080`;如果是在虚拟机、云服务器上安装的 Docker,则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址,并保留 `8080` 端口。
|
||||||
|
|
||||||
直接用浏览器访问的话,我们会看到默认的 Nginx 欢迎页面。
|
直接用浏览器访问的话,我们会看到默认的 Nginx 欢迎页面。
|
||||||
|
|
||||||
@@ -58,6 +58,8 @@ A /var/cache/nginx/proxy_temp
|
|||||||
A /var/cache/nginx/scgi_temp
|
A /var/cache/nginx/scgi_temp
|
||||||
A /var/cache/nginx/uwsgi_temp
|
A /var/cache/nginx/uwsgi_temp
|
||||||
```
|
```
|
||||||
|
其中,`A` 表示新增(Added),`C` 表示变更(Changed),`D` 表示删除(Deleted)。
|
||||||
|
|
||||||
现在我们定制好了变化,我们希望能将其保存下来形成镜像。
|
现在我们定制好了变化,我们希望能将其保存下来形成镜像。
|
||||||
|
|
||||||
要知道,当我们运行一个容器的时候 (如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 `docker commit` 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
|
要知道,当我们运行一个容器的时候 (如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 `docker commit` 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
|
||||||
@@ -67,7 +69,7 @@ A /var/cache/nginx/uwsgi_temp
|
|||||||
```bash
|
```bash
|
||||||
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
|
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
|
||||||
```
|
```
|
||||||
我们可以用下面的命令将容器保存为镜像:
|
我们可以用下面的命令将容器保存为镜像。默认情况下,`docker commit` 会在提交时暂停容器进程,以降低数据损坏的风险;如果确实不希望暂停,可以显式指定 `--no-pause`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker commit \
|
$ docker commit \
|
||||||
@@ -79,7 +81,7 @@ sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214
|
|||||||
```
|
```
|
||||||
其中 `--author` 是指定修改的作者,而 `--message` 则是记录本次修改的内容。这点和 `git` 版本控制相似,不过这里这些信息可以省略留空。
|
其中 `--author` 是指定修改的作者,而 `--message` 则是记录本次修改的内容。这点和 `git` 版本控制相似,不过这里这些信息可以省略留空。
|
||||||
|
|
||||||
我们可以在 `docker image ls` 中看到这个新定制的镜像:
|
我们可以在 `docker image ls` 中看到这个新定制的镜像。下面的输出仅为示例,标签、创建时间和大小会随着镜像版本和本地环境不同而变化:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker image ls nginx
|
$ docker image ls nginx
|
||||||
@@ -88,7 +90,7 @@ nginx v2 07e334659748 9 seconds ago
|
|||||||
nginx 1.27 05a60462f8ba 12 days ago 181.5 MB
|
nginx 1.27 05a60462f8ba 12 days ago 181.5 MB
|
||||||
nginx latest e43d811ce2f4 4 weeks ago 181.5 MB
|
nginx latest e43d811ce2f4 4 weeks ago 181.5 MB
|
||||||
```
|
```
|
||||||
我们还可以用 `docker history` 具体查看镜像内的历史记录,如果比较 `nginx:latest` 的历史记录,我们会发现新增了我们刚刚提交的这一层。
|
我们还可以用 `docker history` 具体查看镜像内的历史记录。例如先执行 `docker history nginx:v2`,再对比 `docker history nginx:latest`,就能看到我们刚刚提交出来的新层。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker history nginx:v2
|
$ docker history nginx:v2
|
||||||
@@ -106,18 +108,18 @@ e43d811ce2f4 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "da
|
|||||||
新的镜像定制好后,我们可以来运行这个镜像。
|
新的镜像定制好后,我们可以来运行这个镜像。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run --name web2 -d -p 81:80 nginx:v2
|
$ docker run --name web2 -d -p 81:80 nginx:v2
|
||||||
```
|
```
|
||||||
这里我们命名为新的服务为 `web2`,并且映射到 `81` 端口。访问 `http://localhost:81` 看到结果,其内容应该和之前修改后的 `webserver` 一样。
|
这里我们将新容器命名为 `web2`,并把宿主机的 `81` 端口映射到容器的 `80` 端口。访问 `http://localhost:81` 后,看到的内容应该和之前修改后的 `webserver` 一样。
|
||||||
|
|
||||||
至此,我们第一次完成了定制镜像,使用的是 `docker commit` 命令,手动操作给旧的镜像添加了新的一层,形成新的镜像,对镜像多层存储应该有了更直观的感觉。
|
至此,我们第一次完成了定制镜像,使用的是 `docker commit` 命令,手动操作给旧的镜像添加了新的一层,形成新的镜像,对镜像多层存储应该有了更直观的感觉。
|
||||||
|
|
||||||
### 4.4.1 慎用 `docker commit`
|
### 4.4.1 慎用 `docker commit`
|
||||||
|
|
||||||
使用 `docker commit` 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中并不会这样使用。
|
使用 `docker commit` 命令虽然可以比较直观地帮助理解镜像分层存储的概念,但它不应作为常规定制镜像的方式。
|
||||||
|
|
||||||
首先,如果仔细观察之前的 `docker diff webserver` 的结果,你会发现除了真正想要修改的 `/usr/share/nginx/html/index.html` 文件外,由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,将会导致镜像极为臃肿。
|
首先,如果仔细观察之前的 `docker diff webserver` 的结果,你会发现除了真正想要修改的 `/usr/share/nginx/html/index.html` 文件外,由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,将会导致镜像极为臃肿。
|
||||||
|
|
||||||
此外,使用 `docker commit` 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为 **黑箱镜像**,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体的操作。这种黑箱镜像的维护工作是非常痛苦的。
|
此外,使用 `docker commit` 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为 **黑箱镜像**,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体的操作。这种黑箱镜像的维护工作是非常痛苦的。
|
||||||
|
|
||||||
而且,回顾之前提及的镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 `docker commit` 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。
|
而且,回顾之前提及的镜像所使用的分层存储的概念,除当前层外,之前的每一层都不会发生改变。换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 `docker commit` 制作镜像,以及后期继续修改,那么每一次修改都会让镜像再膨胀一层;即使某些文件在更高层里被删除了,它们仍然存在于更低层中,只是在最终视图里被隐藏而已。因此,日常定制镜像应使用下一节介绍的 `Dockerfile`,把构建过程写成可重复执行、便于审查的文本。
|
||||||
|
|||||||
+21
-30
@@ -1,17 +1,17 @@
|
|||||||
## 4.5 使用 Dockerfile 定制镜像
|
## 4.5 使用 Dockerfile 定制镜像
|
||||||
|
|
||||||
从刚才的 `docker commit` 的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
|
从刚才的 `docker commit` 的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复、镜像构建不透明、体积难以控制等问题就会更容易解决。这个脚本就是 Dockerfile。
|
||||||
|
|
||||||
Dockerfile 是一个文本文件,其内包含了一条条的 **指令 (Instruction)**,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
|
Dockerfile 是一个文本文件,其内包含了一条条的 **指令 (Instruction)**。其中,会修改文件系统的指令通常会创建新层;而 `LABEL`、`CMD` 这类只修改镜像元数据的指令,则不会新增文件系统层。每一条指令的内容,都是在描述该镜像应当如何构建。
|
||||||
|
|
||||||
### 4.5.1 使用 docker init 快速创建:推荐
|
### 4.5.1 使用 docker init 快速创建:推荐
|
||||||
|
|
||||||
Docker 提供了 `docker init` 命令,可以根据项目类型自动生成 Dockerfile、.dockerignore 和 compose.yaml 文件:
|
Docker 提供了 `docker init` 命令,可以根据项目类型自动生成 `Dockerfile`、`.dockerignore`、`compose.yaml` 和 `README.Docker.md` 等文件:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker init
|
$ docker init
|
||||||
```
|
```
|
||||||
该命令会交互式地询问项目类型(支持 Go、Node.js、Python、Rust、Java、ASP.NET Core、PHP 等),并生成符合最佳实践的配置文件。对于新项目,这是推荐的起步方式。
|
该命令会交互式地询问项目类型(支持 Go、Node.js、Python、Rust、Java、ASP.NET Core、PHP with Apache 等),并生成可作为起点的配置文件。对于新项目,这是一个很好的起步方式,但生成后的内容仍应结合项目实际情况继续调整。
|
||||||
|
|
||||||
### 4.5.2 手动创建 Dockerfile
|
### 4.5.2 手动创建 Dockerfile
|
||||||
|
|
||||||
@@ -59,9 +59,9 @@ FROM scratch
|
|||||||
```docker
|
```docker
|
||||||
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
|
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
|
||||||
```
|
```
|
||||||
* *exec* 格式:`RUN [“可执行文件”, “参数1”, “参数2”]`,这更像是函数调用中的格式。
|
* *exec* 格式:`RUN ["可执行文件", "参数1", "参数2"]`,这更像是函数调用中的格式。
|
||||||
|
|
||||||
Dockerfile 中每一个指令都会建立一层,`RUN` 也不例外。每一个 `RUN` 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,`commit` 这一层的修改,构成新的镜像。
|
在会修改文件系统的指令里,`RUN` 是最典型的一类。每一个 `RUN` 的行为,都可以类比为我们刚才手工建立镜像的过程:先基于当前结果启动一个临时构建环境,在其上执行这些命令,再把这一步产生的文件系统变化保存为新的结果层。
|
||||||
|
|
||||||
> **注意**
|
> **注意**
|
||||||
>
|
>
|
||||||
@@ -79,6 +79,11 @@ Dockerfile 中每一个指令都会建立一层,`RUN` 也不例外。每一个
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker build -t nginx:v3 .
|
$ docker build -t nginx:v3 .
|
||||||
|
```
|
||||||
|
|
||||||
|
在当前版本的 Docker 中,`docker build` 默认会通过 Buildx 调用 BuildKit,因此你更常看到的是 `[+] Building ...` 这类输出。为了帮助理解“每一步如何形成镜像历史”,下面仍展示一种较容易阅读的经典输出形式:
|
||||||
|
|
||||||
|
```bash
|
||||||
Sending build context to Docker daemon 2.048 kB
|
Sending build context to Docker daemon 2.048 kB
|
||||||
Step 1 : FROM nginx
|
Step 1 : FROM nginx
|
||||||
---> e43d811ce2f4
|
---> e43d811ce2f4
|
||||||
@@ -101,11 +106,11 @@ docker build [选项] <上下文路径/URL/->
|
|||||||
|
|
||||||
如果注意,会看到 `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 build` 默认会通过 Buildx 向 BuildKit 后端发起构建请求;无论后端运行在本机还是远端,位置参数指定的都是 **构建上下文**,也就是构建器可以访问到的文件集合。
|
||||||
|
|
||||||
当我们进行镜像构建的时候,并非所有定制都会通过 `RUN` 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 `COPY` 指令、`ADD` 指令等。而 `docker build` 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
|
当我们进行镜像构建的时候,并非所有定制都会通过 `RUN` 指令完成,经常还需要把本地文件复制进镜像,比如通过 `COPY` 指令、`ADD` 指令等。因此,构建器必须能够访问这些文件,而它能访问的范围正是你传给 `docker build` 的那个上下文。
|
||||||
|
|
||||||
这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,`docker build` 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
|
如果上下文是本地目录,那么这个目录中的文件和子目录就会成为可用输入;如果上下文是远端 Git 仓库或 tar 包,那么构建器会直接获取对应内容。对于本地目录,BuildKit 会按需读取构建过程中真正需要的文件,而不是让 Dockerfile 任意访问宿主机上的任意路径。
|
||||||
|
|
||||||
如果在 `Dockerfile` 中这么写:
|
如果在 `Dockerfile` 中这么写:
|
||||||
|
|
||||||
@@ -114,18 +119,18 @@ 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 引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。
|
因此,`COPY` 这类指令中的源文件路径都应该以构建上下文为基准来理解。对于 legacy builder,像 `COPY ../package.json /app` 这样的写法会直接报错;而在 BuildKit 下,前导的越界 `../` 会被剥离并重新解释为上下文内路径。无论是哪种情况,构建器都无法读取上下文之外的宿主机文件;如果真的需要那些文件,应该先把它们放进上下文目录,或重新选择合适的上下文。
|
||||||
|
|
||||||
现在就可以理解刚才的命令 `docker build -t nginx:v3 .` 中的这个 `.`,实际上是在指定上下文的目录,`docker build` 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。
|
现在就可以理解刚才的命令 `docker build -t nginx:v3 .` 中的这个 `.`,实际上是在指定上下文目录,而不是单纯指定 `Dockerfile` 所在目录。
|
||||||
|
|
||||||
如果观察 `docker build` 输出,我们其实已经看到了这个发送上下文的过程:
|
如果观察 `docker build` 的经典输出,或 BuildKit 输出中的 `transferring context` 提示,我们其实都能看到上下文传输的过程:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker build -t nginx:v3 .
|
$ docker build -t nginx:v3 .
|
||||||
Sending build context to Docker daemon 2.048 kB
|
Sending build context to Docker daemon 2.048 kB
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
理解构建上下文对于镜像构建是很重要的,避免犯一些不应该的错误。比如有些初学者在发现 `COPY /opt/xxxx /app` 不工作后,于是干脆将 `Dockerfile` 放到了硬盘根目录去构建,结果发现 `docker build` 执行后,在发送一个几十 GB 的东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让 `docker build` 打包整个硬盘,这显然是使用错误。
|
理解构建上下文对于镜像构建是很重要的,避免犯一些不应该的错误。比如有些初学者在发现需要的文件不在上下文里后,干脆把上下文切到硬盘根目录去构建。这样做即使在 BuildKit 下也会让可见上下文变得过大,并且在使用 `COPY . .`、`ADD . /app` 之类写法时,仍可能触发大规模上下文传输,导致构建缓慢甚至失败。这显然是使用错误。
|
||||||
|
|
||||||
一般来说,应该会将 `Dockerfile` 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 `.gitignore` 一样的语法写一个 `.dockerignore`,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。
|
一般来说,应该会将 `Dockerfile` 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 `.gitignore` 一样的语法写一个 `.dockerignore`,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。
|
||||||
|
|
||||||
@@ -139,26 +144,12 @@ Sending build context to Docker daemon 2.048 kB
|
|||||||
|
|
||||||
#### 直接用 Git repo 进行构建
|
#### 直接用 Git repo 进行构建
|
||||||
|
|
||||||
或许你已经注意到了,`docker build` 还支持从 URL 构建,比如可以直接从 Git repo 中构建:
|
或许你已经注意到了,`docker build` 还支持从 URL 构建,也就是直接把远端 Git 仓库作为上下文。传统写法可以使用 URL 片段 `#ref:dir`,例如:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
## $env:DOCKER_BUILDKIT=0
|
$ docker build https://github.com/user/myrepo.git#mybranch:docker
|
||||||
|
|
||||||
## export DOCKER_BUILDKIT=0
|
|
||||||
|
|
||||||
$ docker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-world
|
|
||||||
|
|
||||||
Step 1/3 : FROM scratch
|
|
||||||
--->
|
|
||||||
Step 2/3 : COPY hello /
|
|
||||||
---> ac779757d46e
|
|
||||||
Step 3/3 : CMD ["/hello"]
|
|
||||||
---> Running in d2a513a760ed
|
|
||||||
Removing intermediate container d2a513a760ed
|
|
||||||
---> 038ad4142d2b
|
|
||||||
Successfully built 038ad4142d2b
|
|
||||||
```
|
```
|
||||||
这行命令指定了构建所需的 Git repo,并且指定分支为 `master`,构建目录为 `/amd64/hello-world/`,然后 Docker 就会自己去 `git clone` 这个项目、切换到指定分支、并进入到指定目录后开始构建。
|
这行命令表示:把 Git 仓库作为构建上下文,使用 `mybranch` 分支中的 `docker/` 子目录来构建。在较新的 Buildx 中,也可以改用结构更清晰的查询参数写法,例如 `?branch=mybranch&subdir=docker`。
|
||||||
|
|
||||||
#### 用给定的 tar 压缩包构建
|
#### 用给定的 tar 压缩包构建
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Docker 镜像并不是一个单纯的文件,而是由一组文件系统叠加
|
|||||||
这种分层存储结构使得镜像的复用、分发变得非常高效:
|
这种分层存储结构使得镜像的复用、分发变得非常高效:
|
||||||
|
|
||||||
* **复用**:如果多个镜像都基于同一个基础镜像 (例如都基于 `ubuntu:24.04`),那么宿主机只需要下载一份 `ubuntu:24.04`,所有镜像都可以共享它。
|
* **复用**:如果多个镜像都基于同一个基础镜像 (例如都基于 `ubuntu:24.04`),那么宿主机只需要下载一份 `ubuntu:24.04`,所有镜像都可以共享它。
|
||||||
* **轻量**:镜像仅仅记录了与基础镜像的差异,因此体积非常小。
|
* **轻量分发**:镜像可以复用已有层,只传输和存储新增差异层;不过镜像是否足够小,仍然取决于基础镜像和新增内容本身。
|
||||||
|
|
||||||
### 4.7.2 容器层与读写
|
### 4.7.2 容器层与读写
|
||||||
|
|
||||||
@@ -57,8 +57,8 @@ Docker 镜像的每一层都有一个唯一的 ID,这个 ID 是根据该层的
|
|||||||
|
|
||||||
### 4.7.4 联合文件系统
|
### 4.7.4 联合文件系统
|
||||||
|
|
||||||
Docker 使用联合文件系统 (Union FS) 来实现这种分层挂载。常见的驱动包括 `overlay2` (目前推荐)、`aufs` (早期使用)、`btrfs`、`zfs` 等。
|
Docker 使用联合文件系统 (Union FS) 与写时复制思路来实现这种分层挂载。传统的实现方式常见于 `overlay2`、`aufs`、`btrfs`、`zfs` 等存储驱动;而在 Docker Engine 29.0 及之后的全新安装中,默认镜像后端已经变为 containerd image store,它使用 snapshotter 来管理这些层。
|
||||||
|
|
||||||
虽然实现细节不同,但它们都遵循上述的 **分层 + CoW** 模型。
|
虽然底层实现细节不同,但它们都遵循上述的 **分层 + CoW** 模型;因此,无论你看到的是 `overlay2` 还是 containerd snapshotter,理解镜像层、容器层和写时复制的方式都是一样重要的。
|
||||||
|
|
||||||
> 想要深入了解 Overlay2 等文件系统的具体实现原理,包括 WorkDir、UpperDir、LowerDir 等底层细节,请阅读 **[第十二章 底层实现](../12_implementation/README.md)** 中的 **[联合文件系统](../12_implementation/12.4_ufs.md)** 章节。
|
> 想要深入了解 Overlay2 等文件系统的具体实现原理,包括 WorkDir、UpperDir、LowerDir 等底层细节,请阅读 **[第十二章 底层实现](../12_implementation/README.md)** 中的 **[联合文件系统](../12_implementation/12.4_ufs.md)** 章节。
|
||||||
|
|||||||
Reference in New Issue
Block a user