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

huangapple go评论87阅读模式
英文:

How to run a command once in Docker compose

问题

所以我正在编写一个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
英文:

So I'm working on a docker compose file to deploy my Go web server. My server uses mongo, so I added a data volume container and the mongo service in docker compose.
Then I wrote a Dockerfile in order to build my Go project, and finally run it.

However, there is another step that must be done. Once my project has been compiled, I have to run the following command:
./my-project -setup

This will add some necessary information to the database, and the information only needs to be added once.
I can't however add this step on the Dockerfile (in the build process) because mongo must already be started.

So, how can I achieve this? Even if I restart the server and then run again docker-compose up I don't want this command to be executed again.

I think I'm missing some Docker understanding, because I don't actually understand everything about data volume containers (are they just stopped containers that mount a volume?).
Also, if I restart the server, and then run docker-compose up, which commands will be run? Will it just start the same container that was now stopped with the given CMD?

In any case, here is my 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

And here is my Dockerfile to build my project image:

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

得分: 13

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

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

以官方的 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> 删除不再需要的卷。

英文:

I suggest to add an entrypoint-script to your container; in this entrypoint-script, you can check if the database has been initialized, and if it isn't, perform the required steps.

As you noticed in your question, the order in which services / containers are started should not be taken for granted, so it's possible your application container is started before the database container, so the script should take that into account.

As an example, have a look at the official WordPress image, which performs a one-time initialization of the database in it's entrypoint-script. The script attempts to connect to the database (and retries if the database cannot be contacted (yet)), and checks if initialization is needed; https://github.com/docker-library/wordpress/blob/df190dc9c5752fd09317d836bd2bdcd09ee379a5/apache/docker-entrypoint.sh#L146-L171

NOTE

I notice you created a "data-only container" to attach your volume to. Since docker 1.9, docker has volume management, including naming volumes. Because of this, you no longer need to use "data-only" containers.

You can remove the data-only container from your compose file, and change your mongo service to look something like this;

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

This should create a new volume, named mongodata if it doesn't exist, or re-use the existing volume with that name. You can list all volumes using docker volume ls and remove a volume with docker volume rm &lt;some-volume&gt; if you no longer need it

答案2

得分: 3

你可以尝试使用ONBUILD指令

ONBUILD指令会在稍后的时间点向镜像中添加一个触发指令,在该镜像被用作另一个构建的基础时执行。触发指令将在下游构建的上下文中执行,就好像它被插入到下游Dockerfile中的FROM指令之后一样。

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

如果你正在构建一个将用作构建其他镜像的基础镜像,例如应用程序构建环境或可以根据用户特定配置进行定制的守护进程,这将非常有用。

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

解决方案是使用ONBUILD来注册提前运行的指令,在下一个构建阶段运行。

具体工作原理如下:

  1. 当构建器遇到ONBUILD指令时,它会向正在构建的镜像的元数据中添加一个触发器。该指令不会影响当前的构建过程。
  2. 在构建结束时,所有触发器的列表将存储在镜像清单中,存储在OnBuild键下。可以使用docker inspect命令查看它们。
  3. 稍后,该镜像可以作为新构建的基础,使用FROM指令。在处理FROM指令时,下游构建器会查找ONBUILD触发器,并按照注册的顺序执行它们。如果任何触发器失败,FROM指令将中止,从而导致构建失败。如果所有触发器都成功,FROM指令完成,构建将按照通常的方式继续。
  4. 触发器在执行后会从最终镜像中清除。换句话说,它们不会被“子孙”构建所继承。
英文:

You could try to use ONBUILD instruction:

The ONBUILD instruction adds to the image a trigger instruction to be executed at a later time, when the image is used as the base for another build. The trigger will be executed in the context of the downstream build, as if it had been inserted immediately after the FROM instruction in the downstream Dockerfile.

Any build instruction can be registered as a trigger.

This is useful if you are building an image which will be used as a base to build other images, for example an application build environment or a daemon which may be customized with user-specific configuration.

For example, if your image is a reusable Python application builder, it will require application source code to be added in a particular directory, and it might require a build script to be called after that. You can’t just call ADD and RUN now, because you don’t yet have access to the application source code, and it will be different for each application build. You could simply provide application developers with a boilerplate Dockerfile to copy-paste into their application, but that is inefficient, error-prone and difficult to update because it mixes with application-specific code.

The solution is to use ONBUILD to register advance instructions to run later, during the next build stage.

Here’s how it works:

  1. When it encounters an ONBUILD instruction, the builder adds a trigger to the metadata of the image being built. The instruction does not otherwise affect the current build.
  2. At the end of the build, a list of all triggers is stored in the image manifest, under the key OnBuild. They can be inspected with the docker inspect command.
  3. Later the image may be used as a base for a new build, using the FROM instruction. As part of processing the FROM instruction, the downstream builder looks for ONBUILD triggers, and executes them in the same order they were registered. If any of the triggers fail, the FROM instruction is aborted which in turn causes the build to fail. If all triggers succeed, the FROM instruction completes and the build continues as usual.
  4. Triggers are cleared from the final image after being executed. In other words they are not inherited by “grand-children” builds.

答案3

得分: 3

在docker-compose中,你可以定义:

restart: no

只运行容器一次,这对于例如db-migration容器非常有用。

英文:

In docker-compose you can define:

restart: no

To run the container only once, which is useful for example for db-migration containers.

答案4

得分: 1

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

  1. 检查所需的状态是否已经存在。
  2. 根据第一步的结果来初始化状态或不初始化。

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

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

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

英文:

Your application need some initial state for working. It means that you should:

  1. Check if required state already exists
  2. Depends on first step result init state or not

You can write program for checking current database state (here I will use bash script but it can be every other language program):

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

In my case if script will return 0 (success exit status) then setup command will be called.

huangapple
  • 本文由 发表于 2016年4月4日 23:59:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/36407477.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定