![](/img/trans.png)
[英]How to run shell script on Host from jenkins docker container?
[英]How to run shell script on host from docker container?
如何從 docker 容器控制主機?
例如,如何執行復制到主機 bash 腳本?
使用命名管道。 在主機操作系統上,創建一個腳本來循環和讀取命令,然后調用eval
。
讓 docker 容器讀取到該命名管道。
為了能夠訪問管道,您需要通過卷安裝它。
這類似於 SSH 機制(或類似的基於套接字的方法),但將您正確地限制在主機設備上,這可能更好。 另外,您不必傳遞身份驗證信息。
我唯一的警告是要小心你為什么這樣做。 如果您想創建一種使用用戶輸入或其他方式進行自我升級的方法,這完全是一件事情,但您可能不想調用命令來獲取一些配置數據,因為正確的方法是將其傳遞為args/volume 進入 docker。 另外,請注意您正在評估的事實,因此請考慮一下權限模型。
其他一些答案,例如運行腳本。 由於它們無法訪問完整的系統資源,因此通常無法在卷下工作,但根據您的使用情況,它可能更合適。
這個答案只是Bradford Medeiros 解決方案的更詳細版本,對我來說這也是最好的答案,所以歸功於他。
在他的回答中,他解釋了要做什么(命名管道),但不完全解釋如何去做。
我不得不承認,當我閱讀他的解決方案時,我不知道什么是命名管道。 所以我努力實現它(雖然它實際上很簡單),但我確實成功了。 所以我回答的重點只是詳細說明您需要運行的命令才能使其正常工作,但再次感謝他。
在主主機上,選擇要放置命名管道文件的文件夾,例如/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”
在主機容器上,不要運行僅輸出作為輸入發送的任何內容的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
(你可以不知道)
現在您可以一個接一個地發送無限數量的命令,它們都將被執行,而不僅僅是第一個。
唯一需要注意的是,如果主機必須重新啟動,“while”循環將停止工作。
要處理重新啟動,這里我做了什么:
將while true; do eval "$(cat /path/to/pipe/mypipe)"; done
while true; do eval "$(cat /path/to/pipe/mypipe)"; done
在帶有#!/bin/bash
標頭的名為execpipe.sh
的文件中while true; do eval "$(cat /path/to/pipe/mypipe)"; done
不要忘記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 中。
如果您像我一樣同時使用 docker compose 和 dockerfile,這就是我所做的:
假設您要將 mypipe 的父文件夾掛載為容器中的/hostpipe
添加這個:
VOLUME /hostpipe
在您的 dockerfile 中以創建掛載點
然后添加這個:
volumes:
- /path/to/pipe:/hostpipe
在您的 docker compose 文件中,以便將 /path/to/pipe 掛載為 /hostpipe
重新啟動 docker 容器。
執行到您的 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寫入管道的內容只能由a讀取Mac OS(這句話可能不太准確,但請注意存在跨平台問題)。
例如,當我從我的 Mac OS 計算機在 DEV 中運行我的 docker setup 時,如上所述的命名管道不起作用。 但在登台和生產中,我有 Linux 主機和 Linux 容器,而且它運行良好。
以下是我如何從 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);
我使用的解決方案是通過SSH
連接到主機並執行如下命令:
ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"
由於這個答案不斷獲得投票,我想提醒(並強烈推薦),用於調用腳本的帳戶應該是一個完全沒有權限的帳戶,但只能將該腳本作為sudo
執行(可以從sudoers
文件中完成)。
我上面建議的解決方案只是我對 Docker 比較陌生時使用的解決方案。 現在在 2021 年,看看有關命名管道的答案。 這似乎是一個更好的解決方案。
但是,那里沒有人提到安全性。 將評估通過管道發送的命令的腳本(調用eval
的腳本)實際上不能將eval
用於整個管道輸出,而是處理特定情況並根據發送的文本調用所需的命令,否則任何可以做任何事情都可以通過管道發送。
這真的取決於你需要那個 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
但是在這一點上,您真的必須深入了解腳本正在做什么以允許從容器內部對您的主機所需的特定權限。
我的懶惰使我找到了未在此處作為答案發布的最簡單的解決方案。
它基於luc juggery的精彩文章。
為了從 docker 容器中獲得 linux 主機的完整 shell,您需要做的就是:
docker run --privileged --pid=host -it alpine:3.8 \
nsenter -t 1 -m -u -n -i sh
解釋:
--privileged :授予容器額外的權限,它允許容器訪問主機的設備(/dev)
--pid=host :允許容器使用 Docker 主機(運行 Docker 守護進程的虛擬機)的進程樹 nsenter 實用程序:允許在現有命名空間(為容器提供隔離的構建塊)中運行進程
nsenter (-t 1 -m -u -n -i sh) 允許在與 PID 1 的進程相同的隔離上下文中運行進程 sh。然后整個命令將在 VM 中提供交互式 sh shell
此設置具有重大安全隱患,應謹慎使用(如果有)。
編寫一個簡單的服務器 python 服務器偵聽端口(比如 8080),將端口 -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()
如果您不擔心安全性並且您只是想從另一個 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 ...
或通過在您的 docker compose 文件中共享 /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/
docker run --detach-keys="ctrl-p" -it -v /:/mnt/rootdir --name testing busybox
# chroot /mnt/rootdir
#
我有一個簡單的方法。
第 1 步:掛載 /var/run/docker.sock:/var/run/docker.sock (這樣您就可以在容器內執行 docker 命令)
第 2 步:在您的容器中執行以下操作。 這里的關鍵部分是( --network host因為這將從主機上下文執行)
docker run -i --rm --network host -v /opt/test.sh:/test.sh alpine:3.7 sh /test.sh
test.sh 應該包含一些你需要的命令(ifconfig、netstat 等)。 現在您將能夠獲得主機上下文輸出。
正如 Marcus 所提醒的,docker 基本上是進程隔離。 從 docker 1.8 開始,您可以在主機和容器之間雙向復制文件,請參閱docker cp
的文檔
https://docs.docker.com/reference/commandline/cp/
復制文件后,您可以在本地運行它
您可以使用管道概念,但使用主機上的文件和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
在我的場景中,我只是在容器中 ssh 登錄主機(通過主機 ip),然后我可以對主機做任何我想做的事情
我發現使用命名管道的答案很棒。 但我想知道是否有辦法獲得執行命令的 output。
解決方案是創建兩個命名管道:
mkfifo /path/to/pipe/exec_in
mkfifo /path/to/pipe/exec_out
然后,@Vincent 建議的使用循環的解決方案將變為:
# on the host
while true; do eval "$(cat exec_in)" > exec_out; done
然后在 docker 容器上,我們可以執行命令並使用以下命令獲取 output:
# on the container
echo "ls -l" > /path/to/pipe/exec_in
cat /path/to/pipe/exec_out
如果有人感興趣,我需要在容器上的主機上使用故障轉移 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"
要擴展user2915097 的響應:
隔離的想法是能夠非常清楚地限制應用程序/進程/容器(無論您的角度是什么)可以對主機系統執行的操作。 因此,能夠復制和執行文件將真正打破整個概念。
是的。 但有時它是必要的。
不,事實並非如此,或者 Docker 不是正確使用的東西。 您應該做的是為您想要做的事情聲明一個清晰的接口(例如更新主機配置),並編寫一個最小的客戶端/服務器來完全做到這一點,僅此而已。 然而,一般來說,這似乎不是很理想。 在許多情況下,您應該簡單地重新考慮您的方法並消除這種需求。 當基本上所有東西都是可以使用某種協議訪問的服務時,Docker 就應運而生了。 我想不出任何合適的 Docker 容器獲得在主機上執行任意內容的權利的用例。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.