¿Cómo controlar el host desde el contenedor Docker?
Por ejemplo, ¿cómo ejecutar el script de bash copiado en el host?
¿Cómo controlar el host desde el contenedor Docker?
Por ejemplo, ¿cómo ejecutar el script de bash copiado en el host?
Respuestas:
¡Eso REALMENTE depende de lo que necesites que haga ese script bash!
Por ejemplo, si el script bash solo hace eco de alguna salida, puede hacer
docker run --rm -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Otra posibilidad es que desee que el script bash instale algún software, por ejemplo, el script para instalar docker-compose. podrías hacer algo como
docker run --rm -v /usr/bin:/usr/bin --privileged -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Pero en este punto, realmente está empezando a tener que saber íntimamente qué está haciendo el script para permitir los permisos específicos que necesita en su host desde el interior del contenedor.
docker run --rm -v $(pwd)/mybashscript.sh:/work/mybashscript.sh ubuntu /work/mybashscript.sh
/usr/binal contenedor. En ningún caso, el contenedor tiene acceso completo al sistema host. Tal vez me equivoque, pero parece una mala respuesta a una mala pregunta.
La solución que uso es conectarme al host SSHy ejecutar el comando de esta manera:
ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"
Como esta respuesta sigue recibiendo votos, me gustaría recordar (y lo recomiendo encarecidamente) que la cuenta que se está utilizando para invocar el script debe ser una cuenta sin ningún permiso, pero solo ejecutando ese script como sudo(eso puede ser hecho desde sudoersarchivo).
sshno se encuentra. ¿Tiene alguna otra sugerencia?
apt update && apt install openssh-client.
Usó una tubería con nombre. En el sistema operativo host, cree un script para ejecutar y leer comandos, y luego llame a eval en eso.
Haga que el contenedor de la ventana acoplable lea en esa tubería con nombre.
Para poder acceder a la tubería, debe montarla a través de un volumen.
Esto es similar al mecanismo SSH (o un método basado en socket similar), pero lo restringe adecuadamente al dispositivo host, que probablemente sea mejor. Además, no tiene que pasar información de autenticación.
Mi única advertencia es que tenga cuidado con la razón por la que está haciendo esto. Es algo que debe hacer si desea crear un método para autoactualizarse con la entrada del usuario o lo que sea, pero probablemente no desee llamar a un comando para obtener algunos datos de configuración, ya que la forma correcta sería pasar eso como argumentos. / volume en la ventana acoplable. También tenga cuidado con el hecho de que está evaluando, así que solo piense en el modelo de permisos.
Algunas de las otras respuestas, como ejecutar un script. Bajo un volumen, no funcionarán de forma genérica ya que no tendrán acceso a todos los recursos del sistema, pero podría ser más apropiado según su uso.
Si no le preocupa la seguridad y simplemente está buscando iniciar un contenedor de la ventana acoplable en el host desde dentro de otro contenedor de la ventana acoplable como el OP, puede compartir el servidor de la ventana acoplable que se ejecuta en el host con el contenedor de la ventana acoplable compartiendo su socket de escucha.
Consulte https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface y vea si su tolerancia al riesgo personal lo permite para esta aplicación en particular.
Puede hacer esto agregando los siguientes argumentos de volumen a su comando de inicio
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
o compartiendo /var/run/docker.sock dentro de su docker componga un archivo como este:
version: '3'
services:
ci:
command: ...
image: ...
volumes
- /var/run/docker.sock:/var/run/docker.sock
Cuando ejecuta el comando de inicio de docker dentro de su contenedor de docker, el servidor de docker que se ejecuta en su host verá la solicitud y aprovisionará el contenedor hermano.
crédito: http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
/usr/bin/docker:/usr/bin/docker. Ej .).
Esta respuesta es solo una versión más detallada de la solución de Bradford Medeiros , que para mí también resultó ser la mejor respuesta, por lo que el mérito es de él.
En su respuesta, explica QUÉ hacer ( tuberías con nombre ) pero no exactamente CÓMO hacerlo.
Tengo que admitir que no sabía qué se llaman tuberías en el momento en que leí su solución. Así que luché para implementarlo (aunque en realidad es realmente simple), pero tuve éxito, así que estoy feliz de ayudar explicando cómo lo hice. Entonces, el punto de mi respuesta es solo detallar los comandos que necesita ejecutar para que funcione, pero nuevamente, el crédito es para él.
En el host principal, elija la carpeta donde desea colocar su archivo de /path/to/pipe/tubería con nombre, por ejemplo mypipe, y un nombre de tubería, por ejemplo , y luego ejecute:
mkfifo /path/to/pipe/mypipe
Se crea la tubería. Tipo
ls -l /path/to/pipe/mypipe
Y compruebe que los derechos de acceso comienzan con "p", como
prw-r--r-- 1 root root 0 mypipe
Ahora ejecuta:
tail -f /path/to/pipe/mypipe
El terminal ahora está esperando que se envíen datos a esta tubería
Ahora abra otra ventana de terminal.
Y luego ejecuta:
echo "hello world" > /path/to/pipe/mypipe
Verifique la primera terminal (la que tiene tail -f), debería mostrar "hola mundo"
En el contenedor del host, en lugar de ejecutar, tail -fque solo genera lo que se envía como entrada, ejecute este comando que lo ejecutará como comandos:
eval "$(cat /path/to/pipe/mypipe)"
Luego, desde la otra terminal, intente ejecutar:
echo "ls -l" > /path/to/pipe/mypipe
Regrese a la primera terminal y debería ver el resultado del ls -lcomando.
Es posible que haya notado que en la parte anterior, justo después de ls -lque se muestra la salida, deja de escuchar los comandos.
En lugar de eval "$(cat /path/to/pipe/mypipe)", ejecuta:
while true; do eval "$(cat /path/to/pipe/mypipe)"; done
(no puedes hacer eso)
Ahora puede enviar un número ilimitado de comandos uno tras otro, todos se ejecutarán, no solo el primero.
La única advertencia es que si el host tiene que reiniciarse, el ciclo "while" dejará de funcionar.
Para manejar el reinicio, esto es lo que hice:
Ponlo while true; do eval "$(cat /path/to/pipe/mypipe)"; doneen un archivo llamado execpipe.shcon #!/bin/bashencabezado
No se olvide de chmod +xella
Agréguelo a crontab ejecutando
crontab -e
Y luego agregando
@reboot /path/to/execpipe.sh
En este punto, pruébelo: reinicie su servidor, y cuando esté respaldado, repita algunos comandos en la tubería y verifique si están ejecutados. Por supuesto, no puede ver el resultado de los comandos, por ls -llo que no ayudará, perotouch somefile ayudará.
Otra opción es modificar el script para colocar la salida en un archivo, como por ejemplo:
while true; do eval "$(cat /path/to/pipe/mypipe)" &> /somepath/output.txt; done
Ahora puede ejecutar ls -ly la salida (stdout y stderr usando&> en bash) debería estar en output.txt.
Si está utilizando docker compose y dockerfile como yo, esto es lo que hice:
Supongamos que desea montar la carpeta principal de mypipe como /hostpipeen su contenedor
Agrega esto:
VOLUME /hostpipe
en su dockerfile para crear un punto de montaje
Luego agrega esto:
volumes:
- /path/to/pipe:/hostpipe
en su docker, componga el archivo para montar / ruta / a / tubería como / hostpipe
Reinicie sus contenedores Docker.
Ejecutar en su contenedor Docker:
docker exec -it <container> bash
Vaya a la carpeta de montaje y compruebe que puede ver la tubería:
cd /hostpipe && ls -l
Ahora intente ejecutar un comando desde dentro del contenedor:
echo "touch this_file_was_created_on_main_host_from_a_container.txt" > /hostpipe/mypipe
¡Y debería funcionar!
ADVERTENCIA: Si tiene un host OSX (Mac OS) y un contenedor de Linux, no funcionará (explicación aquí https://stackoverflow.com/a/43474708/10018801 y problema aquí https://github.com/docker / for-mac / issues / 483 ) porque la implementación de la tubería no es la misma, por lo que lo que escribe en la tubería desde Linux solo puede ser leído por Linux y lo que escribe en la tubería desde Mac OS solo puede ser leído por un Mac OS (esta frase puede no ser muy precisa, pero tenga en cuenta que existe un problema multiplataforma).
Por ejemplo, cuando ejecuto la configuración de mi ventana acoplable en DEV desde mi computadora Mac OS, la tubería nombrada como se explicó anteriormente no funciona. Pero en la puesta en escena y la producción, tengo un host Linux y contenedores Linux, y funciona perfectamente.
Así es como envío un comando desde mi contenedor node js al host principal y recupero el resultado:
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);
Escriba un servidor python simple escuchando en un puerto (digamos 8080), vincule el puerto -p 8080: 8080 con el contenedor, haga una solicitud HTTP a localhost: 8080 para pedirle al servidor Python que ejecute scripts de shell con popen, ejecute un curl o escribiendo código para hacer una solicitud 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()
Mi pereza me llevó a encontrar la solución más fácil que no se publicó como respuesta aquí.
Está basado en el gran artículo de luc juggery .
Todo lo que necesita hacer para obtener un shell completo para su host Linux desde dentro de su contenedor Docker es:
docker run --privileged --pid=host -it alpine:3.8 \
nsenter -t 1 -m -u -n -i sh
Explicación:
--privileged: otorga permisos adicionales al contenedor, permite que el contenedor obtenga acceso a los dispositivos del host (/ dev)
--pid = host: permite que los contenedores usen el árbol de procesos del host de Docker (la máquina virtual en la que se ejecuta el demonio de Docker) Utilidad nsenter: permite ejecutar un proceso en espacios de nombres existentes (los bloques de construcción que proporcionan aislamiento a los contenedores)
nsenter (-t 1 -m -u -n -i sh) permite ejecutar el proceso sh en el mismo contexto de aislamiento que el proceso con PID 1. Todo el comando proporcionará un shell sh interactivo en la VM
Esta configuración tiene importantes implicaciones de seguridad y debe usarse con precauciones (si corresponde).
docker run --detach-keys="ctrl-p" -it -v /:/mnt/rootdir --name testing busybox
# chroot /mnt/rootdir
#
Tengo un enfoque simple.
Paso 1: Monte /var/run/docker.sock:/var/run/docker.sock (para que pueda ejecutar comandos de docker dentro de su contenedor)
Paso 2: Ejecute esto a continuación dentro de su contenedor. La parte clave aquí es ( - host de red, ya que se ejecutará desde el contexto del host)
docker ejecutar -i --rm --network host -v /opt/test.sh:/test.sh alpine: 3.7 sh /test.sh
test.sh debe contener algunos comandos (ifconfig, netstat, etc.) lo que necesite. Ahora podrá obtener la salida del contexto del host.
Como recuerda Marcus, la ventana acoplable es básicamente un proceso de aislamiento. A partir de la ventana acoplable 1.8, puede copiar archivos en ambos sentidos entre el host y el contenedor, consulte el documento dedocker cp
https://docs.docker.com/reference/commandline/cp/
Una vez que se copia un archivo, puede ejecutarlo localmente
myvalue=$(docker run -it ubuntu echo $PATH)y probarlo regularmente en un shell de script (por supuesto, usará algo más que $ PATH, solo es un ejemplo), cuando es un valor específico,
Puede usar el concepto de tubería, pero use un archivo en el host y fswatch para lograr el objetivo de ejecutar un script en la máquina host desde un contenedor docker. Así (use bajo su propio riesgo):
#! /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
En este ejemplo, dentro del contenedor, envíe comandos a / dev / command_pipe, así:
/home/test # echo 'docker network create test2.network.com' > /dev/command_pipe
En el host, puede verificar si se creó la red:
$ docker network ls | grep test2
8e029ec83afe test2.network.com bridge local
Para ampliar la respuesta del usuario 2915097 :
La idea del aislamiento es poder restringir lo que una aplicación / proceso / contenedor (cualquiera que sea su ángulo en esto) puede hacer al sistema host de manera muy clara. Por lo tanto, poder copiar y ejecutar un archivo realmente rompería todo el concepto.
Si. Pero a veces es necesario.
No. Ese no es el caso, o Docker no es lo correcto para usar. Lo que debe hacer es declarar una interfaz clara para lo que desea hacer (por ejemplo, actualizar una configuración de host) y escribir un cliente / servidor mínimo para hacer exactamente eso y nada más. Generalmente, sin embargo, esto no parece ser muy deseable. En muchos casos, simplemente debe repensar su enfoque y erradicar esa necesidad. Docker nació cuando básicamente todo era un servicio al que se podía acceder mediante algún protocolo. No puedo pensar en ningún caso de uso adecuado de un contenedor Docker que obtenga los derechos para ejecutar cosas arbitrarias en el host.
A(src en github). En el Arepositorio, creo los ganchos adecuados que, después del comando 'git pull', crean una nueva imagen de la ventana acoplable y los ejecutan (y eliminan el contenedor antiguo, por supuesto). Siguiente: github tiene web-hooks que permiten crear una solicitud POST a un enlace de punto final arbitrario después de presionar el maestro. Por lo tanto, no crearé el servicio B acoplado, que será ese punto final y que solo ejecutará 'git pull' en el repositorio A en la máquina HOST (importante: el comando 'git pull' debe ejecutarse en el entorno HOST, no en el entorno B porque B no se puede ejecutar un nuevo contenedor A dentro de B ...)