在文件更改时重建 Docker 容器

Posted

技术标签:

【中文标题】在文件更改时重建 Docker 容器【英文标题】:Rebuild Docker container on file changes 【发布时间】:2017-05-10 09:42:47 【问题描述】:

为了运行 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

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

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

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

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

【问题讨论】:

您能否列出构建容器的具体步骤,包括构建命令和每个命令的完整输出? 【参考方案1】:

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

为什么需要移除

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

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

容器应尽可能独立

有了这些知识,就可以理解为什么将数据存储在容器中是qualified as bad practice 而Docker 建议改为data volumes/mounting host directorys:由于必须销毁容器才能更新应用程序,因此其中存储的数据也会丢失。这会导致额外的工作来关闭服务、备份数据等。

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

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

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

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

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

如何移除容器

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

docker rm --force <ContainerName>

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

docker stop <ContainerName>
docker rm <ContainerName>

两者相等。 docker stop 也在使用SIGTERM。但是使用--force 开关会缩短您的脚本,尤其是在使用 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

这很好用:)

【讨论】:

“我发现我对 Docker 容器的生命周期有一些误解”,你把这句话从我嘴里说出来了。谢谢你这么详细的解释。我会向 docker 新手推荐这个。这阐明了 VM 与容器的区别。 经过您的解释,我所做的就是记下我对现有图像所做的操作。为了保留更改,我创建了一个新的 Dockerfile 来创建一个新映像,其中已经包含我要添加的更改。这样一来,创建的新图像就会(有些)更新。 docker compose 上的--force-recreate 选项是否与您在此处描述的相似?如果是这样,那么使用这个解决方案不值得吗(对不起,如果这个问题很愚蠢,但我是一个 docker noob ^^) @cglacet 是的,它是相似的,不能直接比较。但是docker-compose 比普通的docker 命令更智能。我经常使用docker-compose,并且更改检测效果很好,所以我很少使用--force-recreate。当您构建自定义图像(撰写文件中的build 指令)而不是使用来自例如的图像时,docker-compose up --build 很重要。 Docker 中心。 你如何使用这个脚本?【参考方案2】:

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

【讨论】:

mysql docker 容器作为一项服务,如果使用 /opt/mysql/data:/var/lib/mysql 的卷,那么数据库是否会为空? 对我来说,在本地开发环境中总是使用 --build 似乎没有任何缺点。 docker 重新复制它可能认为不需要复制的文件的速度只需要几毫秒,它节省了大量的 WTF 时刻。 这对我来说无缝且没有问题,谢谢。【参考方案3】:

您可以通过运行 docker-compose up --build &lt;service name&gt; 为特定服务运行 build,其中服务名称必须与您在 docker-compose 文件中的调用方式相匹配。

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

额外参数 如果您想在命令中添加额外的参数,例如 -d 以便在后台运行,则该参数必须在服务名称之前: docker-compose up --build -d application

【讨论】:

【参考方案4】:

您可以仅从副本强制重建,而不必进行完全重建。

添加一行类似于

RUN mkdir -p /BUILD_TOKEN/f7e0188ea2c8466ebf77bf37eb6ab1c1
COPY src src

mkdir 调用只是为了让 docker 必须执行一行,其中包含我们将在每次需要部分重建时更改的令牌。

现在,当您需要强制复制时,让您的构建脚本替换 uuid

我在飞镖中:


  if (parsed['clone'] as bool == true) 
    final uuid = const Uuid().v4().replaceAll('-', '');

    replace(dockerfilePath, RegExp('RUN mkdir -p /BUILD_TOKEN/.*'),
        'RUN mkdir -p /BUILD_TOKEN/$uuid');
  

然后我将构建工具运行为:

build.dart --clone

这是我完整的飞镖脚本,但它有一些无关的位:

#! /usr/bin/env dcli

import 'dart:io';

import 'package:dcli/dcli.dart';
import 'package:mongo_dart/mongo_dart.dart';
import 'package:unpubd/src/version/version.g.dart';

/// build and publish the unpubd docker container.
void main(List<String> args) 
  final parser = ArgParser()
    ..addFlag('clean',
        abbr: 'c', help: 'Force a full rebuild of the docker container')
    ..addFlag('clone', abbr: 'l', help: 'Force reclone of the git repo.');

  ArgResults parsed;
  try 
    parsed = parser.parse(args);
   on FormatException catch (e) 
    print(e);
    print(parser.usage);
    exit(1);
  
  final dockerfilePath =
      join(DartProject.self.pathToProjectRoot, 'resources', 'Dockerfile');

  'dcli pack'.run;

  print(blue('Building unpubd $packageVersion'));

  final tag = 'noojee/unpubd:$packageVersion';
  const latest = 'noojee/unpubd:latest';

  var clean = '';
  if (parsed['clean'] as bool == true) 
    clean = ' --no-cache';
  

  if (parsed['clone'] as bool == true) 
    final uuid = const Uuid().v4().replaceAll('-', '');
    replace(dockerfilePath, RegExp('RUN mkdir -p /BUILD_TOKEN/.*'),
        'RUN mkdir -p /BUILD_TOKEN/$uuid');
  

  'docker  build $clean -t $tag -t $latest -f $dockerfilePath .'.run;

  'docker push noojee/unpubd:$packageVersion'.run;
  'docker push $tag'.run;
  'docker push $latest'.run;


【讨论】:

以上是关于在文件更改时重建 Docker 容器的主要内容,如果未能解决你的问题,请参考以下文章

Docker容器怎样更改容器内应用程序的配置文件

docker-compose未显示对代码的任何更改

如何修改已有docker容器的dns

在主机上更改挂载卷中的文件时,不会在 docker 容器中触发文件系统事件

文件更改时,两个带有 Nodemon 的后端服务器不会在 docker 容器中重新加载

如何在 docker-compose.yml 中重建 docker 容器?