Expondo uma porta em um contêiner do Docker ativo

408

Estou tentando criar um contêiner Docker que atue como uma máquina virtual completa. Eu sei que posso usar a instrução EXPOSE dentro de um Dockerfile para expor uma porta e posso usar o -psinalizador with docker runpara atribuir portas, mas quando um contêiner está realmente em execução, existe um comando para abrir / mapear portas adicionais ativas?

Por exemplo, digamos que eu tenha um contêiner do Docker executando o sshd. Outra pessoa usando o ssh do contêiner e instala o httpd. Existe uma maneira de expor a porta 80 no contêiner e mapeá-la para a porta 8080 no host, para que as pessoas possam visitar o servidor Web em execução no contêiner, sem reiniciá-lo?

reberhardt
fonte
1
Você pode fornecer ao contêiner um IP roteável , para que nenhum mapeamento de porta seja necessário.
Matt

Respostas:

331

Você não pode fazer isso via Docker, mas pode acessar a porta não exposta do contêiner na máquina host.

se você tiver um contêiner que, com algo em execução na porta 8000, poderá executar

wget http://container_ip:8000

Para obter o endereço IP do contêiner, execute os 2 comandos:

docker ps

docker inspect container_name | grep IPAddress

Internamente, o Docker faz chamadas para iptables quando você executa uma imagem, portanto, talvez alguma variação disso funcione.

para expor a porta 8000 do contêiner na porta 8001 de localhosts:

 iptables -t nat -A  DOCKER -p tcp --dport 8001 -j DNAT --to-destination 172.17.0.19:8000

Uma maneira de resolver isso é configurar outro contêiner com o mapeamento de porta desejado e comparar a saída do comando iptables-save (porém, tive que remover algumas das outras opções que forçam o tráfego a passar pelo docker proxy).

NOTA: isso está subvertendo a janela de encaixe, portanto, isso deve ser feito com a consciência de que pode criar fumaça azul

OU

Outra alternativa é procurar a opção (new? Post 0.6.6?) -P - que usará portas de host aleatórias e as conectará.

OU

com 0.6.5, você poderia usar o recurso LINKs para abrir um novo contêiner que se comunica com o existente, com algumas retransmissões adicionais aos sinalizadores -p desse contêiner? (Ainda não usei LINKs)

OU

com docker 0,11? você pode usar docker run --net host ..para conectar seu contêiner diretamente às interfaces de rede do host (ou seja, a rede não é espaçada pelo nome) e, portanto, todas as portas que você abre no contêiner são expostas.

SvenDowideit
fonte
6
Isso não parece funcionar com a janela de encaixe 1.3.0, pelo menos. A regra DOCKER DNAT é criada ao executar a janela de encaixe com -p, mas a adição manual não parece permitir conexões. Estranhamente apagar a regra, enquanto um recipiente está sendo executado não parecem parar de funcionar ou ...
silasdavis
4
Obrigado. Fiquei com uma sensação de segurança de que as portas não expostas eram seguras.
seanmcl
Automatizando as coisas e usando jq + sed, pode ser útil: CONTAINER_IP=$(docker inspect container_name | jq .[0].NetworkSettings.IPAddress | sed -r 's/\"([^\"]+)\"/\1/''])'); iptables -t nat -A DOCKER -p tcp --dport 8001 -j DNAT --to-destination ${CONTAINER_IP}:8000
ericson.cepeda
5
@ ericson.cepeda Em vez de invoing jqe sedvocê pode usar -fa opção de docker inspect:CONTAINER_IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' container_name)
pwes
para aqueles que preferem kmkeen.com/jshon jshon -e 0 -e NetworkSettings -e Networks -e bridge -e IPAddress -u
slf
138

Aqui está o que eu faria:

  • Confirme o contêiner ativo.
  • Execute o contêiner novamente com a nova imagem, com portas abertas (eu recomendaria montar um volume compartilhado e abrir a porta ssh também)
