Docker镜像和容器有什么区别?

1230

使用Docker时,我们从一个基础镜像开始。我们启动它,进行更改,这些更改会被保存在不同的层中,形成另一个镜像。

因此,我最终拥有一个用于PostgreSQL实例的镜像和一个用于Web应用程序的镜像,对其进行的更改将继续存在。

什么是容器?


截至2022年,Docker已经几乎“死亡”。因此,问题和答案应该转化为更通用的形式,以符合OCI规范/镜像和容器定义。 - user3192295
9
比起OCI,更多的人知道什么是Docker。那么是否每个macOS相关问题都应该标记为“Darwin”呢? - btraas
32个回答

1611

一个镜像的实例被称为容器。你拥有一张照片,它是由多个层次组成的。如果你启动这张照片,你就会得到一个正在运行的该镜像的容器。你可以同时拥有多个相同镜像的运行中容器。

你可以使用docker images命令查看所有镜像,而使用docker ps命令则可以查看正在运行的容器 (使用docker ps -a命令可以查看所有容器)。

因此,镜像的运行实例即是容器。


178
一个镜像和一个停止的容器有什么区别? - Victor Dombrovsky
500
图像是食谱,容器就是蛋糕;-) 你可以用给定的食谱做出任意多的蛋糕。 - Julien
235
一个停止运转的集装箱就像是放在冰箱里的蛋糕。 - Jacob Ford
85
如果这张图片是食谱,那Dockerfile又是什么呢? :) - Johnny Willer
137
类比有其局限性,但也许我们可以将Dockerfile看作食材清单;否则就称Dockerfile为食谱,镜像为模具,容器仍然是美味的蛋糕。 - Julien
显示剩余10条评论

698

以下是我关于自动化Docker部署(已存档)的文章内容:

Docker镜像和容器

在Docker中,有镜像容器两个概念。它们紧密相关但又不同。对我来说,理解这种二分法极大地澄清了Docker。

什么是镜像?

镜像是一种惰性、不可变的文件,本质上是容器的快照。镜像是使用build命令创建的,并且当使用run启动时会生成一个容器。镜像存储在Docker仓库中,例如registry.hub.docker.com。由于镜像可能变得非常大,因此设计为由其他镜像层组成,从而在通过网络传输镜像时只需发送最小数量的数据。

本地镜像可以通过运行docker images列出:
REPOSITORY                TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu                    13.10               5e019ab7bf6d        2 months ago        180 MB
ubuntu                    14.04               99ec81b80c55        2 months ago        266 MB
ubuntu                    latest              99ec81b80c55        2 months ago        266 MB
ubuntu                    trusty              99ec81b80c55        2 months ago        266 MB
<none>                    <none>              4ab0d9120985        3 months ago        486.5 MB

需要注意的一些事情:

  1. IMAGE ID 是一个镜像的真实标识符的前12个字符。您可以为给定镜像创建许多标签,但它们的 ID 都将相同(如上所述)。
  2. VIRTUAL SIZE 是“虚拟”的,因为它将所有不同的底层层的大小相加。这意味着该列中所有值的总和可能比所有这些镜像使用的磁盘空间大得多。
  3. REPOSITORY 列中的值来自 docker build 命令的 -t 标志,或者来自对现有镜像进行 docker tag。您可以使用适合自己的命名法对图像进行标记,但是请注意,docker 将使用标记作为 docker push 或 docker pull 中的注册表位置。
  4. 标记的完整格式为 [REGISTRYHOST/][USERNAME/]NAME[:TAG]。对于上面的 ubuntu,REGISTRYHOST 推断为 registry.hub.docker.com。因此,如果您计划在 docker.example.com 的注册表中存储名为 my-application 的映像,您应该将该映像标记为 docker.example.com/my-application。
  5. TAG 列只是完整标记的 [:TAG] 部分。这是不幸的术语。
  6. latest 标记并非神奇,只是在不指定标记时的默认标记。
  7. 您可以拥有未标记的图像,仅可通过其 IMAGE ID 进行识别。这些将获得 <none> TAG 和 REPOSITORY。很容易忘记它们。

关于图片的更多信息,请参考Docker文档术语表

什么是容器?

用编程的比喻来说,如果一个镜像是一个类,那么一个容器就是该类的实例——一个运行时对象。容器是您使用Docker的原因;它们是轻量级和可移植的封装,用于运行应用程序的环境。

使用docker ps查看本地正在运行的容器:

CONTAINER ID        IMAGE                               COMMAND                CREATED             STATUS              PORTS                    NAMES
f2ff1af05450        samalba/docker-registry:latest      /bin/sh -c 'exec doc   4 months ago        Up 12 weeks         0.0.0.0:5000->5000/tcp   docker-registry

我正在运行一个docker化的docker注册表,以便我有一个私人存储图像的地方。需要注意以下几点:
  1. 与IMAGE ID相似,CONTAINER ID是容器的真正标识符。它具有相同的形式,但标识了不同类型的对象。
  2. docker ps仅输出正在运行的容器。您可以使用docker ps -a查看所有容器(正在运行或已停止)。
  3. NAMES可用于通过--name标志标识已启动的容器。

