【问题标题】:Rebuild Docker container on file changes在文件更改时重建 Docker 容器
【发布时间】: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 似乎建议构建一个新的镜像而不是修改一个现有的容器,所以我认为像我这样做的重建的一般方式是正确的,但是实现中的一些细节必须改进。

【问题讨论】:

  • 您能否列出构建容器的具体步骤,包括构建命令和每个命令的完整输出?

标签: git jenkins docker asp.net-core dockerfile


【解决方案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 将在容器退出后销毁容器。但是这个开关有两个问题:

  1. Docker 还会删除没有与容器关联的名称的卷,这可能会杀死您的数据
  2. 使用此选项,无法使用-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;
    }
    
    

    【讨论】:

      猜你喜欢
      • 2023-03-26
      • 1970-01-01
      • 1970-01-01
      • 2018-06-20
      • 2022-11-11
      • 1970-01-01
      • 2022-01-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多