如何从Docker容器在主机上运行Shell脚本?


94

如何从Docker容器控制主机?

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


8
那不是将主机与Docker隔离的对立面吗?
MarcusMüller2015年

31
是。但这有时是必要的。
Alex Ushakov 2015年


不确定“控制主机”,但我最近在数据科学家的演讲中使用了docker运行脚本来处理巨大的工作负载(使用AWS安装的GPU)并将结果输出到主机。一个非常有趣的用例。归功于docker
KCD

@KCD以及为什么他们更喜欢通过docker而不是系统级容器(LXC)通过应用程序容器化?
Alex Ushakov

Answers:


28

这真的取决于您需要该bash脚本执行的操作!

例如,如果bash脚本仅回显一些输出,则可以执行

docker run --rm -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh

另一种可能性是您希望bash脚本安装某些软件-例如说该脚本安装docker-compose。你可以做类似的事情

docker run --rm -v /usr/bin:/usr/bin --privileged -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh

但是到了这一点,您真的必须深入了解脚本在做什么,以允许容器从容器内部对其主机授予特定权限。


1
我有个主意,使容器可以连接到主机并创建新的容器。
Alex Ushakov 2015年

1
Docker似乎不喜欢您的相对坐骑。这应该起作用docker run --rm -v $(pwd)/mybashscript.sh:/work/mybashscript.sh ubuntu /work/mybashscript.sh
-KCD

5
第一行启动一个新的ubuntu容器,并将脚本安装在可以读取的地方。例如,它不允许容器访问主机文件系统。第二行将主机的暴露/usr/bin给容器。无论哪种情况,容器都不能完全访问主机系统。也许我错了,但这似乎是对一个错误问题的错误回答。
保罗

3
公平地说-这个问题很模糊。这个问题并没有要求“完全访问主机系统”。如上所述,如果bash脚本仅用于回显某些输出,则不需要对主机文件系统的任何访问。对于我的第二个示例(正在安装docker-compose),您所需的唯一权限是访问存储二进制文件的bin目录。正如我在一开始所说的那样,您必须对脚本在允许正确权限方面的工作有非常具体的想法。
Paul Becotte '17

1
尝试过此脚本后,脚本将在容器中执行,而不是在主机上执行
All2Pie

60

我使用的解决方案是通过连接到主机SSH并执行以下命令:

ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"

更新

由于这个答案一直在投票,所以我想提醒(强烈建议),用于调用脚本的帐户应该是完全没有权限的帐户,而只能执行该脚本sudo(从sudoers文件完成)。


作为另一种解决方法,容器可以输出一组命令,并且主机可以在容器退出后运行它们:eval $(docker run --rm -it container_name_to_output脚本)
parity3

我需要从Docker容器内部的主机上运行命令行,但是当我进入该容器时,ssh找不到。你有什么其他的建议?
罗恩·罗森菲尔德

@RonRosenfeld,您使用的是哪个Docker映像?如果是debian / ubuntu,请运行以下命令:apt update && apt install openssh-client
Mohammed Noureldin

可以在Synology NAS上安装任何东西。我怎么知道?
罗恩·罗森菲尔德

@RonRosenfeld,对不起,我不明白您的意思
Mohammed Noureldin

52

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

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

为了能够访问管道,您需要通过一个卷来安装它。

这类似于SSH机制(或类似的基于套接字的方法),但将您适当地限制在主机设备上,这可能更好。另外,您不必传递身份验证信息。

我唯一的警告是要谨慎选择为什么这么做。如果您想创建一种通过用户输入或其他方式进行自我升级的方法,那完全是要做的事情,但是您可能不想调用命令来获取一些配置数据,因为正确的方法是将其作为args传入/ volume到docker中。也要对您正在回避的事实保持谨慎,因此只需考虑许可模型。

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


14
注意:这是正确/最佳答案,还需要更多好评。其他所有答案都是在轻问“您要做什么”,并为某些内容添加例外。我有一个非常具体的用例,要求我能够做到这一点,这是恕我直言的唯一好答案。上面的SSH要求降低安全性/防火墙标准,并且docker run的东西完全是错误的。谢谢你 我认为这不会得到太多支持,因为这不是简单的复制/粘贴答案,但这就是答案。从我+100点,如果我能
法利

3
对于那些寻求更多信息的人,您可以使用在主机上运行的以下脚本:unix.stackexchange.com/a/369465当然,您必须使用'nohup'运行它并创建某种类型的Supervisor包装器为了维持生命(可能使用cron作业:P)
sucotronic

7
这可能是一个很好的答案。但是,如果您提供更多详细信息和更多命令行说明,那就更好了。可以详细说明吗?
Mohammed Noureldin

5
已投票,这有效!使用“ mkfifo host_executor_queue”在已安装卷的地方创建命名管道。然后,添加一个执行执行作为主机shell放入队列的命令的使用者,请使用'tail -f host_executor_queue | sh&'。末尾的&使其在后台运行。最后,使用“ echo touch foo> host_executor_queue”将命令推送到队列中-此测试在主目录中创建一个临时文件foo。如果希望使用者在系统启动时启动,请在'@reboot tail -f host_executor_queue | sh&'在crontab中。只需将相对路径添加到host_executor_queue。
天棚

