mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-03-10 11:54:37 +00:00
Move more dockerfile content to chapter 7
This commit is contained in:
@@ -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',
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@@ -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 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件。
|
|
||||||
|
|
||||||
### 构建镜像
|
### 构建镜像
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,63 @@
|
|||||||
## 4.7 镜像的实现原理
|
## 4.7 实现原理
|
||||||
|
|
||||||
Docker 镜像是怎么实现增量的修改和维护的?
|
Docker 镜像是怎么实现增量的修改和维护的?为什么容器启动如此之快?这一切都归功于 Docker 的镜像分层存储设计。
|
||||||
|
|
||||||
每个镜像都由很多层次构成,Docker 使用 [Union FS](https://en.wikipedia.org/wiki/UnionFS) 将这些不同的层结合到一个镜像中去。
|
### 镜像与分层存储
|
||||||
|
|
||||||
通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起,Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作。
|
在之前的章节中,我们一直强调镜像包含操作系统完整的 `root` 文件系统,其体积往往是庞大的。因此在 Docker 设计时,就充分利用 **Union FS** 的技术,将其设计为分层存储的架构。
|
||||||
|
|
||||||
Docker 在 OverlayFS 上构建的容器也是利用了类似的原理。
|
Docker 镜像并不是一个单纯的文件,而是由一组文件系统叠加构成的。
|
||||||
|
|
||||||
|
最底层的镜像称为 **基础镜像(Base Image)**,通常是各种 Linux 发行版的 root 文件系统,如 Ubuntu、Debian、CentOS 等。
|
||||||
|
|
||||||
|
当我们在基础镜像之上构建新的镜像时(例如安装了 Nginx),Docker 并不是复制一份基础镜像,而是在基础镜像之上,**新建一个层(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 等文件系统的具体实现原理,包括 WorkDir、UpperDir、LowerDir 等底层细节,请阅读 **[第十四章 底层实现](../14_implementation/README.md)** 中的 **[联合文件系统](../14_implementation/14.4_ufs.md)** 章节。
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# 多阶段构建
|
## 7.17 多阶段构建
|
||||||
|
|
||||||
## 之前的做法
|
## 之前的做法
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
## 实战多阶段构建 Laravel 镜像
|
## 7.18 实战多阶段构建 Laravel 镜像
|
||||||
|
|
||||||
> 本节适用于 PHP 开发者阅读。`Laravel` 基于 8.x 版本,各个版本的文件结构可能会有差异,请根据实际自行修改。
|
> 本节适用于 PHP 开发者阅读。`Laravel` 基于 8.x 版本,各个版本的文件结构可能会有差异,请根据实际自行修改。
|
||||||
|
|
||||||
@@ -6,5 +6,6 @@
|
|||||||
|
|
||||||
这一章介绍如何在 Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式:
|
这一章介绍如何在 Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式:
|
||||||
|
|
||||||
* 数据卷(Volumes)
|
* [数据卷](volume.md)
|
||||||
* 挂载主机目录 (Bind mounts)
|
* [挂载主机目录](bind-mounts.md)
|
||||||
|
|
||||||
|
|||||||
@@ -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 中的网络配置
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#### 使用多阶段构建
|
#### 使用多阶段构建
|
||||||
|
|
||||||
在 `Docker 17.05` 以上版本中,你可以使用 [多阶段构建](../04_image/multistage-builds.md) 来减少所构建镜像的大小。
|
在 Docker 17.05 以上版本中,你可以使用 [多阶段构建](../07_dockerfile/7.17_multistage_builds.md) 来减少所构建镜像的大小。
|
||||||
|
|
||||||
#### 避免安装不必要的包
|
#### 避免安装不必要的包
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user