Move more dockerfile content to chapter 7

This commit is contained in:
Baohua Yang
2026-02-09 13:13:33 -08:00
parent b44c9acd6c
commit bae82e993a
15 changed files with 105 additions and 60 deletions

View File

@@ -295,8 +295,8 @@ module.exports = config({
'image/dockerfile/shell', 'image/dockerfile/shell',
'image/dockerfile/onbuild', 'image/dockerfile/onbuild',
'image/dockerfile/references', 'image/dockerfile/references',
'image/multistage-builds/', 'image/dockerfile/7.17_multistage_builds.md',
'image/multistage-builds/laravel', 'image/dockerfile/7.18_multistage_builds_laravel.md',
'image/manifest', 'image/manifest',
] ]
}, { }, {

View File

@@ -66,53 +66,15 @@ RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
* *exec* 格式`RUN ["可执行文件", "参数1", "参数2"]`这更像是函数调用中的格式 * *exec* 格式`RUN ["可执行文件", "参数1", "参数2"]`这更像是函数调用中的格式
既然 `RUN` 就像 Shell 脚本一样可以执行命令那么我们是否就可以像 Shell 脚本一样把每个命令对应一个 RUN 比如这样 Dockerfile 中每一个指令都会建立一层`RUN` 也不例外每一个 `RUN` 的行为就和刚才我们手工建立镜像的过程一样新建立一层在其上执行这些命令执行结束后`commit` 这一层的修改构成新的镜像
```docker > **注意**
FROM debian:bookworm >
> 每一个 `RUN` 指令都会产生一个新的镜像层为了减少镜像体积和层数我们通常会将多个命令合并到一个 `RUN` 指令中执行
>
> 更多关于 `RUN` 指令的详细用法最佳实践如清理缓存使用 pipefail `Union FS` 的层数限制等内容请参阅 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)** 中的 **[RUN 指令](../07_dockerfile/7.1_run.md)** 小节
RUN apt-get update 要想编写优秀的 `Dockerfile`必须了解每一条指令的作用和副作用 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)** 我们将对 `COPY`, `ADD`, `CMD`, `ENTRYPOINT` 等指令进行详细讲解
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-7.2.4.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
```
之前说过Dockerfile 中每一个指令都会建立一层`RUN` 也不例外每一个 `RUN` 的行为就和刚才我们手工建立镜像的过程一样新建立一层在其上执行这些命令执行结束后`commit` 这一层的修改构成新的镜像
而上面的这种写法创建了 7 层镜像这是完全没有意义的而且很多运行时不需要的东西都被装进了镜像里比如编译环境更新的软件包等等结果就是产生非常臃肿非常多层的镜像不仅仅增加了构建部署的时间也很容易出错
这是很多初学 Docker 的人常犯的一个错误
*Union FS 是有最大层数限制的比如 AUFS曾经是最大不得超过 42 现在是不得超过 127 *
上面的 `Dockerfile` 正确的写法应该是这样
```docker
FROM debian:bookworm
RUN set -x; buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-7.2.4.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
```
首先之前所有的命令只有一个目的就是编译安装 redis 可执行文件因此没有必要建立很多层这只是一层的事情因此这里没有使用很多个 `RUN` 一一对应不同的命令而是仅仅使用一个 `RUN` 指令并使用 `&&` 将各个所需命令串联起来将之前的 7 简化为了 1 在撰写 Dockerfile 的时候要经常提醒自己这并不是在写 Shell 脚本而是在定义每一层该如何构建
并且这里为了格式化还进行了换行Dockerfile 支持 Shell 类的行尾添加 `\` 的命令换行方式以及行首 `#` 进行注释的格式良好的格式比如换行缩进注释等会让维护排障更为容易这是一个比较好的习惯
此外还可以看到这一组命令的最后添加了清理工作的命令删除了为了编译构建所需要的软件清理了所有下载展开的文件并且还清理了 `apt` 缓存文件这是很重要的一步我们之前说过镜像是多层存储每一层的东西并不会在下一层被删除会一直跟随着镜像因此镜像构建时一定要确保每一层只添加真正需要添加的东西任何无关的东西都应该清理掉
很多人初学 Docker 制作出了很臃肿的镜像的原因之一就是忘记了每一层构建的最后一定要清理掉无关文件
### 构建镜像 ### 构建镜像

View File

@@ -1,9 +1,63 @@
## 4.7 镜像的实现原理 ## 4.7 实现原理
Docker 镜像是怎么实现增量的修改和维护的 Docker 镜像是怎么实现增量的修改和维护的为什么容器启动如此之快这一切都归功于 Docker 的镜像分层存储设计
每个镜像都由很多层次构成Docker 使用 [Union FS](https://en.wikipedia.org/wiki/UnionFS) 将这些不同的层结合到一个镜像中去。 ### 镜像与分层存储
通常 Union FS 有两个用途, 一方面可以实现不借助 LVMRAID 将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作 在之前的章节中我们一直强调镜像包含操作系统完整的 `root` 文件系统其体积往往是庞大的因此在 Docker 设计时就充分利用 **Union FS** 的技术将其设计为分层存储的架构
Docker OverlayFS 上构建的容器也是利用了类似的原理 Docker 镜像并不是一个单纯的文件而是由一组文件系统叠加构成的
最底层的镜像称为 **基础镜像Base Image**通常是各种 Linux 发行版的 root 文件系统 UbuntuDebianCentOS
当我们在基础镜像之上构建新的镜像时例如安装了 NginxDocker 并不是复制一份基础镜像而是在基础镜像之上**新建一个层Layer**并在该层中仅记录为了安装 Nginx 而发生的文件变更添加修改删除
这种分层存储结构使得镜像的复用分发变得非常高效
* **复用**如果多个镜像都基于同一个基础镜像例如都基于 `ubuntu:24.04`那么宿主机只需要下载一份 `ubuntu:24.04`所有镜像都可以共享它
* **轻量**镜像仅仅记录了与基础镜像的差异因此体积非常小
### 容器层与读写
我们要理解的一个关键概念是**镜像的每一层都是只读的Read-only**
那么既然镜像只读容器为什么能写文件呢
当容器启动时Docker 会在镜像的最上层添加一个新的**可写层Writable Layer**通常被称为**容器层**
```
┌──────────────────────────────────────────────┐
│ 容器层 (可写, Writable Container Layer) │ <-- 所有的写操作都在这里
├──────────────────────────────────────────────┤
│ 镜像层 (只读, Read-only Image Layer) │
├──────────────────────────────────────────────┤
│ 镜像层 (只读, Read-only Image Layer) │
├──────────────────────────────────────────────┤
│ 基础镜像层 (只读, Base Image Layer) │
└──────────────────────────────────────────────┘
```
* **读取文件**当容器需要读取文件时Docker 会从最上层容器层开始向下层镜像层寻找直到找到该文件为止
* **修改文件**当容器需要修改某个文件时Docker 会从下层镜像中将该文件复制到上层的容器层然后对副本进行修改这被称为 **写时复制Copy-on-Write, CoW** 策略
* **删除文件**当容器删除某个文件时Docker 并不是真的去下层删除它因为下层是只读的而是在容器层创建一个特殊的白障Whiteout文件用来标记该文件已被删除从而在容器视图中隐藏它
这就是为什么
1. **容器删除后数据会丢失**因为所有的数据修改都保存在最上层的容器层中容器销毁时这个层也就随之销毁了除非使用了数据卷详见[数据管理](../08_data_network/README.md)
2. **镜像不可变**无论我们在容器里删除了多少文件基础镜像的体积并不会减小因为它们依然存在于底层的只读层中
### 内容寻址与镜像 ID
Docker 镜像的每一层都有一个唯一的 ID这个 ID 是根据该层的内容计算出来的哈希值SHA256这意味着
* **内容即 ID**只要层的内容有一丁点变化ID 就会变
* **安全性**确保了镜像内容的完整性下载过程中如果数据损坏ID 校验就会失败
* **去重**如果两个不同的镜像甚至是不同来源的镜像包含相同的层ID 相同Docker 引擎在本地只会存储一份绝不重复下载
### 联合文件系统 (Union FS)
Docker 使用联合文件系统Union FS来实现这种分层挂载常见的驱动包括 `overlay2`目前推荐`aufs`早期使用`btrfs``zfs`
虽然实现细节不同但它们都遵循上述的 **分层 + CoW** 模型
> 想要深入了解 Overlay2 等文件系统的具体实现原理包括 WorkDirUpperDirLowerDir 等底层细节请阅读 **[第十四章 底层实现](../14_implementation/README.md)** 中的 **[联合文件系统](../14_implementation/14.4_ufs.md)** 章节

View File

@@ -5,3 +5,13 @@
简单的说容器是独立运行的一个或一组应用以及它们的运行态环境对应的虚拟机可以理解为模拟运行的一整套操作系统提供了运行态环境和其他系统环境和跑在上面的应用 简单的说容器是独立运行的一个或一组应用以及它们的运行态环境对应的虚拟机可以理解为模拟运行的一整套操作系统提供了运行态环境和其他系统环境和跑在上面的应用
本章将具体介绍如何来管理一个容器包括创建启动和停止等 本章将具体介绍如何来管理一个容器包括创建启动和停止等
* [启动容器](5.1_run.md)
* [守护态运行](5.2_daemon.md)
* [终止容器](5.3_stop.md)
* [进入容器](5.4_attach_exec.md)
* [导出和导入容器](5.5_import_export.md)
* [删除容器](5.6_rm.md)

View File

@@ -1,4 +1,4 @@
# 多阶段构建 ## 7.17 多阶段构建
## 之前的做法 ## 之前的做法

View File

@@ -1,4 +1,4 @@
## 实战多阶段构建 Laravel 镜像 ## 7.18 实战多阶段构建 Laravel 镜像
> 本节适用于 PHP 开发者阅读`Laravel` 基于 8.x 版本各个版本的文件结构可能会有差异请根据实际自行修改 > 本节适用于 PHP 开发者阅读`Laravel` 基于 8.x 版本各个版本的文件结构可能会有差异请根据实际自行修改

View File

@@ -6,5 +6,6 @@
这一章介绍如何在 Docker 内部以及容器之间管理数据在容器中管理数据主要有两种方式 这一章介绍如何在 Docker 内部以及容器之间管理数据在容器中管理数据主要有两种方式
* 数据卷Volumes * [数据卷](volume.md)
* 挂载主机目录 (Bind mounts) * [挂载主机目录](bind-mounts.md)

View File

@@ -327,7 +327,6 @@ $ docker network prune
## 延伸阅读 ## 延伸阅读
- [高级网络配置](linking.md)容器互联详解
- [配置 DNS](dns.md)自定义 DNS 设置 - [配置 DNS](dns.md)自定义 DNS 设置
- [端口映射](port_mapping.md)高级端口配置 - [端口映射](port_mapping.md)高级端口配置
- [Compose 网络](../compose/10.5_compose_file.md)Compose 中的网络配置 - [Compose 网络](../compose/10.5_compose_file.md)Compose 中的网络配置

View File

@@ -3,3 +3,14 @@
`Docker Compose` Docker 官方编排Orchestration项目之一负责快速的部署分布式应用 `Docker Compose` Docker 官方编排Orchestration项目之一负责快速的部署分布式应用
本章将介绍 `Compose` 项目情况以及安装和使用 本章将介绍 `Compose` 项目情况以及安装和使用
* [简介](10.1_introduction.md)
* [安装与卸载](10.2_install.md)
* [使用](10.3_usage.md)
* [命令说明](10.4_commands.md)
* [Compose 模板文件](10.5_compose_file.md)
* [实战 Django](10.6_django.md)
* [实战 Rails](10.7_rails.md)
* [实战 WordPress](10.8_wordpress.md)
* [实战 LNMP](10.9_lnmp.md)

View File

@@ -11,3 +11,11 @@ Docker 底层的核心技术包括 Linux 上的命名空间Namespaces
前者相对容易实现一些后者则需要宿主机系统的深入支持 前者相对容易实现一些后者则需要宿主机系统的深入支持
随着 Linux 系统对于命名空间功能的完善实现程序员已经可以实现上面的所有需求让某些进程在彼此隔离的命名空间中运行大家虽然都共用一个内核和某些运行时环境例如一些系统命令和系统库但是彼此却看不到都以为系统中只有自己的存在这种机制就是容器Container利用命名空间来做权限的隔离控制利用 cgroups 来做资源分配 随着 Linux 系统对于命名空间功能的完善实现程序员已经可以实现上面的所有需求让某些进程在彼此隔离的命名空间中运行大家虽然都共用一个内核和某些运行时环境例如一些系统命令和系统库但是彼此却看不到都以为系统中只有自己的存在这种机制就是容器Container利用命名空间来做权限的隔离控制利用 cgroups 来做资源分配
* [基本架构](14.1_arch.md)
* [命名空间](14.2_namespace.md)
* [控制组](14.3_cgroups.md)
* [联合文件系统](14.4_ufs.md)
* [容器格式](14.5_container_format.md)
* [网络](14.6_network.md)

View File

@@ -14,7 +14,7 @@
#### 使用多阶段构建 #### 使用多阶段构建
`Docker 17.05` 以上版本中你可以使用 [多阶段构建](../04_image/multistage-builds.md) 来减少所构建镜像的大小 Docker 17.05 以上版本中你可以使用 [多阶段构建](../07_dockerfile/7.17_multistage_builds.md) 来减少所构建镜像的大小
#### 避免安装不必要的包 #### 避免安装不必要的包

View File

@@ -31,8 +31,6 @@
* [删除本地镜像](04_image/4.3_rm.md) * [删除本地镜像](04_image/4.3_rm.md)
* [利用 commit 理解镜像构成](04_image/4.4_commit.md) * [利用 commit 理解镜像构成](04_image/4.4_commit.md)
* [使用 Dockerfile 定制镜像](04_image/4.5_build.md) * [使用 Dockerfile 定制镜像](04_image/4.5_build.md)
* [Dockerfile 多阶段构建](04_image/multistage-builds/README.md)
* [实战多阶段构建 Laravel 镜像](04_image/multistage-builds/laravel.md)
* [其它制作镜像的方式](04_image/4.6_other.md) * [其它制作镜像的方式](04_image/4.6_other.md)
* [实现原理](04_image/4.7_internal.md) * [实现原理](04_image/4.7_internal.md)
* [第五章 操作容器](05_container/README.md) * [第五章 操作容器](05_container/README.md)
@@ -67,6 +65,8 @@
* [LABEL 为镜像添加元数据](07_dockerfile/7.14_label.md) * [LABEL 为镜像添加元数据](07_dockerfile/7.14_label.md)
* [SHELL 指令](07_dockerfile/7.15_shell.md) * [SHELL 指令](07_dockerfile/7.15_shell.md)
* [参考文档](07_dockerfile/7.16_references.md) * [参考文档](07_dockerfile/7.16_references.md)
* [多阶段构建](07_dockerfile/7.17_multistage_builds.md)
* [实战多阶段构建 Laravel 镜像](07_dockerfile/7.18_multistage_builds_laravel.md)
* [第八章 数据与网络管理](08_data_network/README.md) * [第八章 数据与网络管理](08_data_network/README.md)
* [数据管理](08_data_network/data/README.md) * [数据管理](08_data_network/data/README.md)
* [数据卷](08_data_network/data/volume.md) * [数据卷](08_data_network/data/volume.md)