sudo docker ps 
sudo docker commit <containerid> <foo/live>
sudo docker run -i -p 22 -p 8000:80 -m /data:/data -t <foo/live> /bin/bash
bosky101
fonte
31
A parte principal da minha pergunta é que isso precisa acontecer sem a reinicialização do contêiner ... A mudança para o novo contêiner pode manter arquivos, mas matará efetivamente qualquer processo em execução e será semelhante a uma reinicialização em uma máquina física. Eu preciso fazer isso sem que isso aconteça. Obrigado mesmo assim!
reberhardt
bem. eu compararia mais ainda ao iniciar uma instância paralela. Como agora os dois estão em execução (antigos e novos), pode ser mais fácil fazer proxy do novo contêiner, depois que as migrações necessárias forem feitas.
bosky101
Sim, instâncias paralelas e proxy reverso são alguns dos principais motivos pelos quais eu amo o Docker. No entanto, nesse cenário, preciso preservar todos os processos em execução no contêiner que podem ter sido iniciados via SSH. Enquanto os executáveis ​​serão preservados ao confirmar a imagem e iniciar uma instância paralela, os executáveis ​​em si não serão iniciados e qualquer coisa na RAM será perdida.
23414 reberhardt
6
Por que você corre sudo dockere não apenas docker?
Thiago Figueiro
Essa dica me ajudou muito porque instalei toneladas de pacotes em uma rede lenta. Ao executar, docker commiteu estava pronto para testar o aplicativo novamente, em vez de gastar horas para reinstalar tudo.
Gustavohenke
49

Embora não seja possível expor uma nova porta de um contêiner existente , você pode iniciar um novo contêiner na mesma rede do Docker e fazer com que ele encaminhe o tráfego para o contêiner original.

# docker run \
  --rm \
  -p $PORT:1234 \
  verb/socat \
    TCP-LISTEN:1234,fork \
    TCP-CONNECT:$TARGET_CONTAINER_IP:$TARGET_CONTAINER_PORT

Exemplo Trabalhado

Inicie um serviço da Web que escute na porta 80, mas não exponha sua porta 80 interna (oops!):

# docker run -ti mkodockx/docker-pastebin   # Forgot to expose PORT 80!

Encontre seu IP de rede do Docker:

# docker inspect 63256f72142a | grep IPAddress
                    "IPAddress": "172.17.0.2",

Inicie verb/socatcom a porta 8080 exposta e faça com que ele encaminhe o tráfego TCP para a porta 80 desse IP:

# docker run --rm -p 8080:1234 verb/socat TCP-LISTEN:1234,fork TCP-CONNECT:172.17.0.2:80

Agora você pode acessar o pastebin em http: // localhost: 8080 / , e seus pedidos vão para o socat:1234qual o encaminha pastebin:80, e a resposta segue o mesmo caminho ao contrário.

RobM
fonte
7
Muito esperto! No entanto, provavelmente é melhor usar verb/socat:alpine, já que sua imagem possui 5% da área ocupada (a menos que você encontre incompatibilidades de libc ou DNS ).
jpaugh
3
Há tambémalpine/socat
Jamby 28/10
3
Excelente resposta. Simples, um forro que o fará sem hacks sujos.
Bruno Brant
2
Isso funcionou perfeitamente para mim, obrigado! Eu tive que adicionar --net myfoldername_defaultao meu verb/socatcomando de inicialização desde que iniciei o contêiner não exposto em uma composição do docker que cria uma rede.
emazzotta
35

Os hacks de tabelas de IP não funcionam, pelo menos no Docker 1.4.1.

A melhor maneira seria executar outro contêiner com a porta exposta e retransmitir com o socat. Isto é o que eu fiz para (temporariamente) conectar ao banco de dados com o SQLPlus:

docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus

Dockerfile:

FROM debian:7

RUN apt-get update && \
    apt-get -y install socat && \
    apt-get clean

USER nobody

CMD socat -dddd TCP-LISTEN:1521,reuseaddr,fork TCP:db:1521
Ricardo Branco
fonte
2
Os hacks do iptables devem ser executados na máquina host, não no contêiner do docker. É essencialmente encaminhar solicitações para determinadas portas no host para as portas de contêiner do docker apropriadas. Não é nada específico do docker, e você pode fazer isso em hosts completamente diferentes. Você pode adicionar formatação de código ao seu arquivo Docker? Parece bastante útil.
AusIV
1
Fevereiro de 2016: executando o Docker 1.9.1, esta foi a única solução que funcionou com êxito para mim. Nenhuma das soluções do IPTables funcionou. Vale a pena aconselhar o uso da mesma FROMimagem base do seu contêiner de banco de dados, para uso eficiente dos recursos.
Excalibur
4
Você pode tentar dar um apline / socat . Ele vem com o socat pré-instalado e aceita as opções do socat como comando, para que você não precise escrever um arquivo Dockerfile.
Trkoch 30/05
35

Aqui está outra ideia. Use SSH para fazer o encaminhamento de porta; isso tem o benefício de também trabalhar no OS X (e provavelmente no Windows) quando o host do Docker for uma VM.

