Pular para o conteúdo principal

Redes Docker

https://docs.docker.com/network/

Um do recursos do docker que o faz tão poderoso é justamente a utiliação dos recursos de rede e como conectar os containers de forma fácil.

  • Podemos conectar containers de máquinas diferentes
  • Podemos fazer uma rede de sobreposição entre varios servidores

Recursos linux que o docker utiliza

  • veth
    • é um virtual ethernet para poder criar as placas de redes do container.
  • bridge
    • ponte de roteamento do host para o container
  • iptables
    • firewall de sistema linux

O docker cria já algumas networks por default

vagrant@worker1:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
d0d374d6bfc8 bridge bridge local
51533d503c46 host host local
4a528ce35bc2 none null local
vagrant@worker1:~$

Vale observar o escopo desta rede somente pertence ao host, por isso é local falaremos sobre escopo global quando for trabalhar com clusters e rede overlay.

Por padrão o docker não expoe as portas do seus containers, logo se subirmos um nginx por exemplo ele ficará isolado e não conseguirá fornecer o serviço.

vagrant@worker1:~$ docker container run -dit --name webserver nginx
d874bd76de6ba12e12defdd98560c871b89556a2b9f95b456a14c5a4f4508f3a
vagrant@worker1:~$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d874bd76de6b nginx "/docker-entrypoint.…" 5 seconds ago Up 4 seconds 80/tcp webserver

Nesse caso vc só conseguiria chegar nesse container dentro de outro container que esteja na mesma rede nat que o docker cria. De fora da nat não seria possível

O padrametro -p ou --publish é quando o docker publica uma porta externa que vira para o porta interna do container.

O certo seria

# porta do host:porta do container ou seja, origem:destino
vagrant@worker1:~$ docker container run -dit -p 80:80 --name webserver nginx
51321d90ecee55960a0e965efb4c57cdbf1d4e78eb0aef7caf9f1b844d273ddc
vagrant@worker1:~$ docker container ls
# observe agora que em ports ele esta fazendo um apontamento
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
51321d90ecee nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:80->80/tcp, :::8000->80/tcp webserver

Se vc bater no ip dessa máquina irá agora retornar a página do nginx

nginx

Network drivers

São os recursos fazem que a tradução do docker para a rede do host. Existem diversos drivers.

Bridge

É a rede padrão do Docker, caso não seja especificado um driver. É criada interface de rede no linux chamada de dockerX sendo X um número e é criado uma interface dessa para cada network bridge que for criada. Não confundir com criação de container que utilizam essa rede. O docker cria uma veth para cada container se comunicar com essa interface. É uma rede NAT e já tem o recurso de dns interno.

Para conferir a criação interface dockerX podemos ver que temos uma docker0 na máquina host. Esta interface está DOWN pois nenhum container esta utilizando ainda, mas ficará up assim que rodarmos o primeiro container utilizando a bridge.

vagrant@worker1:~$ ip -c -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
enp0s3 UP 10.0.2.15/24 fe80::cd:65ff:fe0c:9769/64
enp0s8 UP 192.168.56.110/24 fe80::a00:27ff:fe4e:ae9f/64
docker0 DOWN 172.17.0.1/16 fe80::42:77ff:fe88:14cd/64

bridge O switch (docker-proxy), a interface docker0 e o veth promovem uma camada de isolamento para o container nesta figura. Desta forma é possível que vários container usem suas portas específicas podendo cada container por exemplo utilizar a porta 80.

O parâmetro que devemos passar é --network bridge no comando docker container run

docker container run -dit --name webserver --network bridge -p 80:80 nginx

Já verifique que a interface agora ficou UP.

vagrant@worker1:~$ ip -c -br a | grep docker
docker0 UP 172.17.0.1/16 fe80::42:77ff:fe88:14cd/64

Verifique o serviço na porta 80 do host

vagrant@worker1:~$ sudo ss -ntpl | grep 80
LISTEN 0 4096 0.0.0.0:80 0.0.0.0:* users:(("docker-proxy",pid=2227,fd=4))
LISTEN 0 4096 [::]:80 [::]:* users:(("docker-proxy",pid=2233,fd=4))

