fbpx
Perdemos 17 anos por não entender redes Docker
Publicado em: sábado, 18 de jun de 2022
Categorias: Docker de A a Z
Tags: Docker

Somos 6390 pessoas no grupo de Docker do telegram. Se cada um de nós perdeu ao menos 1 dia tentando fazer 2 contêineres conversarem então juntos perdemos mais de 17 anos!

Esse é um guia prático de redes docker que ajudará a entender esse universo.

A missão desse post é trazer os principais conceitos para evitar sofrimento. São conceitos ridiculamente simples, mas que vão poupar horas, dias ou até semanas de cada indivíduo que ler esse post.

Como sei isso?

Bom, eu ajudo a comunidade docker desde 2016, todo dia, várias vezes por dia.

1 ) Verdade 1: Cada container tem seu próprio IP

Se você rodar um simples docker run assim

docker run --rm --name teste -it ubuntu

E inspecionar seu container com

docker inspect teste

Terá como resposta algo assim:

[
    {
        "Id": "2627327edcd3f02ca5678f4160e383b5a95b0a783d1e024c6a522372b801a265",
        "Created": "2022-06-14T23:12:01.573861823Z",
        "Path": "bash",
        "Args": [],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 102858,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2022-06-14T23:12:01.788208155Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:d2e4e1f511320dfb2d0baff2468fcf0526998b73fe10c8890b4684bb7ef8290f",       

         ...

        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "e90ddbe2f53b0300cf23669aaa13a86fd78614ed825253727ccc6aab8a77e23d",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {},
            "SandboxKey": "/var/run/docker/netns/e90ddbe2f53b",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "c9e40ff8413ad8e653388d606515e60581ff49d4a6994210a7c731136bf1fd1b",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "02:42:ac:11:00:02",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "dfd8b5810091b3125deb375c68c263a83d945dab888abd61683e1b9cf85c6209",
                    "EndpointID": "c9e40ff8413ad8e653388d606515e60581ff49d4a6994210a7c731136bf1fd1b",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",
                    "DriverOpts": null
                }
            }
        }
    }
]

Exceções:

Quando você cria o container com rede e define ex-pli-ci-ta-men-te redes NONE ou HOST.

2 ) Verdade 2: Todo container é IRRESTRITO para receber e enviar dados pela rede, para a internet ou para outros containers.

Ao criar e rodar um container, por default, não há restrição alguma, não há limite, necessidade alguma de configuração extra para seu container escutar, receber requisições, e enviar requisições para a internet.

Você não precisa abrir porta, expor porta, não precisa de absolutamente nada.

Seu container já nasce podendo receber requisições em qualquer porta, e enviar requisições para qualquer porta de qualquer IP.

Se queremos que um container chamado API converse com um container chamado DB então:

  • O container DB não precisa expor porta.
  • A porta definida no dockerfile pode ser respeitada ou não.
  • No dockerfile pode não haver porta definida no expose.

O container API precisa saber o IP do container DB, para alcançar o banco de dados. O resto são as credenciais.

3 ) Ao invés de referenciar o IP, use redes

Supomos que você tenha criado seu banco e api assim:

docker network create rededb -d bridge

docker run --name bancodedados --network rededb -e... mysql

docker run --name api --network rededb -e... -p 80:80 minhaimagemdeapi
version: "2"
services:
  api:
    image: minhaimagemdeapi
    environment:
      ...
    ports:
      - 80:80
    networks:
      - rededb

  bancodedados:
    image: mysql
    environment:
      ...
    networks:
      - rededb

networks:
  rededb:
    driver: bridge

Sem expor a porta 3306 no host, ou seja, não tem o parâmetro -p.

Para o container chamado api acessar o banco bancodedados, basta usar o endereço “bancodedados:3306” para alcançar o mysql.

O container com nome bancodedados, nem sequer precisa do -p para estar acessível para o container api.

4) Referenciar IP’s é errado e abominável

Seja você um administrador, ou um container. Fazer referência, estática ao IP de um container é um erro grave.

Exceção:

Aplicações legadas (com mais de 30 anos) cuja infraestrutura de rede das libraries não consegue falar com um DNS.

5) Criar uma rede bridge é ridiculamente simples

docker network create resourcesnetwork -d bridge

6) A rede bridge default (que possui o nome “bridge”) é uma rede especial que não possui resolução de nomes (para service discovery) e é considerada legado

The default bridge network is considered a legacy detail of Docker and is not recommended for production use. Configuring it is a manual operation, and it has technical shortcomings.

https://docs.docker.com/network/bridge/#use-the-default-bridge-network

VERDADE
Uma rede bridge custom (criada por você) tem resolução de nomes.

MENTIRA
A rede bridge tem resolução de nomes.

7) Se o EXPOSE no dockerfile não influencia em como o container é executado, para que serve?

Serve para documentar e comunicar quais portas o container escuta. Quem usa essa informação é o humano que vai criar esse container, seja via docker run, via docker compose ou yaml do Kubernetes.

docker run -d --name c1  nginx

docker ps -a

CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
d4125f39dd11   nginx     "/docker-entrypoint.…"   8 seconds ago   Up 8 seconds   80/tcp    c1

Acima temos um NGINX rodando sem nenhuma porta mapeada. Pela informação na coluna PORTS, sabemos que no dockerfile há um expose para a porta 80.

