Docker Networking
A ideia aqui é tentar relacionar como funciona a rede em containers com a network namespaces e como isso é usado.
É possível executar sem nenhuma conexão com o mundo externo.
docker run --network none nginx
Se executamos um container desta maneira.
docker run --network host nginx
O container não esta criando um namespace de rede para ele. Ele esta utilizando a porta 80 do host.
curl localhost
<!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>
Se executarmos em outro terminal o mesmo comando...
ddocker run --network host nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2024/02/28 20:09:41 [emerg] 1#1: bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
2024/02/28 20:09:41 [emerg] 1#1: bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
2024/02/28 20:09:41 [notice] 1#1: try again to bind() after 500ms
2024/02/28 20:09:41 [emerg] 1#1: bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
2024/02/28 20:09:41 [emerg] 1#1: bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
2024/02/28 20:09:41 [notice] 1#1: try again to bind() after 500ms
2024/02/28 20:09:41 [emerg] 1#1: bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
2024/02/28 20:09:41 [emerg] 1#1: bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
2024/02/28 20:09:41 [notice] 1#1: try again to bind() after 500ms
2024/02/28 20:09:41 [emerg] 1#1: bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
2024/02/28 20:09:41 [emerg] 1#1: bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
2024/02/28 20:09:41 [notice] 1#1: try again to bind() after 500ms
2024/02/28 20:09:41 [emerg] 1#1: bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
2024/02/28 20:09:41 [emerg] 1#1: bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
2024/02/28 20:09:41 [notice] 1#1: try again to bind() after 500ms
2024/02/28 20:09:41 [emerg] 1#1: still could not bind()
nginx: [emerg] still could not bind()
Não funciona pois a porta 80 já está em uso por outro container.
Uma vez instalado o docker na máquina ele cria um interface bridge no sistema chamada docker0 para ele que servirá como o switch que mostramos em network namespaces.
Podemos conferir com o comando.
docker network ls
NETWORK ID NAME DRIVER SCOPE
451b0bc3ade0 bridge bridge local
2b112d7c6904 host host local
def82d997b25 none null local
ip link show docker0
8: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:d1:95:e2:68 brd ff:ff:ff:ff:ff:ff
ip addr show docker0
8: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:d1:95:e2:68 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:d1ff:fe95:e268/64 scope link
valid_lft forever preferred_lft forever
Quando criamos um container o docker cria um namespace para ele, vamos conferir.
docker run -d nginx
3fd2e7fde3934b0fbcb81e944c9081835e43983bf708e128f8f432dde346b845
docker inspect 3fd2e7fde3934b0fbcb81e944c9081835e43983bf708e128f8f432dde346b845
[
{
"Id": "3fd2e7fde3934b0fbcb81e944c9081835e43983bf708e128f8f432dde346b845", # NOSSO ID DO CONTAINER
"Created": "2024-02-28T22:44:19.502717493Z",
"Path": "/docker-entrypoint.sh",
"Args": [
"nginx",
"-g",
"daemon off;"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 984281, # PID NO HOST
"ExitCode": 0,
"Error": "",
"StartedAt": "2024-02-28T22:44:19.745925397Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:e4720093a3c1381245b53a5a51b417963b3c4472d3f47fc301930a4f3b17666a",
"ResolvConfPath": "/var/lib/docker/containers/3fd2e7fde3934b0fbcb81e944c9081835e43983bf708e128f8f432dde346b845/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/3fd2e7fde3934b0fbcb81e944c9081835e43983bf708e128f8f432dde346b845/hostname",
"HostsPath": "/var/lib/docker/containers/3fd2e7fde3934b0fbcb81e944c9081835e43983bf708e128f8f432dde346b845/hosts",
"LogPath": "/var/lib/docker/containers/3fd2e7fde3934b0fbcb81e944c9081835e43983bf708e128f8f432dde346b845/3fd2e7fde3934b0fbcb81e944c9081835e43983bf708e128f8f432dde346b845-json.log",
"Name": "/suspicious_noether",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "docker-default",
"ExecIDs": null,
"HostConfig": {
# REMOVIDO
},
"GraphDriver": {
# REMOVIDO
},
"Mounts": [],
"Config": {
# REMOVIDO
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "3bea2fdf625aec1eccf7e9344f0c78ced4dcc26dc6b706433f19e9efdd9068ba",
"SandboxKey": "/var/run/docker/netns/3bea2fdf625a", # NAMESPACE HASH CRIADO
"Ports": {
"80/tcp": null
},
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "0c6c81bb2867d2cd759750a547a66b47ee0f9248fb4bb2a06f23e60fd7e1bea9",
"Gateway": "172.17.0.1", # GATEWAY É O DOCKER0
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.2", # NOSSO ID DO CONTAINER
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:02",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"MacAddress": "02:42:ac:11:00:02",
"NetworkID": "1bbfbd5a3d8dd49b966e719f06bb810aa3cc9542aa36e82c38405bcc8c84693b",
"EndpointID": "0c6c81bb2867d2cd759750a547a66b47ee0f9248fb4bb2a06f23e60fd7e1bea9",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DriverOpts": null,
"DNSNames": null
}
}
}
}
]
# Se fizemos um curl para esse ip o que temos
curl 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>
E como o docker faz o attach desse container? Do mesmo modo que fizemos antes, através do peers. Vamos inspecionar o que temos no host.
ip addr | grep docker
8: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
35: veth85a0078@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default # Vamos ver essa interface que foi atachada em docker0
# Não consegui executando o ip netns, logo precisei recorrer a outras ferramentas para ver esse namespace
# 984281 é o PID do container no host
sudo nsenter -t 984281 -n ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
34: eth0@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
Se deletarmos o container o que vai acontecer? A interface no host veth85a0078@if34 irá sumir?
docker container rm 3fd2e7fde3934b0fbcb81e944c9081835e43983bf708e128f8f432dde346b845 --force
3fd2e7fde3934b0fbcb81e944c9081835e43983bf708e128f8f432dde346b845
ip addr | grep docker
8: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
E cada container que for criando irá formando o peer.
Todos esses containers se enxergam pois fazem parte da mesma network default do docker.
Vamos fazer um teste?
~ docker run -d --name=nginx nginx
785d4f5fc60214c5cea30d9197b5be3da07aa6c49c67d8b5c6cc88635eaa1fef
docker inspect 785d4 | grep "IPAddress"
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.2",
"IPAddress": "172.17.0.2",
docker run -it --name ubuntu ubuntu bash
root@f1138371fc83:/# apt update; apt install iputils-ping -y
oot@f1138371fc83:/# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.120 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.044 ms
...
O host somente enxerga se apontar no ip correto, pois ele faz parte da rede 172.17.0.0/16 do docker. Qualquer outra máquina não irá enxergar.
Precisamos fazer um portmap através do iptables para que uma porta do host aponte para a porta do container. Reveja network namespaces.
Para fazer isso de forma fácil usamos o -p para mapear a porta do host para a porta do container. Isso irá gerar uma regra em iptables.
O -p 8080:80
fará esse comando para nós iptables -t nat -A PREROUNTING --dport 8080 --to-destination 172.17.0.2:80 -j DNAT
docker run -d -p 8080:80 nginx
curl http://localhost:8080
<!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>
# Conferindo o iptables
sudo iptables -nvL -t nat | grep 8080
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80