O docker executa um processo para cada rede bridge chamado de docker-proxy. O docker cria regras no iptables para o isolamento dos processos de rede.

Podemos verificar isto através do comando

vagrant@worker1:~$ sudo iptables -nL
Chain INPUT (policy ACCEPT)
target prot opt source destination


Chain FORWARD (policy DROP)
target prot opt source destination
# ISOLAMENTO
DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-1 all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

# veja nesta regra
Chain DOCKER (1 references)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 172.17.0.2 tcp dpt:80

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target prot opt source
destination
# ISOLAMENTO
DOCKER-ISOLATION-STAGE-2 all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0

Chain DOCKER-ISOLATION-STAGE-2 (1 references)
target prot opt source destination
DROP all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0

Chain DOCKER-USER (1 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0

Remova o container para a próxima etapa.

Vamos criar 2 containers e comunicar entre eles por uma bridge.

vagrant@worker1:~$ docker container run -dit --network bridge --name container1 --hostname servidor alpine
vagrant@worker1:~$ docker container run -dit --network bridge --name container2 --hostname cliente alpine
# CONTAINER1 com ip 172.17.0.2
vagrant@worker1:~$ docker container exec container1 ip a show eth0
21: eth0@if22: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# CONTAINER2 com ip 172.17.0.3
vagrant@worker1:~$ docker container exec container2 ip a show eth0
23: eth0@if24: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# Pingando container1 no container2
vagrant@worker1:~$ docker container exec container1 ping -c2 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.122 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.120 ms

# Pingando container 2 no container 1
vagrant@worker1:~$ docker container exec container2 ping -c2 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.064 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.082 ms

Tudo funcionando, agora vamos pingar pelo hostname

vagrant@worker1:~$ docker container exec container1 ping -c2 cliente
ping: bad address 'cliente'
vagrant@worker1:~$ docker container exec container2 ping -c2 servidor
ping: bad address 'servidor'
vagrant@worker1:~$

Isso mostra que o default bridge criado pelo docker não tem resolução de dns pela rede. Este recurso não vem habilitado.

Remova os containers.

Para que isso funciona precisamos definir uma user defined network. Vantagens:

  • DNS automático
  • Isolamento
  • Conectar e desconectar on the fly
  • Configuração personalizada
vagrant@worker1:~$ docker network create --driver bridge --subnet 172.100.0.0/16 dockernet
8108970bf72615dcdbbe099d9419b6ac811595ee3f535e2280a18b833fb11747
vagrant@worker1:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
9efd4a6f72a7 bridge bridge local
8108970bf726 dockernet bridge local
51533d503c46 host host local
4a528ce35bc2 none null local

vagrant@worker1:~$ docker network inspect dockernet | jq
[
{
"Name": "dockernet",
"Id": "8108970bf72615dcdbbe099d9419b6ac811595ee3f535e2280a18b833fb11747",
"Created": "2022-06-28T19:55:55.045867514Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.100.0.0/16"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
# todo container que subir vai aparecer aqui.
"Containers": {},
"Options": {},
"Labels": {}
}
]

Agora vamos criar novamente as maquinas e pingar entre elas pelo hostname e verificar.

vagrant@worker1:~$ docker container run -dit --network dockernet --name container1 --hostname server alpine
vagrant@worker1:~$ docker container run -dit --network dockernet --name container2 --hostname cliente alpine
# vamos verificar a rede
vagrant@worker1:~$ docker network inspect dockernet | jq
[
{
"Name": "dockernet",
"Id": "8108970bf72615dcdbbe099d9419b6ac811595ee3f535e2280a18b833fb11747",
"Created": "2022-06-28T19:55:55.045867514Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.100.0.0/16"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
# Observe que os containers aqui estão
"Containers": {
"2f9b7e7b2bb80f8b1966785ddb865f858cd1e30d232410dfb212c5d798cd7a42": {
"Name": "container1",
"EndpointID": "d73ed0c87ff7370963ca166e851ebb39e96380fe0b39e1009192409728fb3b3a",
"MacAddress": "02:42:ac:64:00:02",
"IPv4Address": "172.100.0.2/16",
"IPv6Address": ""
},
"da6ac7aeb79d6c68f540588c92b585772adfb0fe04e835ef92d22d92c3a03b88": {
"Name": "container2",
"EndpointID": "2071f2e0478d8a2291c443a5be837e5150832e588f4e0fa4856dac762d7967fc",
"MacAddress": "02:42:ac:64:00:03",
"IPv4Address": "172.100.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
vagrant@worker1:~$ docker container exec container1 ping -c2 cliente
PING cliente (172.100.0.3): 56 data bytes
64 bytes from 172.100.0.3: seq=0 ttl=64 time=0.114 ms
64 bytes from 172.100.0.3: seq=1 ttl=64 time=0.090 ms
vagrant@worker1:~$ docker container exec container2 ping -c2 server
PING server (172.100.0.2): 56 data bytes
64 bytes from 172.100.0.2: seq=0 ttl=64 time=0.060 ms
64 bytes from 172.100.0.2: seq=1 ttl=64 time=0.097 ms

Agora vamos desconectar um container e conectá-lo com outro ip.

# Disconectando container 2
vagrant@worker1:~$ docker network disconnect dockernet container2
# tentando o ping e vendo que ele não responde
vagrant@worker1:~$ docker container exec container1 ping -c2 cliente
ping: bad address 'cliente'
# conectando o container 2 com ip .10
vagrant@worker1:~$ docker network connect --ip 172.100.0.10 dockernet container2
# conferindo a mudança
vagrant@worker1:~$ docker network inspect dockernet | jq
[
{
"Name": "dockernet",
"Id": "8108970bf72615dcdbbe099d9419b6ac811595ee3f535e2280a18b833fb11747",
"Created": "2022-06-28T19:55:55.045867514Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.100.0.0/16"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"2f9b7e7b2bb80f8b1966785ddb865f858cd1e30d232410dfb212c5d798cd7a42": {
"Name": "container1",
"EndpointID": "d73ed0c87ff7370963ca166e851ebb39e96380fe0b39e1009192409728fb3b3a",
"MacAddress": "02:42:ac:64:00:02",
"IPv4Address": "172.100.0.2/16",
"IPv6Address": ""
},
"da6ac7aeb79d6c68f540588c92b585772adfb0fe04e835ef92d22d92c3a03b88": {
"Name": "container2",
"EndpointID": "2e98ca2dde0473e31014dd776d32c413fcd601b495197f0a1e0d769dbfd9b842",
"MacAddress": "02:42:ac:64:00:0a",
# com ip 10 como definimos
"IPv4Address": "172.100.0.10/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]

Se levantarmos um container e fizer um cat no resolv.conf para conferir quem é o resolver do container

vagrant@worker1:~$ docker container run -dit --name container1 alpine
vagrant@worker1:~$ docker container exec container1 cat /etc/resolv.conf
# observe que ele pegou o dns da máquina
nameserver 10.0.2.3
search localdomain
vagrant@worker1:~$ docker container rm -f container1
container1
vagrant@worker1:~$ docker container run -dit --name container1 --dns 1.1.1.1 alpine
43bc80a0e5ccbf5102816bfb6d08f002bb00045532db703f60a95a9b3b288f4e
vagrant@worker1:~$ docker container exec container1 --dns 1.1.1.1 cat /etc/resolv.conf
OCI runtime exec failed: exec failed: unable to start container process: exec: "--dns": executable file not found in $PATH: unknown
vagrant@worker1:~$ docker container exec container1 cat /etc/resolv.conf
search localdomain
# agora ele pegou o dns que passamos
nameserver 1.1.1.1
vagrant@worker1:~$

para alterar que sempre o dns seja definido sem passar, basta alterar o daemon.json, e reinicie o service do docker.

vagrant@worker1:~$ sudo vi /etc/docker/daemon.json
vagrant@worker1:~$ sudo systemctl restart docker.service -f
vagrant@worker1:~$ cat /etc/docker/daemon.json
{
"storage-driver": "overlay2",
"dns": ["8.8.8.8", "1.1.1.1"]
}
vagrant@worker1:~$ docker container run -dit --name container1 alpine
vagrant@worker1:~$ docker container exec container1 cat /etc/resolv.conf
search localdomain
nameserver 8.8.8.8
nameserver 1.1.1.1
vagrant@worker1:~$

Host

É a rede onde o IP da máquina é compartilhado com o container, nesta rede não é possível existir dois containers utilizando a mesma porta de rede. A camada de isolamento aqui é removida e os containers possuem o mesmo ip do host.

host

O parâmetro que devemos passar é --network host no comando docker container run e não é necessário passar a porta. Ele vai saber qual é a porta de acordo com a imagem que vc tiver rodando. No dockerfile da imagem o parametro EXPOSE vai de dizer qual a porta o container precisa escutar.

vagrant@worker1:~$ docker container run -dit --name webserver --network host  nginx
vagrant@worker1:~$ docker container ls
# observe que não tem a porta
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
71c8dc8650ba nginx "/docker-entrypoint.…" 8 seconds ago Up 8 seconds webserver
# batendo no proprio localhost temos página do nginx
vagrant@worker1:~$ curl localhost
# removido...
<title>Welcome to nginx!</title>
# removido...

# Conferindo o serviço vimos que a porta 80 bate direto no nginx e não no docker-proxy
vagrant@worker1:~$ sudo ss -ntpl | grep 80
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=5029,fd=7),("nginx",pid=5028,fd=7),("nginx",pid=4981,fd=7))
LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=5029,fd=8),("nginx",pid=5028,fd=8),("nginx",pid=4981,fd=8))
# E vemos também que ele não utilou o docker0 por isso esta DOWN
vagrant@worker1:~$ ip -c -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
enp0s3 UP 10.0.2.15/24 fe80::cd:65ff:fe0c:9769/64
enp0s8 UP 192.168.56.110/24 fe80::a00:27ff:fe4e:ae9f/64
docker0 DOWN 172.17.0.1/16 fe80::42:77ff:fe88:14cd/64
vagrant@worker1:~$

Remova o container webserver

None

Com esse parametro o container subirá sem rede, ou seja, completamente isolado. O parâmetro que devemos passar é --network host no comando docker container run

vagrant@worker1:~$ docker container run -dit --name nonet --network none alpine ash
1e8730a7bde13e7cd4fcd1ec8dd3bc2c03397c887b4b5414745d781b41bf67f7
vagrant@worker1:~$ docker container exec nonet ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
vagrant@worker1:~$

Verifique a stack de rede do container executando algum comando de verificação de rede.

vagrant@worker1:~$ docker container run -dit --name nonet --network none alpine ash
vagrant@worker1:~$ docker container exec nonet ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever

Remova o container

Macvlan

É a rede onde utilizamos os recursos de VLAN para fazer a separação lógica da rede, através dela podemos fornecer endereços IP para os containers que serão roteáveis para a rede física. Também é utilizada para darmos a um container um ip diferente do host porém na mesma rede do host. Vamos imaginar que vc que o host tem um ip 10.0.0.10 e vc quer subir um container com o ip 10.0.0.20. Você irá entregar ao seu container uma placa de rede virtual, porém com um endereço mac único. É uma das soluções para um container não passar pelo host.

macvlan

Overlay

É uma rede sobreposta na qual é criada uma rede lógica onde são suportados diversas redes hosts. Este tipo de recurso é utilizado ao se trabalhar com clusters como é o caso do Docker Swarm e Kubernetes.

overlay

Basicamente o docker cria um túnel para por fora da rede externa e é chamado de rede overlay. Com isso é capaz de que todos os containers de todos os hosts sejam acessíveis por qualquer um dos ips dos hosts. Trocando em miúdos, qualquer ip que vc apontar será como um ip único do cluster.