Docker: criando uma rede interna entre containers NGINX e python
Publicado em
Antes de mais nada, este artigo considera que você já tenha o docker
instalado e que você tenha algum conhecimento de terminal/bash para conseguir executar os passos abaixo. Pode ser executado em Windows, Mac ou Linux com suas devidas adaptações. Neste artigo estou utilizando ubuntu 20.04.2 LTS
. Se um comando estiver rodando em um terminal (como um contêiner), abra um novo terminal para executar o outro comando, simples
assim.
Criando um servidor python
Vamos criar um programa em python
para servir uma página dinâmica. Crie uma pasta com o nome python
e crie um arquivo com o nome main.py
dentro delac om o seguinte conteúdo (não vou entrar no mérito de como você fará isso, utilize seu editor de texto de preferência):
import os from http
import HTTPStatus, serverimport socketserver from textwrap
import dedentclass
Server(server.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(HTTPStatus.OK.value, HTTPStatus.OK.phrase)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(bytes(dedent("""
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Exemplo</title>
</head>
<body>
<h1>Python</h1>
<p><code>1 + 1 = {}</code></p>
<img src="/images/carnaval.gif" alt="Imagem de festa de carnaval" />
</body>
</html>
""".format(1 + 1)), "utf-8"))
def serve_forever(port):
socketserver.TCPServer.allow_reuse_address = True
socketserver.TCPServer(('', int(port)), Server).serve_forever()
if __name__ == "__main__":
PORT = os.environ.get('PORT') or 8000
print("Serving on port %s" % PORT)
Server.serve_forever(PORT)
Perceba que foi colocada uma imagem de carnaval ali que não existe. Voltaremosnela a qualquer momento, ela não deve funcionar nesse momento mesmo.
Agora, rode um container montando o volume do container apontando para essapasta expondo a porta 8000
e executando o comando python
sobre esse arquivo:
docker run --rm -ti -v $(pwd)/python:/app -p 8000:8000 --name dinamico python:3-alpine python /app/main.py
Com isso, será possível acessar o servidor python
através do endereçohttp://localhost:8000
. Veja que a página foi acessada, que o conteúdo é oresultado do 1 + 1
que foi processado pelo python
e a imagem de carnavalnão foi trazida.
Criando um servidor estático (nginx)
Vamos criar uma pasta e colocar dentro dela uma imagem de carnaval (sim, aquela que falamos lá no servidor python).
Crie uma pasta com o nome public
e coloque uma imagem bem bonita do carnaval.Você pode usar qualquer imagem vinda dogoogle images.
Levante um servidor de arquivos estáticos utilizando a imagem nginx:alpine
esirva os arquivos que estão na pasta public
na porta 9000
.
docker run --rm -ti -v $(pwd)/public:/usr/share/nginx/html -p 9000:80 --name estaticos nginx:alpine
Experimente acessar a seguinte rota no seu navegador:http://localhost:9000/carnaval.gif
.
O problema
Até este momento, nenhum container consegue “enxergar” o outro pelo seuhostname, somente pelo IP de cada um. Isto acontece porque eles não estãoconectados no mesmo network. Experimente acessar de dentro dos containers.
Primeiro, descubra o IP de cada conteiner.
docker inspect -f '' estaticos
172.17.0.3
docker inspect -f '' dinamico
172.17.0.2
Com esses IPs em mão, entre em um container e faça um ping no IP do outrocontainer.
docker exec -ti dinamico ash
# /
Ao exibir esse prompt
com os caracteres # /
você estará dentro do terminalash
do container. Nele, faça um ping
no outro contêiner. Utilize o atributo-c 2
para que sejam feitas apenas 2 iterações.
# / ping -c 2 172.17.0.3
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.191 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.121 ms
--- 172.17.0.3 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.121/0.156/0.191 ms
Tente acessar um hostname externo, verá que será possível:
/ # ping -c 2 google.com
PING google.com (172.217.29.110): 56 data bytes
64 bytes from 172.217.29.110: seq=0 ttl=254 time=14.828 ms
64 bytes from 172.217.29.110: seq=1 ttl=254 time=11.973 ms
--- google.com ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 11.973/13.400/14.828 ms
Agora, ao tentar acessar usando o hostname
não será possível:
/ # ping -c 2 estaticos
ping: bad address 'estaticos'
Para sair do contêiner, use o comando exit
. Isto tratá você de volta ao seuprompt
de comando normal.
/ # exit
Criando uma ponte entre nós
Para criar a conexão entre os contêineres de forma que um consiga acessar ohostname do outro crie uma network do tipo bridge, ou seja, do tipouser defined. Assim os containers poderão se comunicar de forma simples.
docker network create --driver bridge rede-maneira
Para verificar que a rede foi criada, utilize o comando network ls
.
docker network ls
NETWORK ID NAME DRIVER SCOPE
ea6f9892c87c bridge bridge local
374b6a5a3dc7 host host local
293f617510fd none null local
e201b67bf15b rede-massa bridge local
Conecte-os utilizando o comando network connect [rede] [container]
.
docker network connect rede-massa dinamico
docker network connect rede-massa estaticos
Para verificar se eles estão atrelados, utilize o network inspect [rede]
paraverificar.
docker network inspect rede-massa
[
{
"Name": "rede-massa",
"Id": "e201b67bf15bdc385b43522da4d1e9358e68c4b0e5eed0542232e1e7d86b8574",
"Created": "2021-08-13T00:24:30.615556773-03:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.20.0.0/16",
"Gateway": "172.20.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"0db152ec91ada8675a94b4f318c3ee4eb7d540e2ff55d925c688fcd7b9e7cdcb": {
"Name": "estaticos",
"EndpointID": "9be41271013f4df28c64399513bf3eb26a44b9fe46973ab4d7aec949d712c720",
"MacAddress": "00:00:00:00:00:00",
"IPv4Address": "172.20.0.3/16",
"IPv6Address": ""
},
"cc275ab619c92949bca71e9e04f8e79b4a4e0d8abbfc820818e22e8aed2250de": {
"Name": "dinamico",
"EndpointID": "3e0e9ccf570deb8015b4bad7d8d17527164a94ea2179bfa9a40923e700770b6f",
"MacAddress": "00:00:00:00:00:00",
"IPv4Address": "172.20.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
Tente acessar novamente um contêiner de dentro do outro:
docker exec -ti dinamico
# / ping -c 2 estaticos
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.191 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.121 ms
--- 172.17.0.3 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.121/0.156/0.191 ms
Sucesso! Pode sair do container com o comando exit
.
Criando um proxy para mapear as rotas
Por último, vamos criar um servidor “proxy” utilizando a imagem nginx:alpine
que irá conectar os dois servidores e redirecionar cada requisição para seudevido lugar.
Crie uma pasta chamada proxy
e crie um arquivo de configuração do servidornginx
para mapear as rotas dos servidores utilizando o comando proxy_pass
para informar que ao tentar acessar a rota /
deve
enviar para o servidordinamico
pela porta 8000
e ao acessar a rota /images
deve enviar para oservidor estaticos
na porta 80
. Aproveitando, utilizamos o comando rewrite
para remover a “pasta” /images
da rota, deixando transparente para o contêinerde arquivos estáticos (saber expressões regulares sempre ajuda muito).
worker_processes 1;
http {
server {
listen 80;
location / {
proxy_pass http://dinamico:8000;
}
location /images {
rewrite /images/([^/]+) /$1 break;
proxy_pass http://estaticos:80;
}
}
}
events {
worker_connections 1024;
}
Utilize a pasta proxy
para fazer a configuração do nginx
. Aproveite para jáconectar o container na rede no momento de criação (com o atributo --network
),assim pode-se economizar
linhas de comando.
Vamos expor a porta 3000
para conseguir acessar o proxy.
docker run --rm -ti -v $(pwd)/proxy/nginx.conf:/etc/nginx/nginx.conf --network rede-massa -p 3000:80 --name proxy nginx:alpine
Por fim, tente acessar o proxy pela url http://localhost:3000/
, e você verá a página escrita em python
e a imagem distribuída pelo nginx
, todas disponíveis pelo proxy
.
Basicamente, foi isto o que foi feito:
.------------------------- rede-massa ---------------------------.
| |
| .--------. .----------. |
(internet) -- http://localhost:3000 ---+--> | proxy | --- / --> http://dinamico:8000 --> | dinamico | |
| '--------' '----------' |
| | |
| | .-----------. |
| '--- /images --> http://estaticos:80 --> | estaticos | |
| '-----------' |
'----------------------------------------------------------------'
Desta forma, apenas o proxy
precisa estar com a porta exposta.
Para “destruir” tudo execute a sequencia abaixo, não vou entrar em detalhes poisos comandos são auto explicativos:
# pare os contêineres
docker stop estaticos
docker stop dinamico
docker stop proxy
# remova os contêineres
docker rm estaticos
docker rm dinamico
docker rm proxy
# remova a rede
docker network rm rede-massa
# caso queira, remova as imagens (eu costumo deixar para não precisar baixar novamente no futuro)
docker rmi nginx:alpine
docker rmi python:3-alpine
Verifique se tudo foi removido:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
docker network ls
NETWORK ID NAME DRIVER SCOPE
ea6f9892c87c bridge bridge local
374b6a5a3dc7 host host local
293f617510fd none null local
Tente criar tudo novamente só que agora só expondo somente a porta do proxy
.
Alguns comandos para ajudar:
# container
docker ps # lista os containers
docker run [imagem] # roda um container
docker stop [container] # para um container
docker inspect [container] # verifica informações do container
docker exec [container] [comando] # executa um comando no container
docker rm [container] # remove um container (parado)
# imagens
docker pull [image] # baixa uma imagem do hub.docker.com
docker rmi [image] # apaga uma imagem do cache de sua máquina
# network
docker network create --driver bridge [rede] # cria uma rede do tipo bridge
docker network connect [rede] [container] # conecta o container
docker network disconnect [rede] [container] # desconectar o container
docker network inspect [rede] # vê detalhes de uma rede
docker network rm [rede] # apaga uma rede
Alguns atributos do comando docker run
:
docker run
-i -t # habilita poderem de interação de Input pelo TTY (terminal)
--rm # remove o container assim que ele for finalizado
-p [porta_externa]:[porta_interna] # expõe uma porta para acesso
-v [pasta]:[pasta_do_container] # monta/mapeia uma pasta dentro do container
--network [rede] # conecta o container a uma rede
--name [nome] # nome de fácil acesso ao container
[imagem] # imagem que será executada
[comando] # comando dentro da imagem a ser executado
Referencias:
- https://docs.docker.com/network/bridge/
- https://docs.docker.com/network/network-tutorial-standalone/
- https://hub.docker.com/_/nginx
- https://hub.docker.com/_/python
- https://pythonbasics.org/webserver/
- https://stackoverflow.com/a/25529620
- https://docs.python.org/3/library/http.server.html#module-http.server
- http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
- http://nginx.org/en/docs/beginners_guide.html#conf_structure
- https://gist.github.com/nishantmodak/d08aae033775cb1a0f8a
- https://docs.docker.com/engine/reference/commandline/run/
- https://docs.docker.com/engine/reference/commandline/network/