本文将介绍 Docker 映像和容器以及 docker 文件之间的差异与联系,本文还将解释如何以及何时使用它们。
什么是 Dockerfile?
它是一个简单的文本文件,包含命令或过程的集合。我们运行的这些命令和准则作用于配置为创建新的 Docker 镜像的基本镜像。Dockerfile 是 Docker 镜像的源代码。Dockerfile 是包含各种指令和配置的文本文件。Dockerfile 中的 FROM 命令标识要从中构建的基础镜像。运行 Docker run 命令时,Docker 将使用此文件生成镜像本身。Dockerfile 包含映像的创建说明。与仅保留二进制镜像相比,使用 Dockerfile 的好处是自动构建可确保您始终拥有最新版本。这在安全性方面是有利的,因为您不想安装任何不安全的应用程序。(这里只做简单介绍,详细简述会篇幅过长的)
什么是 Docker Image & Docker Container ?
Docker镜像是静态的、只读的模板,包含了运行应用所需的一切。Docker容器是基于这些镜像创建的运行实例,它们是动态的、可修改的。镜像用于分发应用,而容器用于运行应用。理解这两者的关系对于有效使用Docker至关重要。
关系类比:
- Docker Image:想象Docker镜像就像一个蛋糕的食谱。它包含了所有制作蛋糕所需的原料和步骤,但它本身不是蛋糕。
- Docker Container:如果把Docker镜像比作蛋糕的食谱,那么Docker容器就是根据这个食谱实际烘焙出来的蛋糕。
1. Docker Image
Docker镜像是一个轻量级、可执行的独立软件包,包含运行某个软件所需的所有内容。它包括代码、运行时环境、系统工具、系统库和设置。
- 定义:Docker 镜像是一个只读的模板,包含了运行某个应用程序所需的所有文件、环境变量、配置等。镜像是静态的,不会改变。
- 构建:镜像通常是通过一个名为 Dockerfile 的文件构建的。Dockerfile 包含了一系列指令,描述了如何构建这个镜像,包括从哪个基础镜像开始、需要安装哪些软件包、需要复制哪些文件等。
- 存储和分发:镜像可以存储在 Docker 仓库中(如 Docker Hub),便于分发和共享。用户可以从公共或私有仓库中拉取镜像,用于创建容器。
- 可共享:不同镜像可以共享基础层,节省空间
- 可移植:可以在任何支持Docker的环境中运行
2. Docker Container
Docker容器是镜像的运行实例。它是从镜像创建的可运行进程。
- 定义:Docker 容器是镜像的一个运行实例。容器是动态的,可以启动、停止、移动和删除。每个容器都是独立的,有自己的文件系统、网络接口和进程空间。
- 启动和运行:容器是从镜像启动的。当你运行一个容器时,Docker 会从指定的镜像创建一个可写层,并在这个可写层上运行应用程序。
- 可变性:容器是可变的,运行时的数据和状态会存储在容器的可写层中。如果需要保存容器的数据,可以将其提交为一个新的镜像,或者使用数据卷(volumes)。
- 轻量级:启动快速,资源占用少
- 可移植:可以在任何支持Docker的环境中启动
3. Docker Image & Docker Container 的关系
docker run: 命令会将 docker image作为容器运行。
a) 镜像是容器的基础:容器是基于镜像创建的运行实例。
b) 一对多关系:一个镜像可以创建多个容器。
c) 镜像是静态的,容器是动态的:镜像是只读的模板,而容器在运行时可以被修改。
d) 容器可以创建新镜像:通过对容器的修改,我们可以创建新的镜像。
生命周期
- 镜像的生命周期:构建 -> 分发 -> 运行 -> 更新
- 容器的生命周期:创建 -> 运行 -> 暂停 -> 恢复 -> 停止 -> 删除
使用场景
- 镜像:用于分发和部署应用程序。开发人员创建镜像,然后将其推送到镜像仓库。
- 容器:用于运行应用程序。运维人员从镜像仓库拉取镜像,然后启动容器来运行应用。
主要命令
- 镜像相关:
docker build
,docker push
,docker pull
,docker images
- 容器相关:
docker run
,docker start
,docker stop
,docker rm
,docker ps
数据持久化
- 镜像本身不存储数据。
- 容器可以写入数据,但默认情况下,当容器被删除时,数据也会丢失。
- 为了持久化数据,我们使用Docker卷(Volumes)或绑定挂载(Bind Mounts)。
Docker Image实现原理
1. 联合文件系统(UnionFS)
联合文件系统是一种分层、轻量级且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。
想象一下,你有几张透明的塑料片,每张上面画了不同的图案。当你把这些塑料片叠在一起时,你会看到一个完整的图像,这就是联合文件系统的基本原理。
2. Docker镜像加载原理
Docker镜像加载原理: Docker镜像是由多个只读层叠加而成的。在拉取镜像时,Docker会从Docker Registry中下载这些层,并将它们缓存在本地。当运行容器时,Docker会将这些只读层组合成一个可写的容器层。这种分层设计使得镜像更加高效和灵活,因为不同的层可以被共享和复用。
Docker镜像是基于联合文件系统的概念构建的。每个Docker镜像由一系列层(layers)组成,这些层是只读的。当我们运行一个容器时,Docker会加载这些层,并在最顶部添加一个可写层(容器层)。
让我们用一个例子来解释这个过程:
bootfs (boot file system)
bootfs是启动文件系统的缩写。它包含了bootloader和kernel(内核)。这是系统启动时最先加载的部分。
主要特点:
- bootfs负责引导系统启动
- 在系统启动完成后,整个bootfs会被卸载,以释放内存
- 所有的Docker镜像都共享同一个bootfs
想象bootfs就像电脑的BIOS和操作系统引导程序。它的工作是启动系统,然后就"功成身退"了。
rootfs (root file system)
rootfs是根文件系统的缩写。它包含了典型Linux系统中的/dev, /proc, /bin, /etc等目录和文件。
主要特点:
- rootfs位于bootfs之上
- 不同的Linux发行版,如Ubuntu, CentOS等,有不同的rootfs
- 在Docker中,不同的镜像可能有不同的rootfs
将rootfs想象成你在安装完操作系统后看到的文件系统结构。它包含了操作系统运行所需的所有基本目录和文件。
3. 理解docker于虚拟机的优势
知道了这些概念就可以理解,为什么Docker容器比传统虚拟机启动更快、占用资源更少
- bootfs通常非常小,仅包含bootloader和kernel。
- 当容器启动时,bootfs被加载到内存中,然后kernel启动。
- Docker容器的rootfs默认是只读的。当创建一个容器时,会在rootfs上添加一个读写层。
- 镜像的分层结构:当Docker加载镜像时,它会从底层开始,逐层向上加载。每一层都只包含与上一层的差异部分。这种方法大大减少了存储空间的使用和镜像的传输时间。
- 基础镜像(如Ubuntu)包含bootfs和该发行版特定的rootfs。
- 在此基础上,每安装一个程序,就在上面添加一个新层。
- 这种分层结构使得Docker可以:
-
快速构建镜像(只需加载变化的部分)
-
节省存储空间(相同的层可以被多个镜像共享)
-
快速传输镜像(只需传输变化的层)
-
示例:
bash当你运行docker run命令时,Docker会执行以下步骤: a) 检查本地是否有该镜像。如果没有,就从Docker Hub或指定的registry下载。 b) 创建一个新的容器。 c) 为容器分配一个文件系统,并在镜像的最顶层添加一个可写层。 d) 分配网络/桥接接口。 e) 设置IP地址。 f) 执行你在docker run命令中指定的程序。 g) 捕获并返回应用程序的输出。
-
这种结构带来了几个重要优势:
- 共享资源:多个容器可以共享同一个bootfs,节省了内存。
- 快速启动:由于bootfs很小且共享,容器可以非常快速地启动。
- 层级管理:每一层的变化都可以被单独管理,便于版本控制和回滚。
通过这种方式,Docker实现了高效的镜像管理和快速的容器启动。
分层理解(镜像层 & 容器层)
1. 容器读写层(Container read-write layer)
后续就叫容器层了方便理解。当你基于一个镜像启动一个容器时,Docker会在镜像的最顶层添加一个可写的容器层。
2. 镜像层 (Image Layers)
镜像层是构建Docker镜像的基础。每个镜像由多个只读层组成,这些层堆叠在一起形成最终的镜像。
3. 镜像层和容器层的关系
- 镜像层提供了容器的基础文件系统。
- 容器层允许对运行中的容器进行修改,而不影响底层的镜像。
- 当需要修改一个文件时,Docker使用写时复制(Copy-on-Write)策略:
- 首先,Docker从镜像层复制文件到容器层。
- 然后,在容器层中进行修改。
- 后续的读取操作会从容器层获取修改后的文件。
4. Copy-on-Write 策略
这是Docker用来优化存储空间和提高性能的一种重要机制。
定义
Copy-on-Write是一种延迟复制的优化策略。当多个进程共享同一块数据时,如果某个进程需要进行修改,系统只有在真正写入时才会复制一份数据
5. 工作原理
- 读取文件:如果文件存在于容器层,直接读取;如果不存在,则从镜像层读取。
- 写入文件:如果文件不在容器层,Docker会将文件复制到容器层然后修改。如果文件已在容器层,直接在容器层修改。这就是"写时复制"的由来。
- 删除文件:Docker在容器层创建一个特殊的删除标记,遮蔽下层的同名文件。
6. 优势
- 空间效率:多个容器可以共享相同的底层镜像层,只存储差异部分。
- 快速启动:创建新容器时,无需复制整个文件系统。启动新容器只需添加一个新的容器层,非常快速。
- 性能优化:读操作性能良好,因为大多数文件不会被修改。
- 增量更新:更新镜像只需要添加或替换特定的层。
7. 注意事项
- 写操作可能会带来性能开销,特别是对大文件的首次写入。
- 需要合理规划镜像层,以平衡层数和文件更新频率。过多的层可能会影响性能,因此在构建镜像时应该适当控制层数。
- 容器层中的更改在容器被删除后就会消失。如果需要持久化数据,应该使用Docker卷(Volumes)。
杂项补充
Docker Commit
docker commit
命令会从一个运行中(或停止)的容器创建一个新的镜像。这个新镜像包含了原始镜像的内容,以及在容器中所做的所有更改。
bash
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
常用选项包括:
-a, --author
: 指定作者-m, --message
: 提交信息-p, --pause
: 在提交时暂停容器(默认行为)
使用场景
- 开发过程中保存中间状态
- 调试和故障排除
- 创建自定义镜像
- 将运行时配置保存到新镜像中
最佳实践
- 优先使用 Dockerfile 来创建镜像
- 仅在必要时使用 docker commit
- 在提交前,停止容器并删除不必要的文件
- 为新创建的镜像添加清晰的标签和描述
虽然 docker commit
是一个有用的工具,但在生产环境中,通常推荐使用 Dockerfile 来创建可重现的镜像构建过程。docker commit
主要用于开发、测试和调试阶段。
Docker Image Tags
使用镜像标签是 Docker 最佳实践的重要部分。它们帮助我们有效地管理镜像版本,便于回滚和部署特定版本的应用。通过合理使用标签,我们可以更好地控制应用的生命周期和版本历史。
标签的类型
- 版本标签:如
v1.0
,2.1.3
- 描述性标签:如
latest
,stable
,dev
- 日期标签:如
20230701
相关命令
docker tag
: 为镜像添加新标签docker rmi
: 删除镜像(可以指定标签)docker inspect
: 查看镜像详细信息
bash
docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
docker rmi [OPTIONS] IMAGE [IMAGE...]
docker inspect [OPTIONS] NAME|ID [NAME|ID...]
Docker ID
理解和正确使用 Docker ID 对于有效管理 Docker 环境、确保操作的精确性和提高工作流程的自动化程度非常重要。无论是在开发、测试还是生产环境中,熟练运用 Docker ID 都能帮助您更好地控制和管理 Docker 资源。
Docker ID 主要指两个概念:
a) 用户账户标识符:用于 Docker Hub 和其他 Docker 服务的唯一用户标识。
b) 容器或镜像的唯一标识符:用于在本地 Docker 环境中标识特定的容器或镜像。
Docker 用户账户 ID
- 这是您在 Docker Hub 注册时创建的唯一用户名。
- 用于登录 Docker Hub、拉取私有镜像、推送镜像等操作。
容器 ID
- 每个 Docker 容器都有一个唯一的 ID。
- 通常是一个长的十六进制字符串。
- 也有短 ID,是完整 ID 的前 12 个字符。
容器管理
bash
docker start 1234abcd
docker stop 1234abcd
docker restart 1234abcd
docker rm 1234abcd
docker logs 1234abcd
镜像 ID
- 每个 Docker 镜像也有一个唯一的 ID。
- 同样是一个长的十六进制字符串,也有短 ID 版本。
镜像管理
bash
删除镜像:docker rmi 5678efgh
基于特定镜像创建容器:docker run -d 5678efgh
参考
https://cto.ai/blog/docker-image-vs-container-vs-dockerfile/