如何更新Docker镜像?

26

我读到 Docker 是通过层来工作的,因此在使用 Dockerfile 创建 container 时,从基础镜像开始,后续命令将向容器添加一层,因此如果您保存了该新容器的状态,则会得到一个新镜像。这里有几个问题我想知道。

如果我从一个相当大而笨重的Ubuntu映像开始,因为它是完整的操作系统,然后我添加了一些工具并将其保存为一个新映像,然后上传到hub上。如果有人下载我的镜像,并且他们已经在他们的images folder中保存了一个Ubuntu映像,这是否意味着他们可以跳过下载Ubuntu,因为他们已经有了镜像?如果是这样的话,当我修改原始镜像的某些部分时,它是如何工作的?Docker是否使用其缓存数据选择性地将这些更改应用于之前加载的Ubuntu镜像?

2.) 如何通过修改Dockerfile来更新我构建的映像?我使用以下Dockerfile设置了一个简单的django项目:

FROM python:3.5

ENV PYTHONBUFFERED 1
ENV APPLICATION_ROOT /app
ENV APP_ENVIRONMENT L

RUN mkdir -p $APPLICATION_ROOT
WORKDIR $APPLICATION_ROOT
ADD requirements.txt $APPLICATION_ROOT
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
ADD . $APPLICATION_ROOT

然后使用它来创建开头的图像。所以每次我创建一个盒子,它都会加载所有这些 环境变量,如果我完全重建盒子,则重新安装软件包和所有其他内容。我需要添加一个新的环境变量,所以我将其添加到 Dockerfile 的底部,以及一个测试变量:

ENV COMPOSE_CONVERT_WINDOWS_PATHS 1
ENV TEST_ENV_VAR TEST

当我删除容器和镜像,并构建一个新的容器时,一切似乎都按计划进行,它告诉我它创建了新的第四步:ENV。

COMPOSE_CONVERT_WINDOWS_PATHS 1
 ---> Running in 75551ea311b2
 ---> b25b60e29f18
Removing intermediate container 75551ea311b2

就像在一些中间容器转换中有东西丢失了一样。这就是缓存系统的工作方式吗,每一个新层都是一个“中间容器”?那么考虑到这一点,如何添加新层,您是否总是需要在 Dockerfile 的底部添加新数据?还是在构建完镜像后最好保持 Dockerfile 不变,只修改“容器”并构建一个新镜像?

编辑 我刚试图安装一个叫做bwawrik/bioinformatics的包,它是一个基于CentOS的容器,里面安装了很多工具。

它在一半的时候冻结了,所以我退出了它,然后再次运行它,看看是否安装了所有内容:

$ docker pull bwawrik/bioinformatics
Using default tag: latest
latest: Pulling from bwawrik/bioinformatics

a3ed95caeb02: Already exists
a3ed95caeb02: Already exists
7e78dbe53fdd: Already exists
ebcc98113eaa: Already exists
598d3c8fd678: Already exists
12520d1e1960: Already exists
9b4912d2bc7b: Already exists
c64f941884ae: Already exists
24371a4298bf: Already exists
993de48846f3: Already exists
2231b3c00b9e: Already exists
2d67c793630d: Already exists
d43673e70e8e: Already exists
fe4f50dda611: Already exists
33300f752b24: Already exists
b4eec31201d8: Already exists
f34092f697e8: Already exists
e49521d8fb4f: Already exists
8349c93680fe: Already exists
929d44a7a5a1: Already exists
09a30957f0fb: Already exists
4611e742e0b5: Already exists
25aacf0148db: Already exists
74da82504b6c: Already exists
3e0aac083b86: Already exists
f52c7e0ac000: Already exists
35eee92aaf2f: Already exists
5f6d8eb70885: Already exists
536920bfe266: Already exists
98638e678c51: Already exists
9123956b991d: Already exists
1c4c8a29cd65: Already exists
1804bf352a97: Already exists
aa6fe9359956: Already exists
e7e38d1250a9: Already exists
05e935c831dc: Already exists
b7dfc22c26f3: Already exists
1514d4797ffd: Already exists
Digest: sha256:0391808e21b7b5cc0eb44fc2dad0d7f5415115bdaafb4534c0b6a12efd47a88b
Status: Image is up to date for bwawrik/bioinformatics:latest

所以它肯定是分段安装软件包的,而不是一次性全部安装。这些分段是不同的镜像吗?


2
如果您看到这个问题并认为“更新”意味着“获取最新版本”,那么请注意:docker pull <image> - MakisH
1个回答

81

镜像与容器

首先,让我澄清一些术语。

镜像:一个静态、不可变的对象。这是你使用 Dockerfile 运行 docker build 时构建的东西。镜像本身不能运行。

