如何从docker容器在主机上运行shell脚本?

Ale*_*kov 63 docker

如何从docker容器控制主机?

例如,如何执行复制到主机bash脚本?

Vin*_*ent 45

这个答案只是Bradford Medeiros 解决方案更详细版本,对我来说,这也是最好的答案,所以归功于他。

在他的回答中,他解释了要做什么(命名管道),但没有具体说明如何去做。

我不得不承认,在我阅读他的解决方案时,我不知道什么是命名管道。所以我努力实现它(虽然它实际上很简单),但我确实成功了。所以我的回答的重点只是详细说明为了让它工作而需要运行的命令,但同样,功劳归于他。

第 1 部分 - 在没有 docker 的情况下测试命名管道概念

在主主机上,选择要放置命名管道文件的文件夹,例如/path/to/pipe/和管道名称,例如mypipe,然后运行:

mkfifo /path/to/pipe/mypipe
Run Code Online (Sandbox Code Playgroud)

管道已创建。类型

ls -l /path/to/pipe/mypipe 
Run Code Online (Sandbox Code Playgroud)

并检查以“p”开头的访问权限,例如

prw-r--r-- 1 root root 0 mypipe
Run Code Online (Sandbox Code Playgroud)

现在运行:

tail -f /path/to/pipe/mypipe
Run Code Online (Sandbox Code Playgroud)

终端现在正在等待数据发送到这个管道

现在打开另一个终端窗口。

然后运行:

echo "hello world" > /path/to/pipe/mypipe
Run Code Online (Sandbox Code Playgroud)

检查第一个终端(带有 的终端tail -f),它应该显示“hello world”

第 2 部分 - 通过管道运行命令

在主机容器上,不是运行tail -f它只输出作为输入发送的任何内容,而是运行以下命令将其作为命令执行:

eval "$(cat /path/to/pipe/mypipe)"
Run Code Online (Sandbox Code Playgroud)

然后,从另一个终端,尝试运行:

echo "ls -l" > /path/to/pipe/mypipe
Run Code Online (Sandbox Code Playgroud)

返回第一个终端,您应该会看到ls -l命令的结果。

第 3 部分 - 让它永远聆听

您可能已经注意到,在上一部分中,在ls -l显示输出后,它立即停止侦听命令。

而不是eval "$(cat /path/to/pipe/mypipe)",运行:

while true; do eval "$(cat /path/to/pipe/mypipe)"; done
Run Code Online (Sandbox Code Playgroud)

(你可以不知道)

现在您可以一个接一个地发送无限数量的命令,它们都将被执行,而不仅仅是第一个。

第 4 部分 - 即使发生重启也能正常工作

唯一需要注意的是,如果主机必须重新启动,“while”循环将停止工作。

为了处理重启,这里是我所做的:

while true; do eval "$(cat /path/to/pipe/mypipe)"; done在一个名为execpipe.sh#!/bin/bash

不要忘记chmod +x

通过运行将其添加到 crontab

crontab -e
Run Code Online (Sandbox Code Playgroud)

然后添加

@reboot /path/to/execpipe.sh
Run Code Online (Sandbox Code Playgroud)

此时,测试一下:重新启动服务器,当它备份时,将一些命令回显到管道中并检查它们是否被执行。当然,您无法看到命令的输出,因此ls -l不会有帮助,但touch somefile会有所帮助。

另一种选择是修改脚本以将输出放在一个文件中,例如:

while true; do eval "$(cat /path/to/pipe/mypipe)" &> /somepath/output.txt; done
Run Code Online (Sandbox Code Playgroud)

现在您可以运行ls -l并且输出(&>在 bash 中使用的 stdout 和 stderr )应该在 output.txt 中。

第 5 部分 - 让它与 docker 一起工作

如果您像我一样同时使用 docker compose 和 dockerfile,这就是我所做的:

假设您要像/hostpipe在容器中一样安装 mypipe 的父文件夹

添加这个:

VOLUME /hostpipe
Run Code Online (Sandbox Code Playgroud)

在您的 dockerfile 中以创建挂载点

然后添加这个:

volumes:
   - /path/to/pipe:/hostpipe
