Como controlar o host do contêiner docker?
Por exemplo, como executar um script copiado para hospedar bash?
Como controlar o host do contêiner docker?
Por exemplo, como executar um script copiado para hospedar bash?
Respostas:
Isso realmente depende do que você precisa que o script bash faça!
Por exemplo, se o script bash apenas exibe alguma saída, você pode apenas fazer
docker run --rm -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Outra possibilidade é que você queira que o script bash instale algum software - digamos, o script para instalar o docker-compose. você poderia fazer algo como
docker run --rm -v /usr/bin:/usr/bin --privileged -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Mas, neste ponto, você realmente precisa saber intimamente o que o script está fazendo para permitir as permissões específicas de que ele precisa em seu host de dentro do contêiner.
docker run --rm -v $(pwd)/mybashscript.sh:/work/mybashscript.sh ubuntu /work/mybashscript.sh
/usr/bin
ao contêiner. Em nenhum dos casos, o contêiner tem acesso total ao sistema host. Talvez eu esteja errado, mas parece uma resposta ruim para uma pergunta ruim.
A solução que utilizo é conectar-se ao host SSH
e executar o comando assim:
ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"
Como esta resposta continua recebendo votos, gostaria de lembrar (e altamente recomendável) que a conta que está sendo usada para invocar o script deve ser uma conta sem permissão alguma, mas apenas executando esse script como sudo
(pode ser feito do sudoers
arquivo).
ssh
não foi encontrado. Você tem alguma outra sugestão?
apt update && apt install openssh-client
.
Usou um pipe nomeado. No sistema operacional host, crie um script para fazer um loop e ler comandos e, em seguida, chamar eval para isso.
Faça com que o contêiner do docker leia para esse canal nomeado.
Para poder acessar o tubo, você precisa montá-lo por meio de um volume.
Isso é semelhante ao mecanismo SSH (ou um método baseado em soquete semelhante), mas restringe você corretamente ao dispositivo host, o que provavelmente é melhor. Além disso, você não precisa passar informações de autenticação.
Meu único aviso é ser cauteloso sobre o motivo de estar fazendo isso. É totalmente algo a fazer se você deseja criar um método de autoatualização com a entrada do usuário ou qualquer outra coisa, mas você provavelmente não deseja chamar um comando para obter alguns dados de configuração, já que a maneira correta seria passar isso como args / volume no docker. Também tenha cuidado com o fato de que você está avaliando, então apenas dê uma olhada no modelo de permissão.
Algumas das.as outras respostas, como executar um script. Sob um volume não funcionarão genericamente, uma vez que não terão acesso a todos os recursos do sistema, mas pode ser mais apropriado dependendo de seu uso.
Essa resposta é apenas uma versão mais detalhada da solução do Bradford Medeiros , que também para mim acabou sendo a melhor resposta, então o crédito vai para ele.
Em sua resposta, ele explica O QUE fazer ( tubos nomeados ), mas não exatamente COMO fazer.
Tenho que admitir que não sabia o que são chamados de pipes no momento em que li sua solução. Então, eu me esforcei para implementá-lo (embora seja realmente muito simples), mas tive sucesso, então estou feliz em ajudar explicando como fiz isso. Portanto, o objetivo da minha resposta é apenas detalhar os comandos que você precisa executar para fazê-lo funcionar, mas, novamente, o crédito vai para ele.
No host principal, escolha a pasta onde deseja colocar o arquivo de pipe nomeado, por exemplo, /path/to/pipe/
e um nome de pipe, por exemplo mypipe
, e execute:
mkfifo /path/to/pipe/mypipe
O tubo é criado. Tipo
ls -l /path/to/pipe/mypipe
E verifique se os direitos de acesso começam com "p", como
prw-r--r-- 1 root root 0 mypipe
Agora execute:
tail -f /path/to/pipe/mypipe
O terminal está agora esperando que os dados sejam enviados para este tubo
Agora abra outra janela de terminal.
E então execute:
echo "hello world" > /path/to/pipe/mypipe
Verifique o primeiro terminal (aquele com tail -f
), ele deve exibir "hello world"
No contêiner do host, em vez de executar tail -f
apenas o que é enviado como entrada, execute este comando que o executará como comandos:
eval "$(cat /path/to/pipe/mypipe)"
Em seguida, no outro terminal, tente executar:
echo "ls -l" > /path/to/pipe/mypipe
Volte para o primeiro terminal e você verá o resultado do ls -l
comando.
Você deve ter notado que na parte anterior, logo após a ls -l
saída ser exibida, ele para de ouvir os comandos.
Em vez de eval "$(cat /path/to/pipe/mypipe)"
, execute:
while true; do eval "$(cat /path/to/pipe/mypipe)"; done
(você não pode saber disso)
Agora você pode enviar um número ilimitado de comandos um após o outro, todos eles serão executados, não apenas o primeiro.
A única ressalva é que se o host tiver que reiniciar, o loop "while" parará de funcionar.
Para lidar com a reinicialização, aqui está o que eu fiz:
Coloque o while true; do eval "$(cat /path/to/pipe/mypipe)"; done
em um arquivo chamado execpipe.sh
com #!/bin/bash
cabeçalho
Não se esqueça chmod +x
disso
Adicione-o ao crontab executando
crontab -e
E então adicionando
@reboot /path/to/execpipe.sh
Neste ponto, teste: reinicie o servidor e, quando ele estiver de volta, ecoe alguns comandos no pipe e verifique se eles são executados. Claro, você não é capaz de ver a saída dos comandos, então ls -l
não ajudará, mas touch somefile
ajudará.
Outra opção é modificar o script para colocar a saída em um arquivo, como:
while true; do eval "$(cat /path/to/pipe/mypipe)" &> /somepath/output.txt; done
Agora você pode executar ls -l
e a saída (stdout e stderr usando &>
em bash) deve estar em output.txt.
Se você estiver usando docker compose e dockerfile como eu, aqui está o que eu fiz:
Vamos supor que você deseja montar a pasta pai do mypipe como /hostpipe
em seu contêiner
Adicione isso:
VOLUME /hostpipe
em seu dockerfile para criar um ponto de montagem
Em seguida, adicione este:
volumes:
- /path/to/pipe:/hostpipe
no arquivo de composição do docker para montar / path / to / pipe como / hostpipe
Reinicie seus contêineres do docker.
Exec em seu contêiner do docker:
docker exec -it <container> bash
Vá para a pasta de montagem e verifique se você pode ver o tubo:
cd /hostpipe && ls -l
Agora tente executar um comando de dentro do contêiner:
echo "touch this_file_was_created_on_main_host_from_a_container.txt" > /hostpipe/mypipe
E deve funcionar!
AVISO: Se você tiver um host OSX (Mac OS) e um contêiner Linux, não funcionará (explicação aqui https://stackoverflow.com/a/43474708/10018801 e problema aqui https://github.com/docker / for-mac / issues / 483 ) porque a implementação do pipe não é a mesma, então o que você escreve no pipe do Linux pode ser lido apenas por um Linux e o que você escreve no pipe do Mac OS pode ser lido apenas por um Mac OS (esta frase pode não ser muito precisa, mas esteja ciente de que existe um problema de plataforma cruzada).
Por exemplo, quando executo a configuração da minha docker no DEV do meu computador Mac OS, o pipe nomeado conforme explicado acima não funciona. Mas, no teste e na produção, tenho host e contêineres Linux, e isso funciona perfeitamente.
Aqui está como eu envio um comando do meu contêiner node js para o host principal e recupero a saída:
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);
Escreva um servidor python de servidor simples ouvindo em uma porta (digamos 8080), vincule a porta -p 8080: 8080 com o contêiner, faça uma solicitação HTTP para localhost: 8080 para pedir ao servidor python que executa scripts de shell com popen, execute um curl ou escrevendo código para fazer uma solicitação 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()
Se você não está preocupado com a segurança e está simplesmente procurando iniciar um contêiner do docker no host de dentro de outro contêiner do docker como o OP, você pode compartilhar o servidor docker em execução no host com o contêiner do docker, compartilhando seu soquete de escuta.
Consulte https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface e veja se sua tolerância a risco pessoal permite isso para este aplicativo específico.
Você pode fazer isso adicionando os seguintes argumentos de volume ao seu comando inicial
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
ou compartilhando /var/run/docker.sock dentro de seu arquivo docker compose assim:
version: '3'
services:
ci:
command: ...
image: ...
volumes
- /var/run/docker.sock:/var/run/docker.sock
Quando você executa o comando docker start em seu contêiner do docker, o servidor docker em execução no seu host verá a solicitação e fornecerá o contêiner irmão.
crédito: http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
/usr/bin/docker:/usr/bin/docker
).
Minha preguiça me levou a encontrar a solução mais fácil que não foi publicada como resposta aqui.
É baseado no excelente artigo de luc juggery .
Tudo o que você precisa fazer para obter um shell completo para seu host Linux de dentro do contêiner do docker é:
docker run --privileged --pid=host -it alpine:3.8 \
nsenter -t 1 -m -u -n -i sh
Explicação:
--privileged: concede permissões adicionais ao contêiner, permite que o contêiner obtenha acesso aos dispositivos do host (/ dev)
--pid = host: permite que os contêineres usem a árvore de processos do host Docker (a VM na qual o Docker daemon está sendo executado) utilitário nsenter: permite executar um processo em namespaces existentes (os blocos de construção que fornecem isolamento aos contêineres)
nsenter (-t 1 -m -u -n -i sh) permite executar o processo sh no mesmo contexto de isolamento que o processo com PID 1. O comando inteiro irá então fornecer um sh shell interativo na VM
Essa configuração tem importantes implicações de segurança e deve ser usada com cautela (se houver).
docker run --detach-keys="ctrl-p" -it -v /:/mnt/rootdir --name testing busybox
# chroot /mnt/rootdir
#
Eu tenho uma abordagem simples.
Etapa 1: Monte /var/run/docker.sock:/var/run/docker.sock (para que você possa executar comandos do docker dentro de seu contêiner)
Etapa 2: execute abaixo dentro do seu contêiner. A parte principal aqui é ( - host de rede, pois será executado no contexto do host)
docker run -i --rm --network host -v /opt/test.sh:/test.sh alpine: 3.7 sh /test.sh
test.sh deve conter alguns comandos (ifconfig, netstat etc ...) tudo o que você precisa. Agora você poderá obter a saída do contexto do host.
Como Marcus lembra, docker é basicamente isolamento de processo. A partir do docker 1.8, você pode copiar arquivos de ambas as maneiras entre o host e o contêiner, consulte o documento dedocker cp
https://docs.docker.com/reference/commandline/cp/
Depois que um arquivo é copiado, você pode executá-lo localmente
myvalue=$(docker run -it ubuntu echo $PATH)
e testá-lo regularmente em um shell de script (é claro, você usará algo diferente de $ PATH, é apenas um exemplo), quando é algum valor específico, você inicia seu script
Você pode usar o conceito de pipe, mas use um arquivo no host e fswatch para cumprir o objetivo de executar um script na máquina host a partir de um contêiner docker. Assim (use por sua própria conta e risco):
#! /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
Neste exemplo, dentro do contêiner, envie comandos para / dev / command_pipe, assim:
/home/test # echo 'docker network create test2.network.com' > /dev/command_pipe
No host, você pode verificar se a rede foi criada:
$ docker network ls | grep test2
8e029ec83afe test2.network.com bridge local
Para expandir a resposta do usuário 2915097 :
A ideia de isolamento é ser capaz de restringir o que um aplicativo / processo / contêiner (seja qual for o seu ângulo) pode fazer com o sistema host de forma muito clara. Portanto, poder copiar e executar um arquivo realmente quebraria todo o conceito.
Sim. Mas às vezes é necessário.
Não. Não é esse o caso, ou o Docker não é a coisa certa a se usar. O que você deve fazer é declarar uma interface clara para o que você deseja fazer (por exemplo, atualizar uma configuração de host) e escrever um cliente / servidor mínimo para fazer exatamente isso e nada mais. Geralmente, porém, isso não parece ser muito desejável. Em muitos casos, você deve simplesmente repensar sua abordagem e erradicar essa necessidade. O Docker surgiu quando basicamente tudo era um serviço acessível por meio de algum protocolo. Não consigo pensar em nenhum caso de uso adequado de um contêiner do Docker obtendo os direitos para executar coisas arbitrárias no host.
A
(src no github). No A
repo, eu crio ganchos apropriados que, após o comando 'git pull', criam uma nova imagem do docker e as executo (e removem o contêiner antigo, é claro). A seguir: o github tem web-hooks que permitem criar uma solicitação POST para um link de endpoint arbitrário após o envio no master. Portanto, não quero criar o serviço B dockerizado que será esse endpoint e que só executará 'git pull' no repo A na máquina HOST (importante: o comando 'git pull' deve ser executado no ambiente HOST - não no ambiente B porque B não é possível executar o novo contêiner A dentro de B ...)