如何在 Docker compose 中运行一次命令

Posted

技术标签:

【中文标题】如何在 Docker compose 中运行一次命令【英文标题】:How to run a command once in Docker compose 【发布时间】:2016-07-24 06:42:21 【问题描述】:

所以我正在开发一个 docker compose 文件来部署我的 Go Web 服务器。我的服务器使用 mongo,所以我在 docker compose 中添加了一个数据卷容器和 mongo 服务。 然后我写了一个 Dockerfile 来构建我的 Go 项目,最后运行它。

但是,还有一个步骤必须完成。编译项目后,我必须运行以下命令: ./my-project -setup

这会将一些必要的信息添加到数据库中,这些信息只需要添加一次。 但是我不能在 Dockerfile 上添加这个步骤(在构建过程中),因为 mongo 必须已经启动。

那么,我该如何实现呢?即使我重新启动服务器然后再次运行docker-compose up 我也不希望再次执行此命令。

我认为我缺少对 Docker 的一些理解,因为我实际上并不了解有关数据卷容器的所有内容(它们只是 停止 挂载卷的容器吗?)。 另外,如果我重新启动服务器,然后运行docker-compose up,将会运行哪些命令?它会启动现在使用给定 CMD 停止的同一个容器吗?

无论如何,这是我的 docker-compose.yml:

version: '2'
services:
  mongodata:
    image: mongo:latest
    volumes:
      - /data/db
    command: --break-mongo
  mongo:
    image: mongo:latest
    volumes_from:
      - mongodata
    ports:
      - "28001:27017"
    command: --smallfiles --rest --auth
  my_project:
    build: .
    ports:
      - "6060:8080"
    depends_on:
      - mongo
      - mongodata
    links:
      - mongo

这是用于构建项目映像的 Dockerfile:

FROM golang

ADD . /go/src/my_project
RUN cd /go/src/my_project && go get
RUN go install my_project
RUN my_project -setup
ENTRYPOINT /go/bin/my_project

EXPOSE 8080

【问题讨论】:

您可能可以设置一个标志(文件或数据库)来指示脚本是否已运行并且数据是否已初始化。 【参考方案1】:

我建议在您的容器中添加一个入口点脚本;在这个入口点脚本中,您可以检查数据库是否已初始化,如果没有,请执行所需的步骤。

正如您在问题中所注意到的,服务/容器的启动顺序不应被视为理所当然,因此您的应用程序容器可能在数据库容器之前启动,因此脚本应该考虑到这一点。

例如,看一下官方的 WordPress 图像,它在其入口点脚本中执行一次数据库初始化。该脚本尝试连接到数据库(如果(还)无法联系到数据库,则重试),并检查是否需要初始化; https://github.com/docker-library/wordpress/blob/df190dc9c5752fd09317d836bd2bdcd09ee379a5/apache/docker-entrypoint.sh#L146-L171

注意

我注意到您创建了一个“仅数据容器”来附加您的卷。从 docker 1.9 开始,docker 有了卷管理,包括命名卷。因此,您不再需要使用“仅数据”容器。

您可以从 compose 文件中删除仅数据容器,并将您的 mongo 服务更改为如下所示;

  mongo:
    image: mongo:latest
    volumes:
      - mongodata:/data/db
    ports:
      - "28001:27017"
    command: --smallfiles --rest --auth

这应该创建一个名为mongodata 的新卷(如果它不存在),或者重新使用具有该名称的现有卷。您可以使用docker volume ls 列出所有卷,如果不再需要使用docker volume rm <some-volume> 删除卷

【讨论】:

【参考方案2】:

你可以尝试使用ONBUILD instruction:

ONBUILD 指令向映像添加了一条触发指令,该指令将在以后将映像用作另一个构建的基础时执行。触发器将在下游构建的上下文中执行,就好像它是在下游 Dockerfile 中的 FROM 指令之后立即插入的一样。

任何构建指令都可以注册为触发器。

如果您正在构建一个镜像,该镜像将用作构建其他镜像的基础,例如应用程序构建环境或可以使用用户特定配置自定义的守护程序,这将非常有用。

例如,如果您的图像是可重用的 Python 应用程序构建器,则需要将应用程序源代码添加到特定目录中,并且可能需要在之后调用构建脚本。您现在不能只调用ADDRUN,因为您还没有访问应用程序源代码的权限,而且每个应用程序构建都会有所不同。您可以简单地向应用程序开发人员提供样板文件 Dockerfile 以将其复制粘贴到他们的应用程序中,但这样做效率低、容易出错且难以更新,因为它与特定于应用程序的代码混合在一起。

解决方案是使用ONBUILD 注册高级指令,以便稍后在下一个构建阶段运行。

它是这样工作的:

    当遇到ONBUILD 指令时,构建器将触发器添加到正在构建的映像的元数据中。该指令不会影响当前的构建。 在构建结束时,所有触发器的列表都存储在映像清单中,位于键 OnBuild 下。可以使用docker inspect 命令对其进行检查。 稍后可以使用FROM 指令将映像用作新构建的基础。作为处理FROM 指令的一部分,下游构建器查找ONBUILD 触发器,并按照它们注册的相同顺序执行它们。如果任何触发器失败,FROM 指令将被中止,从而导致构建失败。如果所有触发器都成功,则FROM 指令完成,构建照常继续。 触发器在执行后从最终图像中清除。换句话说,它们不会被“孙子”构建继承。

【讨论】:

你建议如何使用它? @Cortwave 它使结构有点复杂,因为需要先使用ONBUILD 指令创建一个“基础”图像,然后再为应用程序创建一个图像。至于我,我最好在应用程序端解决这个问题,或者使用makefile之类的东西。 @Cortwave 我最好让 db 尽可能简单,所以在应用程序端进行编辑。 但据我了解ONBUILD 命令它只是在您将图像用作其他构建的基础图像时执行一些触发器。换句话说,每次你使用你的应用图像作为基础图像时,它都会被执行。 @Cortwave 是的,每次构建镜像时都会执行,但不是每次运行容器时都会执行。【参考方案3】:

您的应用程序需要一些初始状态才能工作。这意味着您应该:

    检查所需状态是否已存在 取决于第一步结果初始化状态与否

您可以编写检查当前数据库状态的程序(这里我将使用 bash 脚本,但它可以是所有其他语言程序):

RUN if $(./check.sh); then my_project -setup; fi

在我的情况下,如果脚本将返回 0(成功退出状态),则将调用 setup 命令。

【讨论】:

以上是关于如何在 Docker compose 中运行一次命令的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 pycharm 调试在 docker-compose 中运行的进程

如何使用来自其他目录的 docker-compose 环境变量

使用 Docker-Compose 时如何执行 Django 数据库迁移?

如何在 AWS CodeBuild 上运行 docker-compose?

如何在系统启动时运行 docker-compose up -d?

如何使用 docker-compose 在 docker 容器中为 Laravel 环境安装 Behat?