如何编写一个可以启动服务并运行 shell 并接受 shell 参数的 Dockerfile?

Posted

技术标签:

【中文标题】如何编写一个可以启动服务并运行 shell 并接受 shell 参数的 Dockerfile?【英文标题】:How to write a Dockerfile which I can start a service and run a shell and also accept arguments for the shell? 【发布时间】:2016-04-14 00:05:43 【问题描述】:

在一个 Dockerfile 中,最新的指令是:

CMD  sudo chown -R user:user /home/user/che && \
     sudo service docker start && \
     cd /home/user/che/bin/ && ./che.sh run

它有效,但我无法将更多参数传递给./che.sh

che.sh 检查内部docker 是否在执行其他任务后启动。它可以接受几个可选参数,例如-r:111.111.111.111

我尝试将指令修改为:

RUN sudo chown -R user:user /home/user/che && \
     sudo service docker start
ENTRYPOINT ["/home/user/che/bin/che.sh"]

为了像docker run -it --priviledged my/che -r:111.111.111.111 run 一样调用它,但是che.sh shell 会报告内部docker 工作不正常。

我也试过了:

ENTRYPOINT ["sudo service docker start", "&&", "/home/user/che/bin/che.sh run"]

甚至:

ENTRYPOINT ["sh", "-c" "sudo service docker start && /home/user/che/bin/che.sh run"]

但它会报告sudo service docker start is not found in $PATH,或者che.sh 没有运行。

正确的写法是什么?

    sudo service docker start 应该在调用 che.sh 时运行 我需要将参数从外部传递给che.sh,例如docker run -it --priviledged my/che -r:111.111.111.111 run

【问题讨论】:

【参考方案1】:

您必须在 Docker 容器内使用 supervisord,以便在创建容器时使用更复杂的 shell 语法。

关于 supervisord 的 Docker 文档:https://docs.docker.com/engine/articles/using_supervisord/

当您使用 $ docker run 命令创建新容器时,您可以使用更复杂的 shell 语法(您想要使用的),但是这在 systemd 服务文件(由于 systemd 的限制)和 docker-compose 中不起作用。 yml 文件和 Dockerfiles 也是。

首先,您必须在 Dockerfile 中安装 supervisord:

RUN apt-get -y update && apt-get -y dist-upgrade \
    && apt-get -y install \
        supervisor
RUN mkdir -p /var/log/supervisord

将其放在 Dockerfile 的末尾:

COPY etc/supervisor/conf.d/supervisord.conf /etc/supervisor/conf.d/
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

在您的 Dockerfile 旁边的 etc/supervisor/conf.d/supervisord.conf 中创建一个文件:

[unix_http_server]
file=/var/run/supervisord.sock
chmod=0777
chown=root:root
username=root

[supervisord]
nodaemon=true
user=root
environment=HOME="/root",USER="root"
logfile=/var/log/supervisord/supervisord.log
pidfile=/var/run/supervisord.pid
childlogdir=/var/log/supervisord
logfile_maxbytes=10MB
loglevel=info

[program:keepalive]
command=/bin/bash -c 'echo Keep Alive service started... && tail -f /dev/null'
autostart=true
autorestart=true
stdout_events_enabled=true
stderr_events_enabled=true
stdout_logfile=/var/log/supervisord/keepalive-stdout.log
stdout_logfile_maxbytes=1MB
stderr_logfile=/var/log/supervisord/keepalive-stderr.log
stderr_logfile_maxbytes=1MB

[program:dcheck]
command=/bin/bash -c 'chmod +x /root/dcheck/repo/dcheck.sh && cd /root/dcheck/repo && ./dcheck.sh'
autostart=true
autorestart=true
stdout_events_enabled=true
stderr_events_enabled=true
stdout_logfile=/var/log/supervisord/dcheck-stdout.log
stdout_logfile_maxbytes=10MB
stderr_logfile=/var/log/supervisord/dcheck-stderr.log
stderr_logfile_maxbytes=1MB

这是一个更复杂的 supervisord.conf,您可能不需要这里的许多命令,另外您必须根据需要更改文件位置。但是,您可以看到如何从脚本的 bash 输出创建日志文件。

稍后您必须在该容器中docker exec,您可以通过以下方式实时查看日志:

docker exec -it your_running_container /bin/bash -c 'tail -f /var/log/supervisord/dcheck-stdout.log'

您可以选择使用loglevel=debug 在主 supervisord 日志中显示子进程日志,但是这充满了时间戳和 cmets,而不是像直接运行脚本时那样的纯 bash 输出。

正如您在我的 scipt 中看到的那样,我使用 tail -f /dev/null 保持容器处于活动状态,但是这是一种不好的做法。 .sh 脚本应该自己让你的容器保持活动状态。

当您将 scipt 作为 ENTRYPOINT ["sudo service docker start", "&&", "/home/user/che/bin/che.sh run"] 发送到 ENTRYPOINT 时,您希望将默认 docker ENTRYPOINT 从 /bin/sh -c 更改为 sudo(另外,使用完整的位置名称)。

有两种方法可以在 Dockerfile 中更改 docker ENTRYPOINT。一种是将其放在 Dockerfile 的 head 部分:

RUN ln -sf /bin/bash /bin/sh && ln -sf /bin/bash /bin/sh.distrib

或者把它放在底部:

ENTRYPOINT ['/bin/bash', '-c']