Run Code Online (Sandbox Code Playgroud)

在您的 docker compose 文件中,以便将 /path/to/pipe 挂载为 /hostpipe

重新启动您的 docker 容器。

第 6 部分 - 测试

执行到您的 docker 容器中:

docker exec -it <container> bash
Run Code Online (Sandbox Code Playgroud)

进入安装文件夹并检查您是否可以看到管道:

cd /hostpipe && ls -l
Run Code Online (Sandbox Code Playgroud)

现在尝试从容器内运行命令:

echo "touch this_file_was_created_on_main_host_from_a_container.txt" > /hostpipe/mypipe
Run Code Online (Sandbox Code Playgroud)

它应该工作!

警告:如果你有一个 OSX (Mac OS) 主机和一个 Linux 容器,它不会工作(解释在这里/sf/answers/3043229591/和问题在这里https://github.com/docker /for-mac/issues/483 ) 因为管道实现不一样,所以你从 Linux 写入管道的内容只能被 Linux 读取,而你从 Mac OS 写入管道的内容只能被 a 读取Mac OS(这句话可能不太准确,但请注意存在跨平台问题)。

例如,当我从 Mac OS 计算机在 DEV 中运行我的 docker 设置时,上述命名管道不起作用。但是在登台和生产中,我有 Linux 主机和 Linux 容器,它运行良好。

第 7 部分 - 来自 Node.JS 容器的示例

下面是我如何从 Node.JS 容器向主主机发送命令并检索输出:

const pipePath = "/hostpipe/mypipe"
const outputPath = "/hostpipe/output.txt"
const commandToRun = "pwd && ls-l"

console.log("delete previous output")
if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath)

console.log("writing to pipe...")
const wstream = fs.createWriteStream(pipePath)
wstream.write(commandToRun)
wstream.close()

console.log("waiting for output.txt...") //there are better ways to do that than setInterval
let timeout = 10000 //stop waiting after 10 seconds (something might be wrong)
const timeoutStart = Date.now()
const myLoop = setInterval(function () {
    if (Date.now() - timeoutStart > timeout) {
        clearInterval(myLoop);
        console.log("timed out")
    } else {
        //if output.txt exists, read it
        if (fs.existsSync(outputPath)) {
            clearInterval(myLoop);
            const data = fs.readFileSync(outputPath).toString()
            if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath) //delete the output file
            console.log(data) //log the output of the command
        }
    }
}, 300);
Run Code Online (Sandbox Code Playgroud)

  • 功劳归于他,但我的选票投给了你。 (5认同)
  • 这很好用。安全性怎么样?我想用它来从正在运行的容器中启动/停止 docker 容器?我是否只创建一个 dockeruser,除了运行 docker 命令之外没有任何权限? (3认同)
  • 当然,该命令可以在容器中运行,但不能在 php 代码中运行 (2认同)

Moh*_*din 43

我知道这是一个老问题,但理想的解决方案可能是连接到主机SSH并执行命令,如下所示:

ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"
Run Code Online (Sandbox Code Playgroud)

UPDATE

由于这个答案不断获得投票,我想添加一个注释,用于调用脚本的帐户应该是一个完全没有权限的帐户,但只能将该脚本作为sudo执行(可以从sudo文件中完成) .


Bra*_*ros 28

使用命名管道.在主机操作系统上,创建一个脚本来循环和读取命令,然后在其上调用eval.

让docker容器读取到该命名管道.

为了能够访问管道,您需要通过卷安装它.

这类似于SSH机制(或类似的基于套接字的方法),但是正确地限制了主机设备,这可能更好.另外,您不必传递身份验证信息.

我唯一的警告是要谨慎对待你这样做的原因.如果你想创建一个用户输入或其他任何东西进行自我升级的方法,那完全可以做,但是你可能不想调用命令来获取一些配置数据,因为正确的方法是将它传递给args /卷到docker.同样要谨慎对待你正在评估的事实,所以只需考虑一下权限模型.