docker exec -it <containterid> ssh -R5432:localhost:5432 <user>@<hostip>
Scott Carlson
fonte
4
no meu caso a imagem janela de encaixe que estava sendo executado não tem ssh binário
Radu Toader
Preciso criar uma chave SSH na minha máquina e colocá-la no contêiner do docker para isso?
Octavian
@octavian ssh-keygen -t rsa dentro do contêiner. adicione a chave de pub às allowed_keys no host
Luke W
8

Eu tive que lidar com esse mesmo problema e foi capaz de resolvê-lo sem interromper nenhum dos meus contêineres em execução. Esta é uma solução atualizada em fevereiro de 2016, usando o Docker 1.9.1. De qualquer forma, esta resposta é uma versão detalhada da resposta de @ ricardo-branco, mas com mais profundidade para novos usuários.

No meu cenário, eu queria conectar-me temporariamente ao MySQL em execução em um contêiner, e como outros contêineres de aplicativos estão vinculados a ele, parar, reconfigurar e executar novamente o contêiner de banco de dados não era um iniciador.

Como eu gostaria de acessar o banco de dados MySQL externamente (do Sequel Pro via tunelamento SSH), vou usar a porta 33306na máquina host. (Não 3306, apenas no caso de haver uma instância externa do MySQL em execução.)

Cerca de uma hora de ajustar iptables se mostrou infrutífera, embora:

Passo a passo, aqui está o que eu fiz:

mkdir db-expose-33306
cd db-expose-33306
vim Dockerfile

Edite dockerfile, colocando isso dentro:

# Exposes port 3306 on linked "db" container, to be accessible at host:33306
FROM ubuntu:latest # (Recommended to use the same base as the DB container)

RUN apt-get update && \
    apt-get -y install socat && \
    apt-get clean

USER nobody
EXPOSE 33306

CMD socat -dddd TCP-LISTEN:33306,reuseaddr,fork TCP:db:3306

Então construa a imagem:

docker build -t your-namespace/db-expose-33306 .

Em seguida, execute-o, vinculando ao seu contêiner em execução. (Use em -dvez de -rmmantê-lo em segundo plano até explicitamente parar e remover. Só quero que ele seja executado temporariamente neste caso.)

docker run -it --rm --name=db-33306 --link the_live_db_container:db -p 33306:33306  your-namespace/db-expose-33306
Excalibur
fonte
Você pode usar o verbo / socat ou a imagem alpina / socat diretamente. Veja o verbo / socat
pjotr_dolphin
7

Para adicionar à solução de resposta aceita iptables , tive que executar mais dois comandos no host para abri-lo para o mundo externo.

HOST> iptables -t nat -A DOCKER -p tcp --dport 443 -j DNAT --to-destination 172.17.0.2:443
HOST> iptables -t nat -A POSTROUTING -j MASQUERADE -p tcp --source 172.17.0.2 --destination 172.17.0.2 --dport https
HOST> iptables -A DOCKER -j ACCEPT -p tcp --destination 172.17.0.2 --dport https

Nota: eu estava abrindo a porta https (443), o IP interno do meu docker era 172.17.0.2

Nota 2: Essas regras são temporárias e duram apenas até que o contêiner seja reiniciado

dstj
fonte
Isso funcionou para mim ... no entanto, houve alguns Cavat's mais tarde. Conforme a nota 2, isso será interrompido se os contêineres forem reiniciados; pode estar em um IP diferente. Mas o mais importante é que o docker não tem idéia sobre essas entradas do iptable, portanto, não as removerá quando você reiniciar completamente o serviço e solicitar que o docker o faça corretamente. O resultado foram várias entradas iptable que eram exatamente as mesmas, o que causou uma falha com pouco ou nenhum erro ou indicação da causa. Uma vez que regras extras foram decididas, o problema desapareceu. Em outras palavras, examine suas tabelas IP MUITO cuidadosamente, após qualquer alteração.
anthony
5

Você pode usar o SSH para criar um túnel e expor seu contêiner em seu host.

Você pode fazer isso de ambas as formas, de contêiner para host e de host para contêiner. Mas você precisa de uma ferramenta SSH como o OpenSSH em ambos (cliente em um e servidor em outro).

Por exemplo, no contêiner, você pode fazer

$ yum install -y openssh openssh-server.x86_64
service sshd restart
Stopping sshd:                                             [FAILED]
Generating SSH2 RSA host key:                              [  OK  ]
Generating SSH1 RSA host key:                              [  OK  ]
Generating SSH2 DSA host key:                              [  OK  ]
Starting sshd:                                             [  OK  ]
$ passwd # You need to set a root password..

