在文件更改时重建Docker容器


86

为了运行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似乎建议构建一个新映像而不是修改现有容器,因此我认为像我一样进行重建的一般方法是正确的,但是实现中的一些细节必须改进。


您能否列出构建容器所采取的确切步骤,包括构建命令以及每个命令的全部输出?
BMitch '16

Answers:


136

经过一些研究和测试,我发现我对Docker容器的生命存在一些误解。在此期间重建映像时,仅重新启动容器并不会使Docker使用新映像。相反,Docker仅获取映像之前创建容器。因此,运行容器后的状态是持久的。

为什么需要删除

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

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

容器应尽可能独立

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

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

为什么-rf不能真正帮助您

docker run命令有一个名为的清理开关-rf。它将停止永久保留docker容器的行为。使用-rf,Docker将在退出容器后销毁该容器。但是此开关有两个问题:

  1. Docker还删除了没有与容器关联的名称的卷,这可能会杀死您的数据
  2. 使用此选项,将无法使用-dswitch在后台运行容器

虽然该-rf开关是节省开发过程中的工作以进行快速测试的不错选择,但它不适用于生产环境。特别是由于缺少在后台运行容器的选项,因此通常需要这样做。

如何取出容器

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

docker rm --force <ContainerName>

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

docker stop <ContainerName>
docker rm <ContainerName>

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

用于重建Docker容器的完整脚本

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

#!/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

这完美地工作:)


4
“我发现我对Docker容器的生命存在一些误解”,您直言不讳。感谢您的详细解释。我会推荐给Docker新手。这阐明了VM与容器的区别。
文斯·班宗

2
在您解释之后,我要做的就是记下我对现有图像所做的事情。为了保留更改,我创建了一个新的Dockerfile来创建一个新映像,该映像已经包含了要添加的更改。这样,创建的新图像就会(有所更新)。
文斯·班松

docker上的--force-recreate选项是否与您在此描述的内容相似?如果是这样,使用这个解决方案代替(值得一提的是,这个问题很愚蠢,但我是一个
docker

2
@cglacet是的,它是相似的,不能直接比较。但是docker-compose比普通docker命令更聪明。我经常与之合作docker-compose,变更检测效果很好,因此我--force-recreate很少使用。只是docker-compose up --build当你建立一个自定义图像(是非常重要的build指导在撰写文件),而不是使用从例如泊坞枢纽的图像。
狮子

33

每当在dockerfile或compose或需求中进行更改时,请使用重新运行它docker-compose up --build。这样就可以重建和刷新图像


1
将MySQL docker容器作为一项服务,如果一个人将卷用于该数据库,那之后DB是否会为空/opt/mysql/data:/var/lib/mysql
马丁·托马

在我看来,仅--build在本地开发环境中使用似乎没有任何不利之处。泊坞窗重新复制文件的速度仅需要几毫秒,否则它可能不需要复制,因此可以节省大量的WTF时刻。
Danack

0

您可以build通过运行docker-compose up --build <service name>服务名称必须与您在docker-compose文件中的调用方式匹配的位置来运行特定服务。

示例 假设您的docker-compose文件包含许多服务(.net应用程序-数据库-让我们加密...等),并且您只想更新application在docker-compose文件中命名为.net的应用程序。然后,您可以简单地运行docker-compose up --build application

附加参数 如果要向命令中添加附加参数(例如-d在后台运行),则该参数必须在服务名称之前: docker-compose up --build -d application

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.