Ele acusa que ela existe e isso é útil para nós, que estamos subindo o container.

Da mesma forma que no exemplo abaixo temos a lista de portas:

  • 4369/tcp
  • 5671-5672/tcp
  • 15691-15692/tcp
  • 25672/tcp
docker run --rm --name rabbit rabbitmq

docker ps -a

CONTAINER ID   IMAGE      COMMAND                  CREATED          STATUS          PORTS                                                 NAMES
054aa8df1735   rabbitmq   "docker-entrypoint.s…"   34 seconds ago   Up 33 seconds   4369/tcp, 5671-5672/tcp, 15691-15692/tcp, 25672/tcp   rabbit

Se você conhece o RabbitMQ talvez tenha sentido falta da 15672 (porta do management UI). Ela está presente apenas nas imagens cuja tag “management”.

Ao executarmos criarmos um container a partir da imagem rabbitmq:management, temos a 15672 definida no expose.

docker run --rm --name rabbit rabbitmq:management

docker ps -a

CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS                                                                  NAMES
fc9ed1b736d0   rabbitmq:management   "docker-entrypoint.s…"   11 seconds ago   Up 10 seconds   4369/tcp, 5671-5672/tcp, 15671-15672/tcp, 15691-15692/tcp, 25672/tcp   rabbit

A utilidade é essa, comunicar para outro humano. O docker em si, ignora essa informação.

Exceção:

No docker run existe a possibilidade de usar o -P (P maiúsculo), que é opção que não possui parâmetros. Ela usa todas as portas definidas com EXPOSE no dockerfile para alocar portas aleatórias para cada uma delas. É útil para testes… só!

8) Se expor porta (com docker run -p) é desnecessário, para que serve então?

Seu browser, embora no linux esteja na mesma máquina que o container, não está na mesma rede, e é considerado externo do ponto de vista de rede.

No linux você poderia usar o IP do container, por exemplo.

docker run --rm -d  --name c1   nginx
d5d12c8439a578180455b80024e61d6b95fc0284f81fb871a242bcac177589d3

docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' c1
172.17.0.2

curl http://172.17.0.2
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Mas isso estando no mesmo servidor. Os IP’s dos containers são acessíveis apenas dentro do mesmo servidor.

Se eu quero que uma aplicação que está em outra máquina, ou um serviço da internet, ou um usuário da internet, acesse algum container, eu preciso expor a porta no host.

Isso serve para você, inclusive, quando quer acessar o banco de dados com alguma ferramenta.

No Windows por conta da máquina virtual do WSL2 não é tão prático quanto no linux.

9) localhost de um container é o próprio container

Para um container, localhost ou 127.0.0.1 é o próprio container.

Ao executarmos o comando curl http://localhost em um container qualquer, a requisição será enviada para o próprio container.

E nunca, sob hipótese alguma, essa requisição irá para:

  • Seu desktop ou estação de trabalho que o container esteja sendo executado.
    • Mesmo se for Linux.
    • Mesmo se for Windows.
  • O host no qual o container está rodando.
    • Mesmo que seja Linux ou Windows.
    • Ou qualquer outro novo sistema operacional que possa ser criado durante esse milênio.

Exceções:

Ao invés de usar redes bridge ou overlay, seu container pode estar em uma rede host. A rede host remove a camada de abstração de rede, imposta pelo kernel, e entrega a rede do host como rede do container.

Se seu container estiver na rede host. Então, nesse caso, você removeu a camada de rede do docker: Boa sorte!

10) Para um container acessar um recurso do host é preciso…

Há algumas opções:

Usar o IP público do host

Válido para qualquer tipo de setup docker.

E se você

Usar o nome especial host.docker.internal

Válido exclusivamente para DOCKER DESKTOP.

https://docs.docker.com/desktop/windows/networking/

11) Quero que containers de 2 docker-composes conversem entre si

No item 3 vimos que devemos usar redes docker, essa é a reposta.

Existem 2 formas de fazer isso:

11.1) Criando rede previamente

Use o item 5 desse post para criar uma rede.

#Execute
docker network create -d bridge resourcesnetwork 
version: "2"
services:

  bancodedados:
    image: mysql
    environment:
      ...
    networks:
      - resourcesnetwork

networks:
  resourcesnetwork:
    external: true
version: "2"
services:

  api:
    image: minhaimagemdeapi
    environment:
      ...
    ports:
      - 80:80
    networks:
      - resourcesnetwork

networks:
  resourcesnetwork:
    external: true

11.2) Criar a rede no primeiro docker compose, e referenciar no segundo

A primeira definição de rede, do primeiro docker-compose.yaml fica como driver:bridge, enquanto no segundo fica a rede deve ser referenciada como <nome_projeto_compose>_<nome_rede> com external:true.

Ao adotar essa abordagem, o segundo docker-compose dependerá da rede que está sendo gerenciada pelo primeiro. Isso dificulta as coisas na hora de rodar docker-compose down do primeiro docker-compose, já que remover a rede não será possível por estar em uso no segundo.

Sugestões?

Comente!

0 comentários

Enviar um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.

[docker de a a z]

Lives

Fique de olho nas lives

Fique de olho nas lives no meu canal do Youtube, no Canal .NET e nos Grupos do Facebook e Instagram.

Aceleradores

Existem diversas formas de viabilizar o suporte ao teu projeto. Seja com os treinamentos, consultoria, mentorias em grupo.