如何避免镜像和容器的堆积

Docker早期让我感到沮丧的一件事就是似乎不断积累未标记的镜像和已停止的容器。在少数情况下,这种堆积会导致我的硬盘被占满,从而减慢我的笔记本电脑或停止我的自动构建流程。简直是“无处不在的容器”!

我们可以将docker rmi与最近的dangling=true查询结合起来,删除所有未标记的镜像:

docker images -q --filter "dangling=true" | xargs docker rmi

Docker无法删除位于现有容器后面的镜像,因此您可能需要先使用docker rm删除已停止的容器:

docker rm `docker ps --no-trunc -aq`

以下是有关Docker的已知痛点,这些问题可能会在未来版本中得到解决。然而,只要对镜像和容器有清晰的理解,并遵循以下几个实践,就可以避免这些情况:
  1. 始终使用docker rm [CONTAINER_ID]命令删除无用的已停止容器。
  2. 始终使用docker rmi [IMAGE_ID]命令删除无用已停止容器背后的镜像。

9
良好的图像和容器区分非常有助于像我这样的初学者。请问您需要进一步的翻译吗? - Gibbs
2
我想我卡住的地方是图像如何运行(我在Windows上使用boot2docker)。为什么我们要为应用程序创建镜像,比如mysql?此时,mysql是如何运行的?我不需要一个Linux镜像来运行mysql吗? - Kenny Worden
实际上这并不正确:“docker pull”最新标签的镜像将至少向您的本地镜像列表中添加两个镜像:一个带有最新标签,另一个是最新镜像的每个原始标签,例如14.04和trysty。它只会添加一个带有最新标签的镜像。如果图像ID相同,则稍后拉取14.04可能是无操作,但仍需要单独拉取。 - Adrian Mouat
这也不是真的:“在docker pull中指定时,最新的标签总是指向镜像的最新版本。” 最新的标签只是指向使用“latest”作为标签推送的任何内容。棘手的问题是,如果没有指定标签,则将“latest”用作默认标签。 - Adrian Mouat
8
在较新版本的 Docker 中,您可以使用 docker image prune 命令清理悬空镜像。修剪未使用的 Docker 对象 - Dario Seidl
5
我刚刚使用了 docker system prune 命令清除了所有东西。 - Rami Alloush

155

虽然最简单的理解容器就是运行的镜像,但这并不是完全准确的。

实际上,镜像只是一个模板,可以被转换成容器。为了将镜像转换为容器,Docker引擎会在其上添加可读写的文件系统,并初始化各种设置,包括网络端口、容器名称、ID和资源限制。正在运行的容器具有当前执行的进程,但容器也可以被停止(或按Docker的术语说是“退出”)。退出的容器与镜像不同,因为它可以重新启动,保留其设置和任何文件系统更改。


3
如何将图像转换为容器而不运行它? - Janus Troelsen
19
请使用docker create命令。 - Adrian Mouat
2
这有点令人困惑。我们说图像是不可变的,但当作为容器运行时,它会将任何更改存储到可变的顶层,就像您所说的那样。但是停止后,这些更改是否保存为图像中的新层?如果是的话,原始图像怎么可能是不可变的呢? - Dchucks
7
好的,我阅读了这个帖子并得到了答案。 "当容器被删除时,可写层也会被删除,底层镜像保持不变。" - Dchucks
非常有帮助的回复。我之前很困惑。如果我下载一个镜像,将其作为容器运行,在该容器中放入一个随机文本文件,然后停止容器,那么文本文件会在已停止的容器中,但不会出现在我下载的基础镜像中。 - James Allen

112

也许解释整个工作流程可以有所帮助。

一切都始于 Dockerfile。Dockerfile 是镜像的源代码。

创建了 Dockerfile 后,您需要构建它以创建容器的镜像。 镜像只是“源代码” Dockerfile 的“编译版本”。

获取容器镜像后,您应使用注册表重新分发它。 注册表类似于 Git 存储库,您可以推送和拉取镜像。

接下来,您可以使用镜像运行 容器。 运行中的容器在许多方面非常类似于虚拟机(但没有hypervisor)。


79

Dockerfile → (构建) → 镜像 → (运行) → 容器.

  • Dockerfile: 包含一组 Docker 指令,用于按照您的喜好配置您的操作系统,并安装/配置所有软件。

  • 镜像: 编译后的 Dockerfile。可以节省您每次需要运行容器时重新构建 Dockerfile 的时间。并且它也是隐藏您配置代码的一种方式。

  • 容器: 虚拟操作系统本身。您可以 ssh 进入其中并运行任何命令,就像它是一个真实环境一样。您可以从同一个镜像运行 1000 多个容器。


59

工作流程

以下是端到端的工作流程,显示各个命令及其相关输入和输出。这应该能够澄清图像和容器之间的关系。