1
有人可以用示例代码编辑答案吗?
卢卡斯·波特斯基

6

如果您不担心安全性,而只是想从另一个Docker容器(例如OP)中在主机上启动Docker容器,则可以通过共享docker监听端口与Docker容器共享在主机上运行的docker服务器。

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

您可以通过在启动命令中添加以下卷args来完成此操作

docker run -v /var/run/docker.sock:/var/run/docker.sock ...

或在docker组成文件中共享/var/run/docker.sock,如下所示:

version: '3'

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

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

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


1
考虑到docker必须安装在容器中,否则您还需要为docker二进制文件安装一个卷(例如/usr/bin/docker:/usr/bin/docker)。
格里

1
安装在容器中的泊坞窗插座时,请要小心,这可能是一个严重的安全问题:docs.docker.com/engine/security/security/...
DatGuyKaj

@DatGuyKaj谢谢,我已经编辑了答案以反映您的资源概述的问题。
Matt Bucci

这不能回答有关在主机上而不是在容器中运行脚本的问题
布兰登

5

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

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

我不得不承认,在我阅读他的解决方案时,我不知道什么叫管道。因此,我很难实现它(尽管它实际上非常简单),但是我确实成功了,所以很高兴为您解释我是如何做到的。因此,我的答案仅是详细说明您需要运行以使其正常运行的命令,但同样,功劳归功于他。

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

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

mkfifo /path/to/pipe/mypipe

管道已创建。类型

ls -l /path/to/pipe/mypipe 

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

prw-r--r-- 1 root root 0 mypipe

现在运行:

tail -f /path/to/pipe/mypipe

终端现在正在等待将数据发送到此管道中

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

然后运行:

echo "hello world" > /path/to/pipe/mypipe

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

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

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

eval "$(cat /path/to/pipe/mypipe)"

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

echo "ls -l" > /path/to/pipe/mypipe

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

第三部分-让它永远听

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

代替eval "$(cat /path/to/pipe/mypipe)",运行:

while true; do eval "$(cat /path/to/pipe/mypipe)"; done

(你不能这样)

现在,您可以无限发送一个命令,另一个命令将被执行,而不仅仅是第一个。

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

唯一的警告是,如果主机必须重新启动,则“ while”循环将停止工作。

要处理重启,请按以下步骤操作:

将放入带有标头while true; do eval "$(cat /path/to/pipe/mypipe)"; done的文件中execpipe.sh#!/bin/bash

别忘chmod +x

通过运行将其添加到crontab

crontab -e

然后添加

@reboot /path/to/execpipe.sh

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

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

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

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

第5部分-与Docker配合使用

如果像我一样同时使用docker compose和dockerfile,那么我要做的是:

假设您要挂载/hostpipe容器中的mypipe的父文件夹

添加:

VOLUME /hostpipe

在您的dockerfile中以创建挂载点

然后添加:

volumes:
   - /path/to/pipe:/hostpipe

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

重新启动您的Docker容器。

第6部分-测试

执行到您的Docker容器中:

docker exec -it <container> bash

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

cd /hostpipe && ls -l

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

echo "touch this_file_was_created_on_main_host_from_a_container.txt" > /hostpipe/mypipe

它应该工作!

警告:如果您有OSX(Mac OS)主机和Linux容器,它将无法正常工作(在此处进行解释https://stackoverflow.com/a/43474708/10018801并在此处发出https://github.com/docker / for-mac / issues / 483),因为管道的实现方式不同,因此您只能从Linux读取从Linux写入管道的内容,而从Mac OS写入管道的内容则只能由Mac OS(此句子可能不太准确,但请注意存在跨平台问题)。

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

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

这是我从节点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);

这很好。那么安全性呢?我想使用它从正在运行的容器中启动/停止Docker容器吗?我是否仅使dockeruser拥有除运行docker命令以外的任何特权?
克里斯托弗·范·伍恩塞尔

4

编写一个侦听端口(例如8080)的简单服务器python服务器,将端口-p 8080:8080与容器绑定,向localhost:8080发出HTTP请求,以使python服务器使用popen运行shell脚本,运行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()

IMO这是最好的答案。必须通过某种API(例如REST)在主机上运行任意命令。这是可以强制执行安全性和正确控制运行进程的唯一方法(例如,终止,处理stdin,stdout,退出代码等)。如果可以的话,这个API是否可以在Docker内运行就很好了,但是我个人不介意直接在主机上运行它。
barney765

2

我的懒惰使我找到了一个最简单的解决方案,但这里没有给出答案。

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

为了从docker容器中获取完整的shell到Linux主机,您需要做的是:

docker run --privileged --pid=host -it alpine:3.8 \
nsenter -t 1 -m -u -n -i sh

说明:

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

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

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

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


1
docker run --detach-keys="ctrl-p" -it -v /:/mnt/rootdir --name testing busybox
# chroot /mnt/rootdir
# 