Você pode encontrar o endereço IP do contêiner nesta linha (no contêiner):

$ ifconfig eth0 | grep "inet addr" | sed 's/^[^:]*:\([^ ]*\).*/\1/g'
172.17.0.2

Em seguida, no host, você pode apenas fazer:

sudo ssh -NfL 80:0.0.0.0:80 [email protected]
tonelada
fonte
Isso funcionou para mim, com uma pequena correção ... este comando funcionou: sudo ssh -NfL 25252: 172.17.0.50: 22 $ USUÁRIO @ localhost
Alexander Guo
3

Caso nenhuma resposta esteja funcionando para alguém - verifique se o contêiner de destino já está em execução na rede docker:

CONTAINER=my-target-container
docker inspect $CONTAINER | grep NetworkMode
        "NetworkMode": "my-network-name",

Salve-o para mais tarde na variável $NET_NAME:

NET_NAME=$(docker inspect --format '{{.HostConfig.NetworkMode}}' $CONTAINER)

Se sim, você deve executar o contêiner de proxy na mesma rede.

Em seguida, procure o alias do contêiner:

docker inspect $CONTAINER | grep -A2 Aliases
                "Aliases": [
                    "my-alias",
                    "23ea4ea42e34a"

Salve-o para mais tarde na variável $ALIAS:

ALIAS=$(docker inspect --format '{{index .NetworkSettings.Networks "'$NET_NAME'" "Aliases" 0}}' $CONTAINER)

Agora execute socatem um contêiner na rede $NET_NAMEpara fazer a ponte para a $ALIASporta exposta (mas não publicada) do contêiner ed:

docker run \
    --detach --name my-new-proxy \
    --net $NET_NAME \
    --publish 8080:1234 \
    alpine/socat TCP-LISTEN:1234,fork TCP-CONNECT:$ALIAS:80
ktalik
fonte
2

Você pode usar uma rede de sobreposição como o Weave Net , que atribuirá um endereço IP exclusivo a cada contêiner e expor implicitamente todas as portas a todas as partes do contêiner da rede.

O Weave também fornece integração de rede do host . Ele está desativado por padrão, mas, se você deseja acessar também os endereços IP do contêiner (e todas as suas portas) do host, pode executar simplesmente executarweave expose .

Divulgação completa: Trabalho na Weaveworks.

fons
fonte
2

Existe um invólucro HAProxy útil.

docker run -it -p LOCALPORT:PROXYPORT --rm --link TARGET_CONTAINER:EZNAME -e "BACKEND_HOST=EZNAME" -e "BACKEND_PORT=PROXYPORT" demandbase/docker-tcp-proxy

Isso cria um HAProxy para o contêiner de destino. mole-mole.

Kwerle
fonte
1

Leia a resposta de Ricardo primeiro. Isso funcionou para mim.

No entanto, existe um cenário em que isso não funcionará se o contêiner em execução foi iniciado usando o docker-compose. Isso ocorre porque o docker-compose (estou executando o docker 1.17) cria uma nova rede. A maneira de abordar esse cenário seria

docker network ls

Em seguida, acrescente o seguinte docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus --net network_name

ice.nicer
fonte
0

Não é possível fazer mapeamento de porta ao vivo, mas há várias maneiras de fornecer a um contêiner do Docker o que equivale a uma interface real, como uma máquina virtual.

Interfaces Macvlan

O Docker agora inclui um driver de rede Macvlan . Isso conecta uma rede Docker a uma interface do "mundo real" e permite atribuir os endereços dessa rede diretamente ao contêiner (como um modo de ponte de máquinas virtuais).

docker network create \
    -d macvlan \
    --subnet=172.16.86.0/24 \
    --gateway=172.16.86.1  \
    -o parent=eth0 pub_net

pipeworktambém pode mapear uma interface real em um contêiner ou configurar uma sub interface em versões mais antigas do Docker.

IP de roteamento

Se você tiver controle da rede, poderá rotear redes adicionais para o host do Docker para uso nos contêineres.

Em seguida, atribua essa rede aos contêineres e configure o host do Docker para rotear os pacotes pela rede do Docker.

Interface de host compartilhada

A --net hostopção permite que a interface do host seja compartilhada em um contêiner, mas provavelmente essa não é uma boa configuração para executar vários contêineres em um host devido à natureza compartilhada.

Matt
fonte