当你向这个 Dockerfile 发送任何CMD 之后,它将通过/bin/bash -c 命令运行。

还有一点需要注意的是,第一个命令使用 PID1,所以如果你想在我的 supervisord 脚本中运行没有 tail -f /dev/null 的 .sh 脚本,它将占据 PID1 进程位置,并且 CTRL+C 命令将不起作用.您必须从另一个 shell 实例中关闭容器。

但是如果你运行命令:

[program:dcheck]
command=/bin/bash -c 'echo pid1 > /dev/null && chmod +x /root/dcheck/repo/dcheck.sh && cd /root/dcheck/repo && ./dcheck.sh'

echo pid1 > /dev/null 将使用 PID1 和 SIGTERM,SIGKILL 和 SIGINT 将再次与您的 shell 脚本一起使用。

我尽量避免使用 --privileged 标志运行 Docker。您有更多选择可以摆脱这些限制。

我对您的堆栈一无所知,但通常最好不要在容器中 dockerise Docker。 sudo service docker start 在您的 Dockerfile 中是否有特定原因?

我对这个容器一无所知,它必须是活的吗?因为如果没有,有一个更简单的解决方案,仅在必须从命令行处理某些内容时才运行容器。将此文件放在名为run 的主机上,假设在/home/hostuser 文件夹中,并给它chmod +x run

#!/bin/bash
docker run --rm -it -v /home/hostuser/your_host_shared_folder/:/root/your_container_shared_folder/:rw your_docker_image "echo pid1 > /dev/null && chmod +x /root/script.sh && cd  /root && ./script.sh"

在这种情况下,ENTRYPOINT 最好是ENTRYPOINT ['/bin/bash', '-c']

在主机上运行此脚本:

$ cd /home/hostuser
$ ./run -flag1 -flag2 args1 args2 args3

【讨论】:

按照您的回答和示例,我花了几个小时才终于完成工作,我想说非常非常感谢!我并没有真正意识到 docker 容器不是 vm,而更像是一个单一的进程。感谢您多次强调它,现在我终于明白了。 我的最终解决方案是跟随你最后一个“简单”案例,既不写ENTRYPOINT也不写CMD,只写RUN sudo chown -R user:user /home/user/che,并像这样调用它:docker run -it --rm --privileged --net=host my/che bash -c " sudo service docker start && cd /home/user/che/bin/ && ./che.sh -r:159.203.211.163 run",它就像我一样工作预期的,虽然看起来有点丑 先解释一下为什么docker里面有docker。 Che 是一个 web ide,它使用 docker 镜像来运行不同类型的项目。但是由于某种原因,我想运行多个 che 实例,所以我想在 docker 容器中运行它们中的每一个以使它们独立 可能不适用于这个特定的项目,但对于未来的项目可能非常有用,docker-compose 客户端有助于轻松构建 Docker 容器服务。 Compose 没有内置 Docker,但您可以从 github 安装它。这是我的 Dockerised LEMP 堆栈:github.com/DJviolin/LEMP/blob/master/install-lemp.sh 我可以用一个命令启动一切,这很棒。在链接的安装脚本中查找docker-compose.yml 文件。 Docker-compose 参考:docs.docker.com/compose 现在我非常喜欢 Docker + Docker-compose,因此我将它用于每个项目。 感谢您推荐 docker-compose,我一定会研究一下【参考方案2】:

尝试: 1。在 Dockerfile 中

RUN sudo chown -R user:user /home/user/che && \
    sudo service docker start
ENTRYPOINT ["/bin/sh", "-c", "/home/user/che/bin/che.sh run"]
    启动容器docker run -it --priviledged my/che -r:111.111.111.111

【讨论】:

我刚试过,但失败了。 che.sh 中的“docker”检查失败。 当你 RUNservice 拥有 sudo 特权时,这并不是出于所有可能的原因。 1:service 仅在您从映像启动容器时启动。例如。而不是RUN service sshd start,您必须使用CMD ["/usr/bin/sshd", "-D"]。所以RUN sudo service docker start 命令不会在$ docker run 上执行。 2:在 Dockerfile 中,每个命令都以 root 身份运行,事实上,在大多数 dockerised linux 发行版中,默认情况下没有其他用户,只有超级用户。在 Dockerfile 中安装或使用 sudo 无用。 3:ENTRYPOINT正确用法说明在我的帖子里。 另外,我在上面的评论中使用了sshd 示例,仅用于演示目的,最好不要将任何 SSH 服务放在 docker 镜像中。请改用docker exec,并在您的主机操作系统上只保留一个 SSH 实例。 Docker 不是虚拟机。而是想像诺基亚 Symbian S40 平台上的移动 Java 应用程序。它看起来像一个可执行文件,但如果你用 Winrar 打开,你会看到一堆文本文件。好吧,如果你去主机上/var/lib/docker的Docker主文件夹,你可以看到图像和容器是未解压的文件。

以上是关于如何编写一个可以启动服务并运行 shell 并接受 shell 参数的 Dockerfile?的主要内容,如果未能解决你的问题,请参考以下文章

运行程序并返回值 libreoffice calc

如何实现在运行shell程序时添加选项-h能提示程序的用法?

Linux shell脚本如何自动运行程序并输入命令

如何使用 shell 脚本连接到 linux 服务器并停止/启动服务?

shell 学习之脚本编写1

如何“避免” SIGSEGV?