如何在 Docker 容器启动后自动运行脚本

28

我正在使用Search Guard插件来保护由多个节点组成的elasticsearch集群。 这是我的Dockerfile:

#!/bin/sh
FROM docker.elastic.co/elasticsearch/elasticsearch:5.6.3

USER root

# Install search guard
RUN bin/elasticsearch-plugin install --batch com.floragunn:search-guard-5:5.6.3-16 \
    && chmod +x \
        plugins/search-guard-5/tools/hash.sh \
        plugins/search-guard-5/tools/sgadmin.sh \
        bin/init_sg.sh \
    && chown -R elasticsearch:elasticsearch /usr/share/elasticsearch

USER elasticsearch

为了初始化SearchGuard(创建内部用户并分配角色),我需要在容器启动后运行脚本init_sg.sh。 这里有一个问题:除非elasticsearch正在运行,否则该脚本将不会初始化任何安全索引。

脚本的内容为:

sleep 10
plugins/search-guard-5/tools/sgadmin.sh -cd config/ -ts config/truststore.jks -ks config/kirk-keystore.jks -nhnv -icl

现在,我只是在容器启动后手动运行脚本,但由于我在 Kubernetes 上运行它,Pod 可能会因某些原因被杀死或失败并自动重新创建。在这种情况下,插件必须在容器启动后自动初始化!

那么如何实现呢?任何帮助或提示都将不胜感激。


