在文件更改时重建 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 <service name>
为特定服务运行 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 容器中触发文件系统事件