其他一些答案(例如在卷下运行脚本)将无法正常工作,因为它们无法访问完整的系统资源,但根据您的使用情况,它可能更合适.

  • 注意:这是正确/最好的答案,需要更多的赞扬.其他每一个答案都在摆弄"询问你要做什么"并对事物做例外.我有一个非常具体的用例,要求我能够做到这一点,这是唯一的好回答imho.上面的SSH需要降低安全性/防火墙标准,并且docker run的东西是错误的.谢谢你.我认为这并没有得到那么多的赞成,因为它不是一个简单的复制/粘贴答案,但这就是答案.如果可以的话,我可以获得+100分 (6认同)
  • 对于那些寻找更多信息的人,您可以使用在主机上运行的以下脚本:https://unix.stackexchange.com/a/369465 当然,您必须使用“nohup”运行它并创建一些一种主管包装器,以使其保持活动状态(也许使用 cron 作业:P) (4认同)
  • 如果它给出了如何做到这一点的示例,这将是一个很好的答案。这只是一个描述,没有指向任何相关内容的链接。这不是一个很好的答案,但只是朝着正确方向的一个推动。 (3认同)
  • 这可能是一个很好的答案。但是,如果您提供更多详细信息和更多命令行说明,那就更好了。可以详细说明吗? (2认同)
  • 已投票,这有效!使用“ mkfifo host_executor_queue”在已安装卷的位置创建命名管道。然后,添加一个执行执行作为主机外壳程序放入队列的命令的使用者,请使用'tail -f host_executor_queue | sh&'。末尾的&使其在后台运行。最后,使用“ echo touch foo&gt; host_executor_queue”将命令推入队列-此测试在主目录中创建一个临时文件foo。如果希望使用者在系统启动时启动,请在'@reboot tail -f host_executor_queue | sh&'在crontab中。只需将相对路径添加到host_executor_queue。 (2认同)
  • 阅读管道和评估:https://github.com/BradfordMedeiros/automate_firmware/blob/96c72090034c22c4ce78807d38c9c698a25206f6/0_x_install_automate/read_pipe.sh 写入管道:https://github.com/BradfordMedeiros/automate_firmware/blob/96c7209003 4c22c4ce78807d38c9c698a25206f6/0_x_install_automate/ write_pipe.sh 如果您深入研究,您可以看到构建 docker 映像/使用参数运行它等。 (2认同)

Pau*_*tte 22

这真的取决于你需要bash脚本做什么!

例如,如果bash脚本只是回显一些输出,那么你可以这样做

docker run --rm -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Run Code Online (Sandbox Code Playgroud)

另一种可能性是你希望bash脚本安装一些软件 - 比如安装docker-compose的脚本.你可以做点什么

docker run --rm -v /usr/bin:/usr/bin --privileged -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Run Code Online (Sandbox Code Playgroud)

但在这一点上,你真的需要密切了解脚本正在做什么,以便从容器内部允许主机上所需的特定权限.

  • 尝试过这个,脚本是在容器中执行,而不是在主机上执行 (5认同)
  • 第一行启动一个新的ubuntu容器,并将脚本安装在可以读取的位置.例如,它不允许容器访问主机文件系统.第二行将主机的`/ usr/bin`暴露给容器.在任何情况下,容器都不能完全访问主机系统.也许我错了,但对一个糟糕的问题似乎是一个糟糕的答案. (3认同)
  • 很公平 - 问题很模糊.这个问题没有要求"完全访问主机系统".如上所述,如果bash脚本仅用于回显某些输出,则不需要对主机文件系统进行任何访问.对于我的第二个安装docker-compose的示例,您需要的唯一权限是访问存储二进制文件的bin目录.正如我在开始时所说的那样,你必须对脚本正在做什么以获得正确的权限有非常具体的想法. (3认同)

小智 10

我的懒惰使我找到了未在此处作为答案发布的最简单的解决方案。

它是基于伟大的文章吕克juggery

为了从 docker 容器中获得 linux 主机的完整 shell,您需要做的就是:

docker run --privileged --pid=host -it alpine:3.8 \
nsenter -t 1 -m -u -n -i sh
Run Code Online (Sandbox Code Playgroud)

解释:

--privileged : 授予容器额外的权限,它允许容器访问主机 (/dev) 的设备