镜像由多个层组成,可能只有一层,也可能有多层。

容器:一个正在运行的东西。它以一个镜像作为起始模板。

这类似于二进制程序和进程的关系。你在磁盘上有一个二进制程序(例如 /bin/sh),当你运行它时,它是你系统上的一个进程。镜像和容器之间的关系就类似于此。

向基础镜像添加层

你可以从一个基础镜像(例如你示例中的 ubuntu)构建自己的镜像。在你的 Dockerfile 中的某些命令将会在最终镜像中创建一个新层。其中一些命令包括 RUNCOPYADD

第一层没有父层。但是每个其他层都将有一个父层。它们以此链接在一起,像叠饼一样堆叠。

每个层都有一个唯一的 ID(你已经看到的那些长十六进制哈希值)。它们也可以拥有人类友好的名称,称为 标签(例如 ubuntu:16.04)。

层与镜像之间的区别是什么?

从技术上讲,每个层也是一个镜像。如果你构建一个新的镜像并且它有 5 层,你可以使用该镜像,并且它将包含所有 5 层。如果你使用栈中第三层作为你的镜像 ID 来运行容器,你也可以这样做,但它只会包含 3 层。你指定的那一层和它的两个祖先。

但作为惯例,“镜像”一般意味着具有关联标签的那一层。当你运行 docker images 时,它将显示所有顶级镜像,并隐藏其下面的层(但你可以使用 -a 显示所有镜像)。

什么是中间容器?

当运行 docker build 时,它会在容器内部进行所有工作(自然而然!)。因此,如果遇到 RUN 步骤,它将从当前顶层创建一个容器,在其中运行指定的命令,然后将结果保存为新层。然后它将从这个新层创建一个容器,运行下一步操作...等等。

中间容器仅用于构建过程,并在构建后被丢弃。

层文件系统如何工作

你问是否正在下载以你的基于 ubuntu 镜像为基础的镜像时,如果他们已经在本地有了 ubuntu 镜像,那么它们只会进行部分下载。

是的!完全正确。

每个

FROM: ubuntu:16.04
RUN groupadd dan && useradd -g dan dan

这将导致一个两层的镜像。第一层是ubuntu镜像。第二个可能只有少量更改:

  • 具有用户“dan”的新版本/etc/passwd
  • 具有组“dan”的新版本/etc/group
  • 一个新目录/home/dan
  • 一些默认文件,如/home/dan/.bashrc

就是这样。如果您从此镜像启动容器,则这几个文件将在最上层,其他内容将来自于ubuntu镜像文件系统。

容器中最顶层的读写层

另一个要点。当您运行容器时,可以在文件系统中编写文件。但是,如果停止容器并从相同的镜像运行另一个容器,则所有内容都将重置。那么写入的文件在哪里?

镜像是不可变的,因此一旦创建,它们就无法更改。您可以构建新版本,但那是一个新的镜像。它将具有不同的ID,而且不会是同一镜像。

容器具有一个放置在镜像层之上的最高级读写层。任何写入都发生在该层中。它的工作方式与其他层相同。如果需要修改文件(或添加文件,或删除文件),则在顶层执行,不影响下层。如果文件已经存在,则将其复制到读写层中,然后进行修改。这被称为写时复制(CoW)。

更改位置

您必须将新内容添加到Dockerfile的底部吗?不必,您可以在任何地方添加任何内容(或更改任何内容)。

但是,如何操作会影响构建时间,因为构建缓存的工作方式。

Docker将尝试在构建过程中缓存结果。如果在阅读Dockerfile时发现FROM相同,第一个RUN相同,第二个RUN相同...它将假定已经执行过这些步骤,并将使用缓存结果。如果遇到与上次构建不同的内容,它将使缓存无效。从那点开始的所有内容都将重新运行。

某些内容始终会使缓存失效。例如,如果您使用ADDCOPY,则它们始终会使缓存失效。这是因为Docker仅跟踪构建命令。它不尝试弄清楚“我正在复制的文件版本与上次相同吗?”

因此,通常的做法是从FROM开始,然后放置非常静态的东西,例如使用apt-get 安装软件包的RUN命令等。在您的Dockerfile被初始编写后,这些事情往往不会经常更改。稍后在文件中更改更频繁的内容更加方便。

很难简要地给出好的建议,因为这真的取决于具体项目。但是学习构建缓存的工作方式并尝试利用它确实是值得的。


1
非常好的解释,适合像我这样的初学者。您能否添加有关其他重要方面的信息,例如Docker服务器、Linux chroot监狱? - Mudit Jain
--no-cache选项会在拉取基础镜像时忽略缓存,但如果基础镜像已经远程更新,它是否会使其失效并重新拉取呢? - Sentinel

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