Como executar o script de shell no host do contêiner do docker?


99

Como controlar o host do contêiner docker?

Por exemplo, como executar um script copiado para hospedar bash?


8
isso não seria exatamente o oposto de isolar o host do docker?
Marcus Müller

36
Sim. Mas às vezes é necessário.
Alex Ushakov


Não tenho certeza sobre "host de controle", mas recentemente estive em uma palestra de cientistas de dados que estão usando o docker para executar scripts para processar cargas de trabalho enormes (usando GPUs montadas em AWS) e enviar o resultado para o host. Um caso de uso muito interessante. Essencialmente, scripts empacotados com um ambiente de execução confiável graças ao docker
KCD de

@KCD E por que eles preferem a contentorização de aplicativos via docker em vez de usar contêineres de nível de sistema (LXC)?
Alex Ushakov

Respostas:


30

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.


1
Tive a ideia de fazer um container que se conecta ao host e cria novos containers.
Alex Ushakov

1
Docker não parece gostar de sua montagem relativa. Isso deve funcionardocker run --rm -v $(pwd)/mybashscript.sh:/work/mybashscript.sh ubuntu /work/mybashscript.sh
KCD de

5
A primeira linha inicia um novo contêiner ubuntu e monta o script onde ele pode ser lido. Ele não permite que o contêiner acesse o sistema de arquivos do host, por exemplo. A segunda linha expõe o host /usr/binao 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.
Paul

3
Muito justo - a pergunta era muito vaga. A pergunta não pedia "acesso total ao sistema host". Conforme descrito, se o script bash se destina apenas a ecoar alguma saída, ele NÃO PRECISA de nenhum acesso ao sistema de arquivos host. Para meu segundo exemplo, que foi instalar docker-compose, a única permissão necessária é acessar o diretório bin onde o binário é armazenado. Como eu disse no início, para fazer isso, você teria que ter ideias muito específicas sobre o que o script está fazendo para permitir as permissões corretas.
Paul Becotte

1
Tentei fazer isso, o script é executado no contêiner, não no host
All2Pie

62

A solução que utilizo é conectar-se ao host SSHe executar o comando assim:

ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"

ATUALIZAR

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 sudoersarquivo).


Como outra solução alternativa, o contêiner pode gerar um conjunto de comandos e o host pode executá-los após a saída do contêiner: eval $ (docker run --rm -it container_name_to_output script)
parity3

Preciso executar uma linha de comando no Host de dentro de um contêiner do Docker, mas quando entro no contêiner, sshnão foi encontrado. Você tem alguma outra sugestão?
Ron Rosenfeld

@RonRosenfeld, qual imagem Docker você está usando? em caso de Debian / Ubuntu, execute o seguinte: apt update && apt install openssh-client.
Mohammed Noureldin

Seria o que quer que fosse instalado no meu Synology NAS. Como posso eu saber?
Ron Rosenfeld

@RonRosenfeld, desculpe, não entendi o que você quis dizer
Mohammed Noureldin

54

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.


16
ATENÇÃO: Esta é a resposta certa / melhor e precisa de mais elogios. Qualquer outra resposta consiste em perguntar "o que você está tentando fazer" e abrir exceções para as coisas. Eu tenho um caso de uso muito específico que exige que eu seja capaz de fazer isso, e esta é a única boa resposta imho. O SSH acima exigiria reduzir os padrões de segurança / firewall, e o material de execução do docker está totalmente errado. Obrigado por isso. Presumo que não receba tantos votos positivos porque não é uma resposta simples de copiar / colar, mas esta é a resposta. +100 pontos de mim se eu pudesse
Farley

4
Para aqueles que procuram mais informações, você pode usar o seguinte script em execução na máquina host: unix.stackexchange.com/a/369465 Claro, você terá que executá-lo com 'nohup' e criar algum tipo de wrapper de supervisor para mantê-lo vivo (talvez use um cron job: P)
sucotronic

Eu criei um diagrama para ilustrar um caso de uso: imgur.com/a/9Wkxqu9
sucotronic

8
Esta pode ser uma boa resposta. No entanto, seria muito melhor se você fornecer mais detalhes e mais explicações sobre a linha de comando. É possível elaborar?
Mohammed Noureldin

5
Votado, isso funciona! Faça um pipe nomeado usando 'mkfifo host_executor_queue' onde o volume está montado. Então, para adicionar um consumidor que executa comandos que são colocados na fila como o shell do host, use 'tail -f host_executor_queue | sh & '. O & no final faz com que seja executado em segundo plano. Finalmente, para enviar comandos para a fila, use 'echo touch foo> host_executor_queue' - este teste cria um arquivo temporário foo no diretório inicial. Se você deseja que o consumidor inicie na inicialização do sistema, coloque '@reboot tail -f host_executor_queue | sh & 'no crontab. Basta adicionar o caminho relativo ao host_executor_queue.
Skybunk 01 de

