在Docker容器中仅在第一次启动时运行命令。

52

我有一个使用脚本(/bin/bash /init.sh)作为入口点的Docker镜像。我希望在容器第一次启动时执行此脚本,但在docker守护进程崩溃后重新启动或再次启动容器时跳过此脚本。

是否可以通过docker本身实现这一点,还是必须在脚本中实现某种检查?

4个回答

49

我遇到了同样的问题,这里是一个简单的过程(即解决方法)来解决它:

步骤1:

创建一个名为“myStartupScript.sh”的脚本,其中包含此代码:

CONTAINER_ALREADY_STARTED="CONTAINER_ALREADY_STARTED_PLACEHOLDER"
if [ ! -e $CONTAINER_ALREADY_STARTED ]; then
    touch $CONTAINER_ALREADY_STARTED
    echo "-- First container startup --"
    # YOUR_JUST_ONCE_LOGIC_HERE
else
    echo "-- Not first container startup --"
fi

步骤 2:

将“# YOUR_JUST_ONCE_LOGIC_HERE”一行替换为您想要在容器首次启动时执行的代码。

步骤 3:

将该脚本设置为 Dockerfile 的入口点:

ENTRYPOINT ["/myStartupScript.sh"]

简而言之,逻辑很简单,它检查文件系统中是否存在特定的文件;如果不存在,则创建该文件并执行您的一次性代码。下次启动容器时,文件已经存在于文件系统中,因此代码不会被执行。


1
第一次看起来没问题。但是当我使用“docker container prune”修剪容器时,我希望启动脚本再次运行。但实际上并没有发生这种情况。 - Kranthi Kiran P
3
@KranthiKiranP,“CONTAINER_ALREADY_STARTED”文件保存在容器文件系统中,因此如果容器使用卷,则该文件可能在卷中。清除卷应该可以解决您的问题。 - Francesco Cina
1
我使用了这种方法,为了测试,如果容器被初始化,我会检查/tmp目录中是否有特定的文件,例如:if [ ! -f /tmp/foo.txt ]; then echo "文件未找到!" else echo "文件已找到!" fi。所以@KranthiKiranP,它将涵盖您的情况。 - Mr.Teapot

13

docker容器的入口点告诉docker守护进程在你想要“运行”特定容器时要运行什么。让我们问一些问题,“当第二次启动容器时它应该运行什么?”或者“重启后容器应该运行什么?”

可能,你所做的是遵循“老式”的软件配置机制的相同方法。你的脚本正在“安装”所需的脚本,并将你的应用程序作为systemd/upstart服务运行,对吧?如果你这样做了,你应该把它改成更加“docker化”的定义。

该容器的入口点应该是一个实际启动你的应用程序而不是设置的脚本。假设你需要安装Java才能运行你的应用程序,那么在dockerfile中,你可以设置基础容器来安装所有需要的内容,例如:

FROM alpine:edge

RUN apk --update upgrade && apk add openjdk8-jre-base
RUN mkdir -p /opt/your_app/ && adduser -HD userapp

ADD target/your_app.jar /opt/your_app/your-app.jar
ADD scripts/init.sh /opt/your_app/init.sh

USER userapp
EXPOSE 8081

CMD ["/bin/bash", "/opt/your_app/init.sh"]

在我所工作的公司,我们的容器在运行init.sh脚本时会从consul获取配置(而不是提供挂载点并将配置放置在主机中或嵌入到容器中)。因此,该脚本看起来会像这样:

#!/bin/bash

echo "Downloading config from consul..."
confd -onetime -backend consul -node $CONSUL_URL -prefix /cfgs/$CONSUL_APP/$CONSUL_ENV_NAME
echo "Launching your-app..."
java -jar /opt/your_app/your-app.jar

我能给你的一个建议是(在我和容器一起工作的非常短的经验中):一旦容器被创建,把它们当做无状态的处理(包括所有在入口点之前运行的命令)。


1
完全同意将容器视为无状态。如果有要初始化的数据,请将其放置在卷中。 - BMitch
3
我很想听从你的建议,但这并不容易。我对应用程序的更改几乎没有影响力,需要进行很多变更才能按照你所描述的方式使用它。例如:每次启动容器时,初始化脚本都会为应用程序设置一个数据库。如果重新启动容器,则不应该发生这种情况,因为它会覆盖已经存在的数据......不幸的是,向应用程序开发人员教授如何使用Docker被证明非常困难。因此,我正在尝试让他们保持极其简单。 - user2995925
你怎么运行这个? - StarWind0
你能提供一个答案,然后解释为什么这是一种不好的做法,让人们自己决定是否需要吗?我想运行一些CLI命令来设置单元测试数据,而这个问题看起来正是我想要的。但是你的“不是答案”一点也没有帮助。 - Atomosk

12

我必须执行以下命令:docker run -d,这将创建一个分离的容器并启动bash(在后台运行),然后再执行docker exec进行必要的初始化。这里是一个例子:

docker run -itd --name=myContainer myImage /bin/bash
docker exec -it myContainer /bin/bash -c /init.sh
现在当我重新启动容器时,我只需要执行以下操作
docker start myContainer
docker attach myContainer

这可能不是最理想的,但对我来说效果很好。


1
这实际上是处理这种情况的最佳方式。您将容器作为无状态容器启动,然后只需运行初始化命令即可,该命令可能执行许多操作,例如我的容器运行所有待处理的数据库迁移。我真的不认为有必要只为运行迁移而使用容器。 - galileopy

3

我希望在Windows容器上做同样的事情。可以通过Windows任务计划程序实现。Linux任务计划程序的等效物是cron。您可以在这种情况下使用它。要执行此操作,请编辑dockerfile并在结尾处添加以下行:

WORKDIR /app 
COPY myTask.ps1 . 
RUN schtasks /Create /TN myTask /SC ONSTART /TR "c:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe C:\app\myTask.ps1" /ru SYSTEM

这将创建一个名为myTask的任务,在ONSTART时运行,该任务本身将执行放置在“c:\ app \ myTask.ps1”位置的powershell脚本。

此myTask.ps1脚本将执行您需要在容器启动时进行的任何初始化。确保在成功执行后删除此任务,否则它将在每次启动时运行。要删除它,您可以在myTask.ps1脚本的末尾使用以下命令。

schtasks /Delete /TN myTask /F

如何只运行一次cron作业 - jpaugh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接