+------------+  docker build   +--------------+  docker run -dt   +-----------+  docker exec -it   +------+
| Dockerfile | --------------> |    Image     | --------------->  | Container | -----------------> | Bash |
+------------+                 +--------------+                   +-----------+                    +------+
                                 ^
                                 | docker pull
                                 |
                               +--------------+
                               |   Registry   |
                               +--------------+
  • 要列出可运行的镜像,请执行以下命令:
docker image ls

列出您可以执行命令的容器:

docker ps

1
更全面的工作流程图,请参见此链接:https://stackoverflow.com/a/46528745/714112 - Sridhar Sarnobat
2
但是在那个 ASCII 艺术中所需的滚动是一个问题。 - Peter Mortensen
Unicode 可以用于获取更漂亮的框。在线工具是 Javascript Box Drawing Demo - Peter Mortensen

52

尽管我已经阅读了这里所有问题的答案,但我仍然无法理解图像 的概念。最终我偶然发现了Docker的出色文档(当然啦!)。

该文档中的示例确实是理解整个概念的关键。由于该文章很长,因此我将总结需要真正掌握以获得清晰度的关键要点。

  • 图像:Docker镜像由一系列只读层构建而成

  • :每个层代表镜像的Dockerfile中的一条指令

示例:下面的Dockerfile包含四个命令,每个命令都创建了一个层。

FROM ubuntu:15.04

COPY . /app

RUN make /app

CMD python /app/app.py

重要的是,每个层与它之前的层之间仅有差异。

  • 容器。 当您创建一个新容器时,在基础层上添加了一个新的可写层。这个层通常称为“容器层”。所有对运行容器所做的更改(例如编写新文件、修改现有文件和删除文件)都会写入这个可写容器层。

因此,容器与镜像之间的主要区别是顶部可写层。添加新数据或修改现有数据的所有写入容器的操作都存储在该可写层中。删除容器时,可写层也将被删除。底层镜像保持不变。

从磁盘大小的角度理解图像和容器

要查看正在运行的容器的大约大小,您可以使用 docker ps -s 命令。您将获得 sizevirtual size 作为输出之一:

  • Size:每个容器可写层使用的(磁盘上的)数据量

  • Virtual Size:容器使用的只读图像数据的数据量。多个容器可能共享某些或所有只读图像数据。因此,这些数据不是可加的。即,您不能将所有虚拟大小相加以计算图像占用了多少磁盘空间

另一个重要的概念是写时复制策略

如果文件或目录存在于镜像内的较低层,并且另一层(包括可写层)需要读取它,则直接使用现有文件。当另一层(构建镜像或运行容器时)第一次需要修改该文件时,该文件被复制到该层并进行修改。

希望这能够帮助像我这样的其他人。


2
感谢您的评论,它确认了大小和虚拟大小之间的差异,并且对于共享相同只读数据的多个容器非常有趣,这是磁盘空间的收益。 - user1842947

19

简单来说,如果一张图片是一个,那么一个容器就是该类的一个运行时对象实例。


14

一个容器只是一个可执行二进制文件,由主机操作系统在使用应用程序(例如Docker)预设的一组限制下运行。这个应用程序知道如何告诉操作系统应该应用哪些限制。

典型的限制与进程隔离、安全相关(例如使用SELinux保护)以及系统资源相关(内存、磁盘、CPU和网络)有关。

直到最近,只有基于Unix的系统内核支持在严格限制下运行可执行文件的能力。因此,今天大多数容器讨论都涉及Linux或其他Unix发行版。

Docker是那些应用程序之一,它知道如何告诉操作系统(主要是Linux)在什么限制下运行可执行文件。可执行文件包含在Docker镜像中,这只是一个tar文件。该可执行文件通常是Linux发行版用户空间的简化版本(如Ubuntu、CentOS、Debian等),预配置了一个或多个应用程序的运行环境。

尽管大多数人使用Linux作为可执行文件的基础,但只要主机操作系统的内核能够运行它(参见使用scratch创建简单基础镜像),它也可以是任何其他二进制应用程序。无论Docker镜像中的二进制文件是操作系统用户空间还是仅仅是一个应用程序,在操作系统主机看来,它只是另一个进程,由预设的操作系统边界控制的包含进程。
其他应用程序,例如LXClibvirtsystemd,也可以像Docker一样在运行时告诉主机操作系统应用哪些边界。Docker曾经使用这些应用程序来间接地与Linux操作系统交互,但现在Docker使用自己的库“libcontainer”直接与Linux交互。

因此,容器只是以受限模式运行的进程,类似于chroot所做的。

在我看来,Docker与任何其他容器技术不同之处在于其存储库(Docker Hub)及其管理工具,这使得使用容器非常容易。

请参见Docker(软件)


一个容器不是可执行二进制文件。 - Clearer

13

Docker 的核心概念是让创建“机器”变得容易,这些“机器”可以被视为容器。容器有助于可重用性,允许您轻松地创建和销毁容器。

镜像描述了容器在每个时间点的状态。因此,基本的工作流程是:

  1. 创建一个镜像
  2. 启动一个容器
  3. 更改容器
  4. 将容器另存为镜像

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接