在文件更改时重建 Docker 容器

新手上路,请多包涵

为了运行 ASP.NET Core 应用程序,我生成了一个 dockerfile,它构建应用程序并复制容器中的源代码,由 Git 使用 Jenkins 获取。因此,在我的工作区中,我在 dockerfile 中执行以下操作:

 WORKDIR /app
COPY src src

虽然 Jenkins 使用 Git 正确更新了我主机上的文件,但 Docker 并没有将其应用于我的图像。

我的基本构建脚本:

 #!/bin/bash
imageName=xx:my-image
containerName=my-container

docker build -t $imageName -f Dockerfile  .

containerRunning=$(docker inspect --format="{{ .State.Running }}" $containerName 2> /dev/null)

if [ "$containerRunning" == "true" ]; then
        docker stop $containerName
        docker start $containerName
else
        docker run -d -p 5000:5000 --name $containerName $imageName
fi

我尝试了不同的东西,比如 --rm--no-cache 参数 docker run 并且还在构建新容器 之前 停止/删除容器。我不确定我在这里做错了什么。似乎 docker 正在正确更新图像,因为调用 COPY src src 会导致层 id 并且没有缓存调用:

 Step 6 : COPY src src
 ---> 382ef210d8fd

更新容器的推荐方法是什么?

我的典型场景是:应用程序在 Docker 容器中的服务器上运行。现在,应用程序的某些部分已更新,例如通过修改文件。现在容器应该运行新版本。 Docker 似乎建议构建一个新的镜像而不是修改一个现有的容器,所以我认为像我这样做的重建的一般方式是正确的,但是实现中的一些细节必须改进。

原文由 Lion 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.4k
2 个回答

带有视觉解释的视频(从 2022 年开始)

视觉视频解释容器与图像

由于 我之前的第一个视觉解释 得到了很多积极的反馈,我决定为这个问题和答案创建另一个视频,因为有些东西可以在图形视频中更好地可视化。它可视化并使用我在过去几年在多个系统(以及 K8s)上使用 Docker 获得的知识和经验来更新这个答案。

虽然这个问题是在 ASP.NET Core 的上下文中提出的,但它与这个框架并没有真正的关系。问题是缺乏对 Docker 概念的基本理解,因此几乎每个应用程序和框架都会发生这种情况。出于这个原因,我在这里使用了一个简单的 Nginx 网络服务器,因为我想你们中的许多人都熟悉网络服务器,但并不是每个人都知道像 ASP.NET Core 这样的特定框架是如何工作的。

根本问题是了解容器与图像的区别以及它们在生命周期中的不同之处,这是本视频的基本主题。

文字答案(最初来自 2016 年)

经过一番研究和测试,我发现我对 Docker 容器的生命周期存在一些误解。当镜像同时重建时,简单地重新启动容器不会使 Docker 使用新镜像。相反,Docker 仅 创建容器之前获取图像。所以容器运行后的状态是持久的。

为什么需要删除

因此,重建和重新启动是不够的。我认为容器就像服务一样工作:停止服务,进行更改,重新启动它,它们就会应用。那是我最大的错误。

因为容器是永久性的,所以您必须首先使用 docker rm <ContainerName> 删除它们。移除容器后,不能简单地通过 docker start 启动它。这必须使用 docker run 来完成,它本身使用最新的图像来创建新的容器实例。

容器应尽可能独立

有了这些知识,就可以理解为什么将数据存储在容器中被 认为是不好的做法,而 Docker 建议改为使用 数据卷/挂载主机目录:由于必须销毁容器才能更新应用程序,因此其中存储的数据也会丢失。这会导致额外的工作来关闭服务、备份数据等。

因此,将这些数据完全从容器中排除是一个明智的解决方案:当数据安全地存储在主机上并且容器只保存应用程序本身时,我们不必担心我们的数据。

为什么 -rf 可能对你没有帮助

docker run 命令有一个名为 -rf清理 开关。它将停止永久保留 docker 容器的行为。使用 -rf ,Docker 将在容器退出后销毁容器。但是这个开关有一个问题:Docker 也会删除没有与容器关联的名称的卷,这可能会杀死你的数据

虽然 -rf 开关是在开发期间节省工作以进行快速测试的好选择,但它不太适合生产。特别是因为缺少在后台运行容器的选项,而这通常是必需的。

如何移除容器

我们可以通过简单地移除容器来绕过这些限制:

 docker rm --force <ContainerName>

--force (或 -f )开关在运行的容器上使用 SIGKILL。相反,您也可以在之前停止容器:

 docker stop <ContainerName>
docker rm <ContainerName>

两者是平等的。 docker stop 也在使用 SIGTERM 。但是使用 --force 开关会缩短您的脚本,尤其是在使用 CI 服务器时: docker stop 如果容器未运行,则会引发错误。这将导致 Jenkins 和许多其他 CI 服务器错误地将构建视为失败。要解决此问题,您必须首先检查容器是否像我在问题中所做的那样运行(请参阅 containerRunning 变量)。

有更好的方法(2016 年添加)

虽然像 docker builddocker run 等简单的 docker 命令是初学者理解基本概念的好方法,但当您已经熟悉 Docker 并希望提高工作效率时,它会变得很烦人。更好的方法是使用 Docker-Compose。虽然它是为多容器环境而设计的,但在使用单个容器独立使用时,它也会给您带来好处。 Altough 多容器环境并不少见。几乎每个应用程序都至少有一个应用程序服务器和一些数据库。有些甚至更喜欢缓存服务器、cron 容器或其他东西。

 version: "2.4"
services:
  my-container:
    build: .
    ports:
      - "5000:5000"

现在您可以使用 docker-compose up --build 并且 compose 会处理我手动执行的所有步骤。我更喜欢这个脚本而不是带有普通 docker 命令的脚本,这是我从 2016 年开始添加的答案。它仍然有效,但更复杂,它可以处理某些情况,不如 docker-compose 好。例如,compose 检查是否所有内容都是最新的,并且只重建那些因更改而需要重建的东西。

特别是当您使用多个容器时,compose 提供了更多好处。例如,链接需要手动创建/维护网络的容器。您还可以指定依赖关系,以便在应用程序服务器之前启动一个数据库容器,该应用程序服务器在启动时依赖于数据库。

在过去使用 Docker-Compose 1.x 时,我注意到了一些问题,尤其是在缓存方面。这会导致容器没有被更新,即使发生了一些变化。我已经测试了 compose v2 一段时间,没有再次看到任何这些问题,所以它现在似乎已修复。

用于重建 Docker 容器的完整脚本(原始答案 vom 2016)

根据这些新知识,我通过以下方式修复了我的脚本:

 #!/bin/bash
imageName=xx:my-image
containerName=my-container

docker build -t $imageName -f Dockerfile  .

echo Delete old container...
docker rm -f $containerName

echo Run new container...
docker run -d -p 5000:5000 --name $containerName $imageName

这完美地工作:)

原文由 Lion 发布,翻译遵循 CC BY-SA 4.0 许可协议

每当对 dockerfile 或 compose 或需求进行更改时,请使用 docker-compose up --build 重新运行它。以便图像得到重建和刷新

原文由 yunus 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题