Wie steuere ich den Host vom Docker-Container aus?
Wie kann man beispielsweise ein in das Host-Bash-Skript kopiertes Skript ausführen?
Wie steuere ich den Host vom Docker-Container aus?
Wie kann man beispielsweise ein in das Host-Bash-Skript kopiertes Skript ausführen?
Antworten:
Das hängt WIRKLICH davon ab, wozu Sie dieses Bash-Skript benötigen!
Wenn das Bash-Skript beispielsweise nur eine Ausgabe wiedergibt, können Sie dies einfach tun
docker run --rm -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Eine andere Möglichkeit besteht darin, dass das Bash-Skript Software installieren soll - beispielsweise das Skript zur Installation von Docker-Compose. du könntest so etwas tun
docker run --rm -v /usr/bin:/usr/bin --privileged -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
An diesem Punkt müssen Sie jedoch genau wissen, was das Skript tut, um die spezifischen Berechtigungen, die es auf Ihrem Host benötigt, aus dem Container heraus zuzulassen.
docker run --rm -v $(pwd)/mybashscript.sh:/work/mybashscript.sh ubuntu /work/mybashscript.sh
/usr/bin
dem Container ausgesetzt. In keinem Fall hat der Container vollen Zugriff auf das Hostsystem. Vielleicht irre ich mich, aber es scheint eine schlechte Antwort auf eine schlechte Frage zu sein.
Die Lösung, die ich verwende, besteht darin, eine Verbindung zum Host herzustellen SSH
und den Befehl wie folgt auszuführen:
ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"
Da diese Antwort immer mehr Stimmen erhält, möchte ich daran erinnern (und wärmstens empfehlen), dass das Konto, das zum Aufrufen des Skripts verwendet wird, ein Konto ohne Berechtigungen sein sollte, das dieses Skript jedoch nur als sudo
(das kann sein) ausführt aus sudoers
Datei gemacht).
ssh
wird sie nicht gefunden. Haben Sie weitere Vorschläge?
apt update && apt install openssh-client
.
Verwendete eine Named Pipe. Erstellen Sie auf dem Host-Betriebssystem ein Skript zum Schleifen und Lesen von Befehlen, und rufen Sie dann eval auf.
Lassen Sie den Docker-Container in die Named Pipe lesen.
Um auf das Rohr zugreifen zu können, müssen Sie es über ein Volume bereitstellen.
Dies ähnelt dem SSH-Mechanismus (oder einer ähnlichen Socket-basierten Methode), beschränkt Sie jedoch ordnungsgemäß auf das Host-Gerät, was wahrscheinlich besser ist. Außerdem müssen Sie keine Authentifizierungsinformationen weitergeben.
Meine einzige Warnung ist, vorsichtig zu sein, warum Sie dies tun. Es ist absolut etwas zu tun, wenn Sie eine Methode zum Selbst-Upgrade mit Benutzereingaben oder was auch immer erstellen möchten, aber wahrscheinlich keinen Befehl aufrufen möchten, um einige Konfigurationsdaten abzurufen, da der richtige Weg darin besteht, diese als Argumente zu übergeben / Volume in Docker. Seien Sie auch vorsichtig mit der Tatsache, dass Sie eine Bewertung vornehmen. Denken Sie also einfach über das Berechtigungsmodell nach.
Einige der anderen Antworten, z. B. das Ausführen eines Skripts unter einem Volume, funktionieren nicht generisch, da sie nicht auf die vollständigen Systemressourcen zugreifen können. Je nach Verwendung ist dies jedoch möglicherweise angemessener.
Wenn Sie sich keine Sorgen um die Sicherheit machen und einfach einen Docker-Container auf dem Host aus einem anderen Docker-Container wie dem OP heraus starten möchten, können Sie den auf dem Host ausgeführten Docker-Server für den Docker-Container freigeben, indem Sie dessen Listen-Socket freigeben.
Bitte sehen Sie unter https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface nach, ob Ihre persönliche Risikotoleranz dies für diese bestimmte Anwendung zulässt.
Sie können dies tun, indem Sie Ihrem Startbefehl die folgenden Volume-Argumente hinzufügen
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
oder indem Sie /var/run/docker.sock in Ihrer Docker-Compose-Datei wie folgt freigeben:
version: '3'
services:
ci:
command: ...
image: ...
volumes
- /var/run/docker.sock:/var/run/docker.sock
Wenn Sie den Docker-Startbefehl in Ihrem Docker-Container ausführen, sieht der Docker-Server, der auf Ihrem Host ausgeführt wird, die Anforderung und stellt den Geschwistercontainer bereit.
Bildnachweis: http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
/usr/bin/docker:/usr/bin/docker
. B. ).
Schreiben Sie einen einfachen Python-Server, der einen Port überwacht (z. B. 8080), binden Sie den Port -p 8080: 8080 an den Container, senden Sie eine HTTP-Anfrage an localhost: 8080, um den Python-Server aufzufordern, Shell-Skripte mit Popen auszuführen, einen Curl auszuführen oder Schreiben von Code, um eine HTTP-Anfrage zu erstellen 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()
Ich habe einen einfachen Ansatz.
Schritt 1: Mounten Sie /var/run/docker.sock:/var/run/docker.sock (damit Sie Docker-Befehle in Ihrem Container ausführen können)
Schritt 2: Führen Sie dies unten in Ihrem Container aus. Der Schlüsselteil hier ist ( --network host, da dies aus dem Hostkontext ausgeführt wird)
Docker run -i --rm --network host -v /opt/test.sh:/test.sh alpine: 3.7 sh /test.sh
test.sh sollte einige Befehle enthalten (ifconfig, netstat usw.), die Sie benötigen. Jetzt können Sie die Host-Kontextausgabe erhalten.
Meine Faulheit führte mich dazu, die einfachste Lösung zu finden, die hier nicht als Antwort veröffentlicht wurde.
Es basiert auf dem großartigen Artikel von luc juggery .
Alles, was Sie tun müssen, um eine vollständige Shell für Ihren Linux-Host aus Ihrem Docker-Container heraus zu erhalten, ist:
docker run --privileged --pid=host -it alpine:3.8 \
nsenter -t 1 -m -u -n -i sh
Erläuterung:
--privileged: Erteilt dem Container zusätzliche Berechtigungen. Dadurch kann der Container auf die Geräte des Hosts (/ dev) zugreifen.
--pid = host: Ermöglicht den Containern die Verwendung des Prozessbaums des Docker-Hosts (der VM, auf der der Docker-Dämon ausgeführt wird). nsenter-Dienstprogramm: Ermöglicht die Ausführung eines Prozesses in vorhandenen Namespaces (den Bausteinen, die Container isolieren).
Mit nsenter (-t 1 -m -u -n -i sh) kann der Prozess sh im gleichen Isolationskontext wie der Prozess mit PID 1 ausgeführt werden. Der gesamte Befehl stellt dann eine interaktive sh-Shell in der VM bereit
Dieses Setup hat erhebliche Auswirkungen auf die Sicherheit und sollte mit Vorsicht (falls vorhanden) verwendet werden.
Diese Antwort ist nur eine detailliertere Version der Lösung von Bradford Medeiros , die sich auch für mich als die beste Antwort herausstellte. Der Verdienst geht also an ihn.
In seiner Antwort erklärt er, was zu tun ist ( Named Pipes ), aber nicht genau, wie es zu tun ist.
Ich muss zugeben, dass ich zum Zeitpunkt des Lesens seiner Lösung nicht wusste, was Pfeifen heißen. Ich hatte Mühe, es zu implementieren (obwohl es eigentlich ganz einfach ist), aber es gelang mir, und ich helfe gerne, indem ich erkläre, wie ich es gemacht habe. In meiner Antwort geht es also nur darum, die Befehle zu beschreiben, die Sie ausführen müssen, damit es funktioniert, aber auch hier geht ihm die Ehre zu.
Wählen Sie auf dem Haupthost den Ordner aus, in dem Sie beispielsweise Ihre Named-Pipe-Datei und beispielsweise /path/to/pipe/
einen Pipe-Namen ablegen möchten mypipe
, und führen Sie dann Folgendes aus:
mkfifo /path/to/pipe/mypipe
Die Pipe wird erstellt. Art
ls -l /path/to/pipe/mypipe
Überprüfen Sie, ob die Zugriffsrechte mit "p" beginnen, z
prw-r--r-- 1 root root 0 mypipe
Führen Sie jetzt aus:
tail -f /path/to/pipe/mypipe
Das Terminal wartet nun darauf, dass Daten in diese Pipe gesendet werden
Öffnen Sie nun ein weiteres Terminalfenster.
Und dann laufen:
echo "hello world" > /path/to/pipe/mypipe
Überprüfen Sie das erste Terminal (das mit tail -f
), es sollte "Hallo Welt" anzeigen.
Führen Sie auf dem Host-Container tail -f
diesen Befehl aus, der ihn nur als Befehle ausführt , anstatt nur das auszugeben, was als Eingabe gesendet wird:
eval "$(cat /path/to/pipe/mypipe)"
Versuchen Sie dann vom anderen Terminal aus Folgendes auszuführen:
echo "ls -l" > /path/to/pipe/mypipe
Gehen Sie zurück zum ersten Terminal und Sie sollten das Ergebnis des ls -l
Befehls sehen.
Möglicherweise haben Sie bemerkt, dass im vorherigen Teil direkt nach der ls -l
Anzeige der Ausgabe nicht mehr auf Befehle gewartet wird.
eval "$(cat /path/to/pipe/mypipe)"
Führen Sie stattdessen Folgendes aus:
while true; do eval "$(cat /path/to/pipe/mypipe)"; done
(Das kannst du nicht sagen)
Jetzt können Sie eine unbegrenzte Anzahl von Befehlen nacheinander senden. Alle Befehle werden ausgeführt, nicht nur der erste.
Die einzige Einschränkung ist, dass die "while" -Schleife nicht mehr funktioniert, wenn der Host neu gestartet werden muss.
Um den Neustart zu handhaben, habe ich Folgendes getan:
Fügen Sie das while true; do eval "$(cat /path/to/pipe/mypipe)"; done
in eine Datei execpipe.sh
mit #!/bin/bash
Header ein
Vergiss chmod +x
es nicht
Fügen Sie es crontab hinzu, indem Sie es ausführen
crontab -e
Und dann hinzufügen
@reboot /path/to/execpipe.sh
Testen Sie es an diesem Punkt: Starten Sie Ihren Server neu und geben Sie nach dem Sichern einige Befehle in die Pipe ein und prüfen Sie, ob sie ausgeführt werden. Natürlich können Sie die Ausgabe von Befehlen ls -l
nicht sehen, touch somefile
es hilft also nicht, aber es hilft.
Eine weitere Option besteht darin, das Skript so zu ändern, dass die Ausgabe in eine Datei eingefügt wird, z.
while true; do eval "$(cat /path/to/pipe/mypipe)" &> /somepath/output.txt; done
Jetzt können Sie ausführen ls -l
und die Ausgabe (sowohl stdout als auch stderr mit &>
in bash) sollte in output.txt sein.
Wenn Sie Docker Compose und Dockerfile wie ich verwenden, habe ich Folgendes getan:
Angenommen, Sie möchten den übergeordneten Ordner von mypipe wie /hostpipe
in Ihrem Container bereitstellen
Füge das hinzu:
VOLUME /hostpipe
in Ihrer Docker-Datei, um einen Mount-Punkt zu erstellen
Dann füge folgendes hinzu:
volumes:
- /path/to/pipe:/hostpipe
Erstellen Sie in Ihrem Docker eine Compose-Datei, um / path / to / pipe als / hostpipe zu mounten
Starten Sie Ihre Docker-Container neu.
In Ihren Docker-Container ausführen:
docker exec -it <container> bash
Gehen Sie in den Mount-Ordner und überprüfen Sie, ob Sie die Pipe sehen können:
cd /hostpipe && ls -l
Versuchen Sie nun, einen Befehl aus dem Container heraus auszuführen:
echo "touch this_file_was_created_on_main_host_from_a_container.txt" > /hostpipe/mypipe
Und es sollte funktionieren!
WARNUNG: Wenn Sie einen OSX-Host (Mac OS) und einen Linux-Container haben, funktioniert dies nicht (Erklärung hier https://stackoverflow.com/a/43474708/10018801 und Problem hier https://github.com/docker / for-mac / issue / 483 ), da die Pipe-Implementierung nicht identisch ist. Was Sie also unter Linux in die Pipe schreiben, kann nur von Linux gelesen werden, und was Sie unter Mac OS in die Pipe schreiben, kann nur von a gelesen werden Mac OS (dieser Satz ist möglicherweise nicht sehr genau, aber beachten Sie, dass ein plattformübergreifendes Problem vorliegt).
Wenn ich beispielsweise mein Docker-Setup in DEV von meinem Mac OS-Computer aus ausführe, funktioniert die oben erläuterte Named Pipe nicht. Aber in Staging und Produktion habe ich Linux-Host und Linux-Container, und es funktioniert perfekt.
So sende ich einen Befehl von meinem js-Container des Knotens an den Haupthost und rufe die Ausgabe ab:
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);
Wie Marcus erinnert, ist Docker im Grunde genommen Prozessisolation. Ab Docker 1.8 können Sie Dateien in beide Richtungen zwischen Host und Container kopieren (siehe Dokument von)docker cp
https://docs.docker.com/reference/commandline/cp/
Sobald eine Datei kopiert wurde, können Sie sie lokal ausführen
myvalue=$(docker run -it ubuntu echo $PATH)
und ihn regelmäßig in einer Skript-Shell testen (natürlich verwenden Sie etwas anderes als $ PATH, dies ist nur ein Beispiel), wenn dies der Fall ist
docker run --detach-keys="ctrl-p" -it -v /:/mnt/rootdir --name testing busybox
# chroot /mnt/rootdir
#
Sie können das Pipe-Konzept verwenden, aber eine Datei auf dem Host und fswatch verwenden , um das Ziel zu erreichen, ein Skript aus einem Docker-Container auf dem Host-Computer auszuführen. So (Verwendung auf eigenes Risiko):
#! /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
In diesem Beispiel senden Sie im Container Befehle wie folgt an / dev / command_pipe:
/home/test # echo 'docker network create test2.network.com' > /dev/command_pipe
Auf dem Host können Sie überprüfen, ob das Netzwerk erstellt wurde:
$ docker network ls | grep test2
8e029ec83afe test2.network.com bridge local
So erweitern Sie die Antwort von user2915097 :
Die Idee der Isolation besteht darin, in der Lage zu sein, die Auswirkungen einer Anwendung / eines Prozesses / eines Containers (unabhängig von Ihrem Blickwinkel) auf das Hostsystem sehr deutlich einzuschränken. Das Kopieren und Ausführen einer Datei würde also das gesamte Konzept brechen.
Ja. Aber es ist manchmal notwendig.
Nein, das ist nicht der Fall, oder Docker ist nicht das Richtige. Was Sie tun sollten, ist eine klare Schnittstelle für das zu deklarieren, was Sie tun möchten (z. B. eine Host-Konfiguration aktualisieren), und einen minimalen Client / Server zu schreiben, um genau das zu tun , und nichts weiter. Im Allgemeinen scheint dies jedoch nicht sehr wünschenswert zu sein. In vielen Fällen sollten Sie einfach Ihren Ansatz überdenken und diese Notwendigkeit beseitigen. Docker entstand, als im Grunde alles ein Dienst war, der mit einem Protokoll erreichbar war. Ich kann mir keinen richtigen Verwendungszweck für einen Docker-Container vorstellen, der die Rechte erhält, beliebige Dinge auf dem Host auszuführen.
A
(src auf Github). Im A
Repo erstelle ich richtige Hooks, die nach dem Befehl 'git pull' ein neues Docker-Image erstellen und ausführen (und natürlich den alten Container entfernen). Weiter: Github verfügt über Web-Hooks, mit denen POST-Anforderungen an beliebige Endpunktverbindungen nach dem Push-on-Master erstellt werden können. Ich werde also keinen Docker-Dienst B erstellen, der dieser Endpunkt ist und der nur 'git pull' in Repo A auf der HOST-Maschine ausführt (wichtig: Der Befehl 'git pull' muss in der HOST-Umgebung ausgeführt werden - nicht in der B-Umgebung, weil B. kann keinen neuen Container A in B laufen lassen ...)