--pid=host :允许容器使用 Docker 主机(运行 Docker 守护程序的 VM)的进程树 nsenter 实用程序:允许在现有命名空间(为容器提供隔离的构建块)中运行进程

nsenter (-t 1 -m -u -n -i sh) 允许在与 PID 为 1 的进程相同的隔离上下文中运行进程 sh。然后整个命令将在 VM 中提供一个交互式 sh shell

此设置具有重大的安全隐患,应谨慎使用(如果有)。

  • 迄今为止最好、最简单的解决方案!谢谢 Shmulik 提供它(Yashar Koach!) (2认同)

小智 6

编写一个简单的服务器 python 服务器侦听端口(比如 8080),将端口 -p 8080:8080 与容器绑定,向 localhost:8080 发出 HTTP 请求以询问运行带有 popen 的 shell 脚本的 python 服务器,运行 curl 或编写代码以发出 HTTP 请求 curl -d '{"foo":"bar"}' localhost:8080

#!/usr/bin/python
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
import subprocess
import json

PORT_NUMBER = 8080

# This class will handles any incoming request from
# the browser 
class myHandler(BaseHTTPRequestHandler):
        def do_POST(self):
                content_len = int(self.headers.getheader('content-length'))
                post_body = self.rfile.read(content_len)
                self.send_response(200)
                self.end_headers()
                data = json.loads(post_body)

                # Use the post data
                cmd = "your shell cmd"
                p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
                p_status = p.wait()
                (output, err) = p.communicate()
                print "Command output : ", output
                print "Command exit status/return code : ", p_status

                self.wfile.write(cmd + "\n")
                return
try:
        # Create a web server and define the handler to manage the
        # incoming request
        server = HTTPServer(('', PORT_NUMBER), myHandler)
        print 'Started httpserver on port ' , PORT_NUMBER

        # Wait forever for incoming http requests
        server.serve_forever()

except KeyboardInterrupt:
        print '^C received, shutting down the web server'
        server.socket.close()
Run Code Online (Sandbox Code Playgroud)


Mat*_*cci 6

如果您不担心安全性,而只是希望从另一个 docker 容器(如 OP)中启动主机上的 docker 容器,则可以通过共享主机上的侦听套接字来与 docker 容器共享主机上运行的 docker 服务器。

请参阅https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface并查看您的个人风险承受能力是否允许此特定应用程序。

您可以通过将以下卷参数添加到启动命令中来完成此操作

docker run -v /var/run/docker.sock:/var/run/docker.sock ...
Run Code Online (Sandbox Code Playgroud)

或者通过在 docker compose 文件中共享 /var/run/docker.sock ,如下所示:

version: '3'

services:
   ci:
      command: ...
      image: ...
      volumes:
         - /var/run/docker.sock:/var/run/docker.sock

Run Code Online (Sandbox Code Playgroud)

当您在 docker 容器中运行 docker start 命令时,主机上运行的 docker 服务器将看到该请求并配置同级容器。

信用:http ://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/


ubu*_*gnu 6

我发现使用命名管道的答案很棒。但我想知道是否有办法获取执行命令的输出。

解决方案是创建两个命名管道:

mkfifo /path/to/pipe/exec_in
mkfifo /path/to/pipe/exec_out
Run Code Online (Sandbox Code Playgroud)

然后,按照@Vincent的建议,使用循环的解决方案将变为:

# on the host
while true; do eval "$(cat exec_in)" > exec_out; done
Run Code Online (Sandbox Code Playgroud)

然后在 docker 容器上,我们可以执行命令并使用以下命令获取输出:

# on the container
echo "ls -l" > /path/to/pipe/exec_in
cat /path/to/pipe/exec_out
Run Code Online (Sandbox Code Playgroud)

如果有人感兴趣,我需要从容器在主机上使用故障转移 IP,我创建了这个简单的 ruby​​ 方法:

def fifo_exec(cmd)
  exec_in = '/path/to/pipe/exec_in'
  exec_out = '/path/to/pipe/exec_out'
  %x[ echo #{cmd} > #{exec_in} ]
  %x[ cat #{exec_out} ]
end
# example
fifo_exec "curl https://ip4.seeip.org"
Run Code Online (Sandbox Code Playgroud)