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

时间:2015-08-23 06:44:25

标签: docker

如何从docker容器控制主机?

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

13 个答案:

答案 0 :(得分:36)

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

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

更新

由于这个答案不断获得投票,我想提醒(并强烈推荐),用于调用脚本的帐户应该是一个完全没有权限的帐户,但只执行该脚本{ {1}}(可以从sudo文件中完成)。

答案 1 :(得分:17)

这真的取决于你需要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

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

答案 2 :(得分:16)

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

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

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

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

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

其他一些答案,例如运行脚本。在一个卷下,由于无法访问完整的系统资源而无法正常工作,但根据您的使用情况,它可能更合适

答案 3 :(得分:8)

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

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

我必须承认,在我阅读他的解决方案时我不知道什么叫管道。因此,我努力实现它(尽管实际上非常简单),但是我确实取得了成功,所以我很乐意通过解释如何做到这一点来提供帮助。 因此,我的答案仅是详细说明您需要运行以使其正常运行的命令,但再次感谢他。

PART 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”

PART 2-通过管道运行命令

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

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

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

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

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

PART 3-使其永远聆听

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

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

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

(您不能对此说)

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

PART 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中。

PART 5-使它与docker一起使用

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

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

添加此内容:

VOLUME /hostpipe

在您的dockerfile中创建挂载点

然后添加此内容:

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

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

重新启动您的Docker容器。

PART 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容器,并且运行良好。

PART 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);

答案 4 :(得分:2)

正如Marcus所说,docker基本上是流程隔离。从docker 1.8开始,您可以在主机和容器之间双向复制文件,请参阅docker cp的文档

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

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

答案 5 :(得分:2)

我的懒惰使我找到了一个最简单的解决方案,该解决方案并未在此处发布。

它基于great articleluc 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

此设置对安全性有重大影响,应谨慎使用(如果有)。

答案 6 :(得分:1)

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

答案 7 :(得分:1)

如果您只是想从另一个Docker容器(如OP)中在主机上启动Docker容器,则可以通过共享Docker容器的侦听套接字与Docker容器共享在主机上运行的Docker服务器。

您可以通过在启动命令中添加以下卷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/

答案 8 :(得分:0)

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

答案 9 :(得分:0)

我有一个简单的方法。

第1步:挂载/var/run/docker.sock:/var/run/docker.sock(这样您就可以在容器内执行docker命令)

第2步:在容器内部执行以下操作。此处的关键部分是(-网络主机,因为这将从主机上下文执行)

  

docker run -i --rm --network host -v /opt/test.sh:/test.sh高山:3.7   sh /test.sh

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

答案 10 :(得分: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

答案 11 :(得分:-1)

在我的场景中,我只是在容器内通过 ssh 登录主机(通过主机 ip),然后我可以对主机做任何我想做的事情

答案 12 :(得分:-2)

展开user2915097's response

隔离的想法是能够限制应用程序/进程/容器(无论你的角度是什么)可以非常清楚地对主机系统做什么。因此,能够复制和执行文件将真正打破整个概念。

  

是。但它有时是必要的。

没有。事实并非如此,或者Docker不适合使用。你应该做的是为你想要做的事情声明一个清晰的界面(例如更新一个主机配置),并写一个最小的客户端/服务器来完全 那个而已。但是,一般来说,这似乎并不是很理想。在许多情况下,您应该简单地重新思考您的方法并根除这种需求。当基本上所有东西都是使用某种协议可以访问的服务时,Docker就存在了。我无法想到Docker容器的任何正确用法,它有权在主机上执行任意内容。