Jak kontrolować hosta z kontenera Docker?
Na przykład, jak wykonać skopiowany na hosta skrypt bash?
Jak kontrolować hosta z kontenera Docker?
Na przykład, jak wykonać skopiowany na hosta skrypt bash?
Odpowiedzi:
To NAPRAWDĘ zależy od tego, do czego potrzebujesz tego skryptu bash!
Na przykład, jeśli skrypt bash po prostu wyświetla jakieś wyjście, możesz po prostu to zrobić
docker run --rm -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Inną możliwością jest to, że chcesz, aby skrypt bash zainstalował jakieś oprogramowanie - powiedzmy, że skrypt instaluje docker-compose. możesz zrobić coś takiego
docker run --rm -v /usr/bin:/usr/bin --privileged -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Ale w tym momencie naprawdę zaczynasz dokładnie wiedzieć, co robi skrypt, aby zezwolić na określone uprawnienia, których potrzebuje na twoim hoście z wnętrza kontenera.
docker run --rm -v $(pwd)/mybashscript.sh:/work/mybashscript.sh ubuntu /work/mybashscript.sh
/usr/binkontenerze. W żadnym przypadku kontener nie ma pełnego dostępu do systemu hosta. Może się mylę, ale wydaje się, że to zła odpowiedź na złe pytanie.
Rozwiązaniem, którego używam, jest połączenie z hostem SSHi wykonanie następującego polecenia:
ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"
Ponieważ ta odpowiedź wciąż zbiera głosy, chciałbym przypomnieć (i bardzo polecam), że konto, które jest używane do wywoływania skryptu, powinno być kontem bez żadnych uprawnień, a jedynie wykonywać ten skrypt jako sudo(to może być zrobione z sudoerspliku).
sshnie można go znaleźć. Czy masz jakieś inne sugestie?
apt update && apt install openssh-client.
Użyto nazwanej potoku. W systemie operacyjnym hosta utwórz skrypt do zapętlania i odczytywania poleceń, a następnie wywołaj na nim eval.
Odczytaj kontener Dockera do tego nazwanego potoku.
Aby mieć dostęp do rury, należy ją zamontować za pomocą woluminu.
Jest to podobne do mechanizmu SSH (lub podobnej metody opartej na gnieździe), ale ogranicza cię odpowiednio do urządzenia hosta, co prawdopodobnie jest lepsze. Ponadto nie musisz przekazywać informacji uwierzytelniających.
Moje jedyne ostrzeżenie to ostrożność, dlaczego to robisz. To całkiem coś do zrobienia, jeśli chcesz stworzyć metodę samodzielnej aktualizacji z danymi wejściowymi użytkownika lub czymkolwiek, ale prawdopodobnie nie chcesz wywoływać polecenia, aby uzyskać dane konfiguracyjne, ponieważ właściwym sposobem byłoby przekazanie ich jako argumentów / volume do dockera. Uważaj również na fakt, że ewaluujesz, więc po prostu pomyśl o modelu uprawnień.
Niektóre z innych odpowiedzi, takie jak uruchomienie skryptu pod woluminem, nie będą działać ogólnie, ponieważ nie będą miały dostępu do pełnych zasobów systemowych, ale może być bardziej odpowiednie w zależności od wykorzystania.
Ta odpowiedź jest po prostu bardziej szczegółową wersją rozwiązania Bradforda Medeirosa , które również dla mnie okazało się najlepszą odpowiedzią, więc zasługa jego.
W swojej odpowiedzi wyjaśnia CO robić ( nazwane rury ), ale nie dokładnie JAK to zrobić.
Muszę przyznać, że nie wiedziałem, jak nazywa się rury, kiedy czytałem jego rozwiązanie. Więc starałem się go wdrożyć (chociaż jest to naprawdę proste), ale mi się udało, więc z przyjemnością pomogę, wyjaśniając, jak to zrobiłem. Tak więc celem mojej odpowiedzi jest po prostu wyszczególnienie poleceń, które musisz uruchomić, aby działało, ale znowu zasługa jego.
Na głównym hoście wybierz folder, w którym chcesz umieścić swój nazwany plik potoku, na przykład /path/to/pipe/i nazwę potoku mypipe, a następnie uruchom:
mkfifo /path/to/pipe/mypipe
Rura została utworzona. Rodzaj
ls -l /path/to/pipe/mypipe
I sprawdź, czy prawa dostępu zaczynają się od „p”, na przykład
prw-r--r-- 1 root root 0 mypipe
Teraz biegnij:
tail -f /path/to/pipe/mypipe
Terminal oczekuje teraz na przesłanie danych do tego potoku
Teraz otwórz kolejne okno terminala.
A potem biegnij:
echo "hello world" > /path/to/pipe/mypipe
Sprawdź pierwszy terminal (ten z tail -f), powinien wyświetlać „hello world”
W kontenerze hosta, zamiast uruchamiania, tail -fktóre po prostu wyświetla wszystko, co jest wysyłane jako dane wejściowe, uruchom to polecenie, które wykona je jako polecenia:
eval "$(cat /path/to/pipe/mypipe)"
Następnie z drugiego terminala spróbuj uruchomić:
echo "ls -l" > /path/to/pipe/mypipe
Wróć do pierwszego terminala i powinieneś zobaczyć wynik ls -lpolecenia.
Być może zauważyłeś to w poprzedniej części, zaraz po ls -l wyświetleniu wyjścia, przestaje nasłuchiwać poleceń.
Zamiast eval "$(cat /path/to/pipe/mypipe)"uruchamiać:
while true; do eval "$(cat /path/to/pipe/mypipe)"; done
(nie możesz tego zrobić)
Teraz możesz wysłać nieograniczoną liczbę poleceń jedno po drugim, wszystkie zostaną wykonane, a nie tylko pierwsze.
Jedynym zastrzeżeniem jest to, że jeśli host musi się zrestartować, pętla „while” przestanie działać.
Aby obsłużyć ponowne uruchomienie, oto co zrobiłem:
Umieść while true; do eval "$(cat /path/to/pipe/mypipe)"; doneplik w pliku o nazwie execpipe.shz #!/bin/bashnagłówkiem
Nie zapomnij o chmod +xtym
Dodaj go do crontab, uruchamiając
crontab -e
A potem dodawanie
@reboot /path/to/execpipe.sh
W tym momencie przetestuj to: zrestartuj serwer, a kiedy zostanie utworzony, powtórz echo niektórych poleceń w potoku i sprawdź, czy są wykonywane. Oczywiście nie możesz zobaczyć wyniku poleceń, więc ls -lnie pomoże, ale touch somefilepomoże.
Inną opcją jest zmodyfikowanie skryptu, aby umieścić dane wyjściowe w pliku, na przykład:
while true; do eval "$(cat /path/to/pipe/mypipe)" &> /somepath/output.txt; done
Teraz możesz uruchomić, ls -la dane wyjściowe (zarówno stdout, jak i stderr używane &>w bash) powinny znajdować się w output.txt.
Jeśli używasz zarówno docker compose, jak i dockerfile, tak jak ja, oto co zrobiłem:
Załóżmy, że chcesz zamontować folder nadrzędny mypipe /hostpipew swoim kontenerze
Dodaj:
VOLUME /hostpipe
w pliku dockerfile, aby utworzyć punkt montowania
Następnie dodaj to:
volumes:
- /path/to/pipe:/hostpipe
w swoim pliku Docker Compose, aby zamontować / ścieżkę / do / potok jako / hostpipe
Zrestartuj kontenery Dockera.
Wykonaj do swojego kontenera docker:
docker exec -it <container> bash
Przejdź do folderu montowania i sprawdź, czy widzisz rurę:
cd /hostpipe && ls -l
Teraz spróbuj uruchomić polecenie z kontenera:
echo "touch this_file_was_created_on_main_host_from_a_container.txt" > /hostpipe/mypipe
I powinno działać!
OSTRZEŻENIE: jeśli masz hosta OSX (Mac OS) i kontener Linuksa, to nie zadziała (wyjaśnienie tutaj https://stackoverflow.com/a/43474708/10018801 i wystaw tutaj https://github.com/docker / for-mac / Issues / 483 ), ponieważ implementacja potoku nie jest taka sama, więc to, co napiszesz do potoku z Linuksa, może być odczytane tylko przez Linuksa, a to, co napiszesz do potoku z Mac OS, może zostać odczytane tylko przez Mac OS (to zdanie może nie być zbyt dokładne, ale pamiętaj, że istnieje problem między platformami).
Na przykład, kiedy uruchamiam konfigurację dockera w DEV z mojego komputera z systemem Mac OS, nazwany potok, jak wyjaśniono powyżej, nie działa. Ale w przejściach i produkcji mam Linux hosta i kontenery Linuksa i działa idealnie.
Oto jak wysyłam polecenie z kontenera node js do głównego hosta i pobieram dane wyjściowe:
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);
Napisz prosty serwer Pythona, który nasłuchuje na porcie (powiedzmy 8080), połącz port -p 8080: 8080 z kontenerem, wyślij żądanie HTTP do localhost: 8080, aby zapytać serwer Pythona z popenem ze skryptami powłoki, uruchom curl pisanie kodu wysyłającego żądanie 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()
Jeśli nie martwisz się o bezpieczeństwo i po prostu chcesz uruchomić kontener docker na hoście z innego kontenera docker, takiego jak OP, możesz udostępnić serwer docker działający na hoście z kontenerem docker, udostępniając jego gniazdo nasłuchujące.
Zobacz https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface i sprawdź, czy Twoja osobista tolerancja ryzyka pozwala na to w tej konkretnej aplikacji.
Możesz to zrobić, dodając następujące argumenty głośności do polecenia startu
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
lub udostępniając /var/run/docker.sock w swoim pliku redagowania dockera w następujący sposób:
version: '3'
services:
ci:
command: ...
image: ...
volumes
- /var/run/docker.sock:/var/run/docker.sock
Po uruchomieniu polecenia docker start w kontenerze docker, serwer docker działający na twoim hoście zobaczy żądanie i aprowizuje kontener siostrzany.
kredyt: http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
/usr/bin/docker:/usr/bin/docker.).
Moje lenistwo doprowadziło mnie do znalezienia najłatwiejszego rozwiązania, które nie zostało tutaj opublikowane jako odpowiedź.
Opiera się ona na wielki artykuł przez luc juggery .
Wszystko, co musisz zrobić, aby uzyskać pełną powłokę swojego hosta linuxowego z poziomu kontenera docker, to:
docker run --privileged --pid=host -it alpine:3.8 \
nsenter -t 1 -m -u -n -i sh
Wyjaśnienie:
--privileged: przyznaje dodatkowe uprawnienia do kontenera, pozwala kontenerowi uzyskać dostęp do urządzeń hosta (/ dev)
--pid = host: pozwala kontenerom na korzystanie z drzewa procesów hosta Dockera (maszyny wirtualnej, na której działa demon Docker) Narzędzie nsenter: umożliwia uruchomienie procesu w istniejących przestrzeniach nazw (elementy konstrukcyjne zapewniające izolację kontenerów)
nsenter (-t 1 -m -u -n -i sh) pozwala na uruchomienie procesu sh w tym samym kontekście izolacji co proces z PID 1. Całe polecenie zapewni wtedy interaktywną powłokę sh na maszynie wirtualnej
Ta konfiguracja ma poważne konsekwencje dla bezpieczeństwa i powinna być używana z ostrzeżeniami (jeśli istnieją).
docker run --detach-keys="ctrl-p" -it -v /:/mnt/rootdir --name testing busybox
# chroot /mnt/rootdir
#
Mam proste podejście.
Krok 1: Zamontuj /var/run/docker.sock:/var/run/docker.sock (dzięki czemu będziesz mógł wykonywać polecenia dockera w swoim kontenerze)
Krok 2: Wykonaj to poniżej w swoim kontenerze. Kluczową częścią tutaj jest (- host sieciowy, ponieważ zostanie wykonany z kontekstu hosta)
docker run -i --rm --network host -v /opt/test.sh:/test.sh alpine: 3.7 sh /test.sh
test.sh powinien zawierać polecenia (ifconfig, netstat itd.), jakie potrzebujesz. Teraz będziesz mógł uzyskać dane wyjściowe kontekstu hosta.
Jak przypomina Marcus, docker to w zasadzie izolacja procesów. Począwszy od Dockera 1.8, możesz kopiować pliki w obie strony między hostem a kontenerem, zobacz dokumentacjędocker cp
https://docs.docker.com/reference/commandline/cp/
Po skopiowaniu pliku możesz uruchomić go lokalnie
myvalue=$(docker run -it ubuntu echo $PATH)i testować ją regularnie w powłoce skryptu (oczywiście użyjesz czegoś innego niż $ PATH, to tylko przykład), gdy jest jakąś określoną wartością, uruchamiasz skrypt
Możesz użyć koncepcji potoku, ale użyj pliku na hoście i fswatch, aby osiągnąć cel wykonania skryptu na maszynie hosta z kontenera dockera. Tak (używaj na własne ryzyko):
#! /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
W tym przykładzie wewnątrz kontenera wyślij polecenia do / dev / command_pipe, na przykład:
/home/test # echo 'docker network create test2.network.com' > /dev/command_pipe
Na hoście możesz sprawdzić, czy sieć została utworzona:
$ docker network ls | grep test2
8e029ec83afe test2.network.com bridge local
Aby rozwinąć na user2915097 męska odpowiedzi :
Ideą izolacji jest możliwość bardzo wyraźnego ograniczenia tego, co aplikacja / proces / kontener (bez względu na to, z jakiego punktu widzenia) może zrobić z systemem hosta. W związku z tym możliwość skopiowania i wykonania pliku naprawdę złamałby całą koncepcję.
Tak. Ale czasami jest to konieczne.
Nie. To nie przypadek lub Docker nie jest właściwym rozwiązaniem. Powinieneś zadeklarować przejrzysty interfejs dla tego, co chcesz zrobić (np. Aktualizację konfiguracji hosta) i napisać minimalny klient / serwer, który robi dokładnie to i nic więcej. Generalnie jednak nie wydaje się to być zbyt pożądane. W wielu przypadkach powinieneś po prostu przemyśleć swoje podejście i wyeliminować tę potrzebę. Docker powstał, gdy w zasadzie wszystko było usługą osiągalną za pomocą jakiegoś protokołu. Nie przychodzi mi do głowy żaden właściwy przypadek użycia kontenera Dockera, który uzyskałby prawa do wykonywania dowolnych rzeczy na hoście.
A(src na github). W Arepozytorium tworzę odpowiednie hooki, które po poleceniu 'git pull' tworzą nowy obraz dockera i uruchamiają go (i oczywiście usuwają stary kontener). Dalej: github ma web-hooki, które pozwalają na utworzenie żądania POST do dowolnego linku do punktu końcowego po wypchnięciu na master. Więc nie chciałbym stworzyć zdokeryzowanej usługi B, która będzie tym punktem końcowym i która będzie uruchamiała 'git pull' w repozytorium A na maszynie HOST (ważne: polecenie 'git pull' musi być wykonane w środowisku HOST - a nie w środowisku B, ponieważ B nie można uruchomić nowego kontenera A w B ...)