3
尽管此答案可能会解决OP的问题,但建议您解释一下OP的工作原理以及解决问题的原因。这可以帮助新开发人员了解正在发生的事情以及如何自行解决此问题和类似问题。感谢您的贡献!
卡列布·克里夫特

1

我有一个简单的方法。

步骤1:挂载/var/run/docker.sock:/var/run/docker.sock(因此您将能够在容器内执行docker命令)

步骤2:在您的容器中执行以下操作。这里的关键部分是(--network host,因为它将从主机上下文执行)

docker run -i --rm-网络主机-v /opt/test.sh:/test.sh高山:3.7 sh /test.sh

test.sh应该包含一些您需要的命令(ifconfig,netstat等)。现在,您将能够获取主机上下文输出。


2
根据有关使用主机网络进行联网的docker官方文档,“但是,在所有其他方式(例如存储,进程名称空间和用户名称空间)中,进程与主机隔离。” 检出-docs.docker.com/network/network-tutorial-host
Peter Mutisya

0

正如Marcus提醒的那样,泊坞窗基本上是进程隔离。从docker 1.8开始,您可以在主机和容器之间双向复制文件,请参阅docker cp

https://docs.docker.com/reference/commandline/cp/

复制文件后,您可以在本地运行它


1
我知道。换句话说,如何从docker容器中运行此脚本?
亚历克斯·乌沙科夫


2
@AlexUshakov:没办法。这样做将破坏Docker的许多优势。不要这样 不要尝试。重新考虑您需要做什么。
MarcusMüller2015年

另请参见弗拉德的骗术Forums.docker.com/t/…– 2015
user2915097

1
您总是可以在主机上获取容器中某个变量的值,例如myvalue=$(docker run -it ubuntu echo $PATH)并定期在脚本外壳中对其进行测试(当然,您将使用除$ PATH以外的其他值,仅作为示例),是一些特定的值,您启动脚本
user2915097

0

您可以使用管道概念,但可以使用主机和fswatch上的文件来实现从Docker容器在主机上执行脚本的目标。像这样(使用后果自负):

#! /bin/bash

touch .command_pipe
chmod +x .command_pipe

# Use fswatch to execute a command on the host machine and log result
fswatch -o --event Updated .command_pipe | \
            xargs -n1 -I "{}"  .command_pipe >> .command_pipe_log  &

 docker run -it --rm  \
   --name alpine  \
   -w /home/test \
   -v $PWD/.command_pipe:/dev/command_pipe \
   alpine:3.7 sh

rm -rf .command_pipe
kill %1

在此示例中,在容器内向/ dev / command_pipe发送命令,如下所示:

/home/test # echo 'docker network create test2.network.com' > /dev/command_pipe

在主机上,您可以检查是否已创建网络:

$ docker network ls | grep test2
8e029ec83afe        test2.network.com                            bridge              local

-7

扩展user2915097的 响应

隔离的思想是能够非常清楚地限制应用程序/进程/容器(无论您的角度如何)对主机系统的作用。因此,能够复制和执行文件确实会破坏整个概念。

是。但这有时是必要的。

不。不是这样,否则Docker不是正确的选择。你应该做的是宣布一个明确的接口,你想要做什么(例如更新主机配置),并写一个最小的客户端/服务器做的正是这一点,仅此而已。通常,这似乎不是很理想。在许多情况下,您只需要重新考虑您的方法并消除这种需求即可。当基本上所有东西都是使用某种协议可以到达的服务时,Docker就应运而生了。我想不出Docker容器有任何适当的用例来获得在主机上执行任意内容的权利。


我有用例:我有dockerized服务A(github上的src)。在A回购协议中,我创建了适当的钩子,在执行“ git pull”命令后,将创建新的docker映像并运行它们(当然,并删除旧容器)。下一步:github有Web钩子,允许在按下master之后向任意端点链接创建POST请求。因此,我将不会创建将成为该端点的dockerized服务B,并且该服务将仅在HOST机器中的存储库A中运行“ git pull”(重要:命令“ git pull”必须在HOST环境中执行-不在B环境中执行,因为B无法在B内运行新的容器A ...)
KamilKiełczewski17年

1
问题:除了Linux,Git和Docker外,我不想在主机中什么都没有。我想拥有dockerizet服务A和服务B(实际上是git-push处理程序,在有人对主服务器执行git push之后,它对repo A执行git pull)。因此,git自动部署是一个有问题的用例
KamilKiełczewski17年

@KamilKiełczewski我正在尝试完全相同,您找到解决方案了吗?
user871784 '17

1
说“不,不是这样”的想法狭narrow,并假设您了解世界上的每个用例。我们的用例正在运行测试。他们需要在容器中运行以正确测试环境,但是鉴于测试的性质,他们还需要在主机上执行脚本。
塞尼卡·冈萨雷斯

1
对于那些想知道为什么我留下-7答案的人: a)容易犯错。我错了。可以在此处进行记录。b)评论实际上是有价值的;删除答案也会将其删除。c)它仍然提供了一种可能是明智的考虑的观点(如果不需要,请不要打破孤立。不过有时候也必须这样做)。
MarcusMüller
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.