如何通过 dockerfile 在 ENTRYPOINT 之前执行 shell 命令

新手上路,请多包涵

我的 nodejs 项目有以下文件

FROM node:boron

# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install

# Bundle app source
COPY . /usr/src/app

# Replace with env variable
RUN envsubs < fil1 > file2

EXPOSE 8080
CMD [ "npm", "start" ]

我使用提供环境变量的 -e 标志运行 docker 容器

但我没有看到替代品。当 env 变量可用时,会执行 Run c 命令吗?

原文由 user_mda 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 965
2 个回答

图像是不可变的

Dockerfile 定义了镜像的构建过程。一旦构建,图像是不可变的(无法更改)。运行时变量不会被烘焙到这个不可变的图像中。所以 Dockerfile 是解决这个问题的错误地方。

使用入口点脚本

您可能想要做的是用您自己的脚本覆盖默认值 ENTRYPOINT ,并让该脚本对环境变量执行一些操作。由于入口点脚本将在运行时(容器启动时)执行,因此这是收集环境变量并对其进行处理的正确时间。

首先,您需要调整 Dockerfile 以了解入口点脚本。虽然 Dockerfile 不直接参与处理环境变量,但它仍然需要了解此脚本,因为该脚本将被烘焙到您的映像中。

Dockerfile:

 COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
CMD ["npm", "start"]

现在,编写一个入口点脚本,在命令运行 之前 执行所需的任何设置,最后, exec 命令本身。

入口点.sh:

 #!/bin/sh

# Where $ENVSUBS is whatever command you are looking to run
$ENVSUBS < file1 > file2

npm install

# This will exec the CMD from your Dockerfile, i.e. "npm start"
exec "$@"

在这里,我包括 npm install ,因为您在评论中询问了这个问题。我会注意到这将 在每次运行 时运行 npm install 。如果合适的话,很好,但我想指出它每次都会运行,这会给你的启动时间增加一些延迟。

现在重建您的图像,因此入口点脚本是其中的一部分。

在运行时使用环境变量

入口点脚本知道如何使用环境变量,但您仍然需要告诉 Docker 在运行时导入该变量。您可以使用 -e 标志到 docker run 这样做。

 docker run -e "ENVSUBS=$ENVSUBS" <image_name>

在这里,Docker 被告知定义一个环境变量 ENVSUBS ,它分配的值是来自当前 shell 环境的 $ENVSUBS 的值。

入口点脚本如何工作

我将对此进行详细说明,因为在评论中,您似乎对它如何组合在一起有些模糊。

当 Docker 启动一个容器时,它会在容器内执行一个(并且只有一个)命令。该命令变为 PID 1,就像典型 Linux 系统上的 initsystemd 一样。这个进程负责运行容器需要的任何其他进程。

默认情况下, ENTRYPOINT/bin/sh -c 。您可以在 Dockerfile 或 docker-compose.yml 中覆盖它,或者使用 docker 命令。

当容器启动时,Docker 运行入口点命令,并将命令( CMD )作为参数列表传递给它。之前,我们将自己的 ENTRYPOINT 定义为 /entrypoint.sh 。这意味着在您的情况下,这是 Docker 启动时将在容器中执行的内容:

 /entrypoint.sh npm start

因为 ["npm", "start"] 被定义为命令,所以它作为参数列表传递给入口点脚本。

因为我们使用 -e 标志定义了一个环境变量,所以这个入口点脚本(及其子脚本)将可以访问该环境变量。

在入口点脚本的最后,我们运行 exec "$@" 。因为 $@ 扩展为传递给脚本的参数列表,这将运行

exec npm start

因为 exec 将其参数作为命令运行,用它自己 替换 当前进程,当你完成时, npm start 在你的容器中变成 PID 1。

为什么你不能使用多个 CMD

在评论中,您询问是否可以定义多个 CMD 条目来运行多个事物。

您只能定义一个 ENTRYPOINT 和一个 CMD 。在构建过程中根本不使用这些。与 RUNCOPY 不同,它们不会在构建期间执行。一旦构建,它们就会作为元数据项添加到图像中。

只有稍后,当图像作为容器运行时,这些元数据字段才会被读取,并用于启动容器。

如前所述,入口点是真正运行的,它通过 CMD 作为参数列表传递。它们分开的部分原因是历史原因。在 Docker 的早期版本中, CMD 是唯一可用的选项,而 ENTRYPOINT 被固定为 /bin/sh -c 。但是由于这种情况,Docker最终允许用户定义 ENTRYPOINT

原文由 Dan Lowe 发布,翻译遵循 CC BY-SA 4.0 许可协议

对于以 bash 作为默认入口点的图像,如果需要,我会这样做以允许自己在 shell 启动之前运行一些脚本:

 FROM ubuntu
COPY init.sh /root/init.sh
RUN echo 'a=(${BEFORE_SHELL//:/ }); for c in ${a[@]}; do source $x; done' >> ~/.bashrc

如果你想在容器登录时获取一个脚本,你可以在环境变量 BEFORE_SHELL 中传递它的路径。使用 docker-compose 的示例:

 version: '3'
services:
  shell:
    build:
      context: .
    environment:
      BEFORE_SHELL: '/root/init.sh'

一些备注:

  • 如果 BEFORE_SHELL 没有设置,那么什么都不会发生(我们有默认行为)
  • 您可以传递容器中可用的任何脚本路径,包括已安装的
  • 脚本来源,因此脚本中定义的变量将在容器中可用
  • 可以传递多个脚本(使用 : 分隔路径)

原文由 piarston 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