dgmike

Venha comigo descobrir esse mundo de tecnologia.

Docker: criando uma rede interna entre containers NGINX e python

Publicado em

Conhecendo como funciona o sistema de redes do docker. Neste artigo, demonstro como criar e utilizar uma rede com docker puro.

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:alpineque 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_passpara 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 rewritepara 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:

Tags:

  • Docker
  • Tutoriais
  • Python