1
您只需要在每个集群中初始化 SG 一次。在每次 Pod 重启时重新初始化 SG 将覆盖任何未保存的自定义角色/权限。 - nafooesi
@nafooesi 绝对正确!SG应该每个集群只初始化一次。 - PhiloJunkie
这个问题也在这里得到了解答:(https://dev59.com/B1cO5IYBdhLWcg3wsjxk#64935472) - ManishM
7个回答

27

在Dockerfile中,镜像本身有一个入口点ENTRYPOINT ["/run/entrypoint.sh"]。你可以用自己的脚本来替换它。例如,创建一个新脚本,挂载它,先调用/run/entrypoint.sh,然后等待elasticsearch启动后再运行你的init_sg.sh


3
我正在尝试做完全相同的事情,但容器中没有/run/entrypoint.sh。我找不到它所在的位置。有什么帮助吗? - RedGiant
在elasticsearch:1.5.2中找到了/entrypoint.sh。 - Marieke
请问您能具体说明如何“挂载它”吗? - Gulzar
1
您可以在Dockerfile命令中使用ADD将脚本静态地添加到容器中。如果您经常更改脚本并且不想每次重新构建映像, 您可以创建一个从entrypoint.sh调用的脚本,并在其中进行所有更改。 - Do-do-new

8

我不确定这是否能解决您的问题,但它值得检查我的repoDockerfile

我创建了一个简单的run.sh文件复制到docker镜像中,在Dockerfile中写入CMD ["run.sh"]。同样地,在run.sh中定义您想要的任何内容并写入CMD ["run.sh"]。您可以找到另一个如下所示的例子

Dockerfile

FROM java:8

RUN apt-get update && apt-get install stress-ng -y 
ADD target/restapp.jar /restapp.jar 
COPY dockerrun.sh /usr/local/bin/dockerrun.sh 
RUN chmod +x /usr/local/bin/dockerrun.sh 
CMD ["dockerrun.sh"]

dockerrun.sh

#!/bin/sh
java -Dserver.port=8095 -jar /restapp.jar &
hostname="hostname: `hostname`"
nohup stress-ng --vm 4 &
while true; do
  sleep 1000
done

2
感谢回复,elasticsearch镜像已经有entrypoint.sh文件了,使用CMD会覆盖它,导致elasticsearch搜索无法启动 :/ - PhiloJunkie

7

这在文档中有所涉及: https://docs.docker.com/config/containers/multi-service_container/

如果您的某个进程依赖于主进程,则可以使用 wait-for-it 等脚本先启动辅助进程,然后再通过删除 fg %1 行来启动主进程。

#!/bin/bash
  
# turn on bash's job control
set -m
  
# Start the primary process and put it in the background
./my_main_process &
  
# Start the helper process
./my_helper_process
  
# the my_helper_process might need to know how to wait on the
# primary process to start before it does its work and returns
  
  
# now we bring the primary process back into the foreground
# and leave it there
fg %1

请注意,这是一个已经在Stack Overflow上回答过的问题。如果您想将启动脚本添加到Dockerfile中,可以按照以下步骤进行操作:
  1. 在Dockerfile所在的目录中创建一个新文件,命名为startup.sh(或者您喜欢的其他名称)。
  2. startup.sh文件中编写您的启动脚本代码。
  3. 在Dockerfile中使用COPY指令将startup.sh文件复制到容器中的适当位置。例如,如果您希望将其复制到容器的根目录下,可以使用以下指令:COPY startup.sh /
  4. 使用RUN指令在Dockerfile中运行启动脚本。例如,如果您将startup.sh复制到了容器的根目录下,可以使用以下指令:RUN chmod +x /startup.sh && /startup.sh
通过按照以上步骤操作,您就可以将启动脚本添加到Dockerfile中,并在构建镜像时执行该脚本。希望对您有所帮助!
- ManishM

5

我试图解决确切的问题。这是对我有效的方法。

  1. 创建一个单独的 shell 脚本,检查 ES 状态并在 ES 就绪时仅启动 SG 的初始化:

Shell 脚本

#!/bin/sh

echo ">>>>  Right before SG initialization <<<<"
# use while loop to check if elasticsearch is running 
while true
do
    netstat -uplnt | grep :9300 | grep LISTEN > /dev/null
    verifier=$?
    if [ 0 = $verifier ]
        then
            echo "Running search guard plugin initialization"
            /elasticsearch/plugins/search-guard-6/tools/sgadmin.sh -h 0.0.0.0 -cd plugins/search-guard-6/sgconfig -icl -key config/client.key -cert config/client.pem -cacert config/root-ca.pem -nhnv
            break
        else
            echo "ES is not running yet"
            sleep 5
    fi
done

在Dockerfile中安装脚本

您需要在容器中安装脚本,以便在容器启动后可以访问它。

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

更新入口脚本

您需要编辑ES镜像的入口脚本或运行脚本,以使其在启动ES进程之前后台启动sginit.sh。

# Run sginit in background waiting for ES to start
/sginit.sh &

这样,sginit.sh将在后台启动,并且仅在ES启动后才初始化SG。

在ES之前以后台方式启动sginit.sh脚本的原因是为了避免它阻止ES启动。如果您在ES启动后再运行该脚本,则除非您将ES启动置于后台,否则该脚本永远不会运行。


如何等待ES启动,然后初始化SG? - Akira
1
如下所述原问题的评论,您不需要每次都初始化SG。这将在Pod重新启动时覆盖任何修改的配置。SG应该在集群开始时仅初始化一次。如果您需要在ES启动后运行其他脚本,可以使用我的脚本作为示例,在启动脚本之前检测ES是否在后台运行。 - nafooesi
这里描述的一般方法非常有用。我曾经遇到过类似的问题(不是关于ES的),我想在主进程有机会启动后运行一个脚本。这种模式非常有效! - bischoje

3
您也可以使用wait-for-it脚本。它会等待主机和TCP端口的可用性。它对于同步相互依赖的服务的启动非常有用,并且与容器完美配合。它没有任何外部依赖,因此您只需将其作为RUN命令运行,而无需进行其他操作。
基于此线程的Dockerfile示例:
FROM elasticsearch

# Make elasticsearch write data to a folder that is not declared as a volume in elasticsearchs' official dockerfile.
RUN mkdir /data && chown -R elasticsearch:elasticsearch /data && echo 'es.path.data: /data' >> config/elasticsearch.yml && echo 'path.data: /data' >> config/elasticsearch.yml

# Download wait-for-it
ADD https://raw.githubusercontent.com/vishnubob/wait-for-it/e1f115e4ca285c3c24e847c4dd4be955e0ed51c2/wait-for-it.sh /utils/wait-for-it.sh

# Copy the files you may need and your insert script

# Insert data into elasticsearch
RUN /docker-entrypoint.sh elasticsearch -p /tmp/epid & /bin/bash /utils/wait-for-it.sh -t 0 localhost:9200 -- path/to/insert/script.sh; kill $(cat /tmp/epid) && wait $(cat /tmp/epid); exit 0;

等一下,从主分支获取: https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh - Damien Golding

2

我建议将CMD放在你的Docker文件中,在容器启动时执行脚本。

FROM debian
RUN apt-get update && apt-get install -y nano && apt-get clean
EXPOSE 8484
CMD ["/bin/bash", "/opt/your_app/init.sh"]

还有另一种方法,但在使用之前请先查看您的需求。

    ENTRYPOINT "put your code here" && /bin/bash
    #exemple ENTRYPOINT service nginx start && service ssh start &&/bin/bash "use && to separate your code"

此解决方案覆盖了 Elasticsearch 镜像提供的入口点。 - PhiloJunkie
我还没有研究过Elasticsearch镜像的安装,你是说这对你不起作用吗? - Sohan

1

有一个专门的工具可以做到这一点 - s6-overlay
引用他们的描述:

一个简单的 init 进程,允许最终用户执行初始化等任务(...)在单个容器中运行多个进程(...)能够以“Docker 方式”操作

该存储库提供了详细的说明,介绍了它的工作原理、安装方法等,这里不再赘述。

示例

我认为他们的存储库缺少一个可行的、直接的最小示例,展示如何运行一个进程和一个脚本,因此我提供了一个。我修改了他们在文档中提供的示例。
假设我们想要运行 nginx(或任何在容器生命周期结束之前一直运行的进程),以及一些 shell 脚本 myscript.sh

本地目录结构:

./Dockerfile
./myscript.sh
./s6-overlay/s6-rc.d/myapp/type
./s6-overlay/s6-rc.d/myapp/up
./s6-overlay/s6-rc.d/user/contents.d/myapp

Dockerfile

FROM ubuntu
ARG S6_OVERLAY_VERSION=3.1.4.1

RUN apt-get update && apt-get install -y nginx xz-utils
RUN echo "daemon off;" >> /etc/nginx/nginx.conf

# Minimal set of dependecies required for s6
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-x86_64.tar.xz

# Overhead of files to manage processes via s6
COPY s6-overlay /etc/s6-overlay
# Copy the script we intend to run
COPY myscript.sh /home

# CMD is the main process - nothing special here
CMD ["/usr/sbin/nginx"]
# ENTRYPOINT must be /init for s6 to work
ENTRYPOINT ["/init"]

myscript.sh - 确保它是可执行的:

#!/bin/bash
echo "foo" > /home/foo.txt
echo "bar" > /home/bar.txt

s6-overlay/s6-rc.d/myapp/type:

oneshot

"

一个up文件包含一条单独的命令行,因此当我们的脚本有超过1行时,我们必须将脚本外包到一个单独的文件中。因此,这是我们的s6-overlay/s6-rc.d/myapp/up文件:

"
/home/myscript.sh

s6-overlay/s6-rc.d/myapp/contents.d/myapp 是一个空文件。


现在我们只需要执行docker build (...)docker run -p 80:80 (...)。如果你做的都正确,应该能够在容器启动时看到一个日志消息s6-rc: info: service myapp successfully started
然后你可以访问localhost:80并运行docker exec CONTAINER bash -c "cat /home/foo.txt"来确认它按预期工作。
请注意,使用s6-rc.d是推荐的方式。还有一种传统的方法可以通过将myscript.sh放入文件夹/etc/cont-init.d/中来完成,这样开销就会小一些。

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