11

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.

PARTE 1 - Testando o conceito de tubo nomeado sem janela de encaixe

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"

PARTE 2 - Executar comandos através do tubo

No contêiner do host, em vez de executar tail -fapenas 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 -lcomando.

PARTE 3 - Faça ouvir para sempre

Você deve ter notado que na parte anterior, logo após a ls -lsaí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.

PARTE 4 - Faça funcionar mesmo quando a reinicialização acontecer

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)"; doneem um arquivo chamado execpipe.shcom #!/bin/bashcabeçalho

Não se esqueça chmod +xdisso

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 -lnão ajudará, mas touch somefileajudará.

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 -le a saída (stdout e stderr usando &>em bash) deve estar em output.txt.

PARTE 5 - Faça funcionar com o docker

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 /hostpipeem 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.

PARTE 6 - Teste

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.

PARTE 7 - Exemplo de contêiner Node.JS

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);

Isso funciona muito bem. E quanto à segurança? Quero usar isso para iniciar / parar contêineres do docker de dentro de um contêiner em execução? Acabo de criar um dockeruser sem quaisquer privilégios, exceto para executar comandos docker?
Kristof van Woensel

6

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()

IMO esta é a melhor resposta. A execução de comandos arbitrários na máquina host DEVE ser feita por meio de algum tipo de API (por exemplo, REST). Esta é a única maneira pela qual a segurança pode ser aplicada e os processos em execução podem ser controlados adequadamente (por exemplo, matar, lidar com stdin, stdout, código de saída e assim por diante). Claro, seria ótimo se essa API pudesse ser executada dentro do Docker, mas pessoalmente não me importo de executá-la diretamente no host.
barney765

6

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/


1
Considere que o docker deve ser instalado no contêiner, caso contrário, você também precisará montar um volume para o binário do docker (por exemplo /usr/bin/docker:/usr/bin/docker).
Gerry

1
Tenha cuidado ao montar o soquete docker em seu contêiner, isso pode ser um sério problema de segurança: docs.docker.com/engine/security/security/…
DatGuyKaj

@DatGuyKaj obrigado, editei minha resposta para refletir os problemas descritos por seu recurso.
Matt Bucci

Isso não responde à pergunta, que é sobre como executar um script no host, não em um contêiner
Brandon,

2

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).


1
docker run --detach-keys="ctrl-p" -it -v /:/mnt/rootdir --name testing busybox
# chroot /mnt/rootdir
# 

3
Embora essa resposta possa resolver a questão do OP, sugere-se que você explique como funciona e por que resolve o problema. Isso ajuda os novos desenvolvedores a entender o que está acontecendo e como corrigir isso e problemas semelhantes por conta própria. Obrigado por contribuir!
Caleb Kleveter

1

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.


2
De acordo com a documentação oficial do docker sobre rede usando rede de host, "No entanto, em todas as outras formas, como armazenamento, namespace de processo e namespace de usuário, o processo é isolado do host. Confira - docs.docker.com/network/network-tutorial-host
Peter Mutisya

0

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


1
Eu sei isso. Em outras palavras, como executar esse script de dentro do contêiner do docker?
Alex Ushakov


2
@AlexUshakov: de jeito nenhum. Fazer isso quebraria muitas das vantagens do docker. Não faça isso. Não tente. Reconsidere o que você precisa fazer.
Marcus Müller

Veja também Forums de
user2915097

1
você sempre pode, no host, obter o valor de alguma variável em seu contêiner, algo como 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
user2915097

0

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

-7

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.


Eu tenho um caso de uso: tenho o serviço dockerized A(src no github). No Arepo, 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 ...)
Kamil Kiełczewski

1
O problema: não quero ter nada no HOST, exceto linux, git e docker. E eu quero ter o serviço dockerizet A e o serviço B (que na verdade é um manipulador git-push que executa git pull no repo A depois que alguém faz git push no master). Portanto, git auto-deploy é um caso de uso problemático
Kamil Kiełczewski

@ KamilKiełczewski Estou tentando fazer exatamente o mesmo, você encontrou uma solução?
user871784

1
Dizer: "Não, não é esse o caso" é tacanho e pressupõe que você conheça todos os casos de uso do mundo. Nosso caso de uso é a execução de testes. Eles precisam ser executados em contêineres para testar corretamente o ambiente, mas devido à natureza dos testes, eles também precisam executar scripts no host.
Senica Gonzalez

1
Apenas para aqueles que estão se perguntando por que deixo uma resposta -7: a) tudo bem ser falível. Eu estava errado. Tudo bem que isso esteja documentado aqui. b) Os comentários realmente contribuem com valor; excluir a resposta também os excluiria. c) Ainda contribui com um ponto de vista que pode ser sensato considerar (não rompa seu isolamento se não for necessário. Às vezes, no entanto).
Marcus Müller
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.