Pular para o conteúdo principal

Network Namespaces

Network namespaces são usados pelos containers para implementar isolamento da rede.

Este é um estudo muito importante para entender como funciona a rede em containers. É a base para entender muita coisa no Docker e no Kubernetes.

Falando de processos, os containers são separados do host através de namespaces.

Se pensarmos em uma casa, cada filho tem o seu quarto. Isso garante que cada um tenha a privacidade no seu próprio quarto e somente pode bagunçar o seu espaço. Seria uma casa dentro da casa, mas os pais podem conferir os quartos.

Quando criamos um container estamos garantindo que os processos desse container não são capazes de enxergar os processos do host e de outros containers. O container pensa que está sozinho. O host consegue ver os processos de todos os containers.

alt text

Se observou na imagem acima, o container vê o processo que mantém o container vivo como PID 1, mas o host vê este processo com PID 3816.

Namespaces são um isolamento criado pelo Linux para "enganar" o container.

Quando um container é criado um namespace de rede é criado no host para isolar as informações de rede do host e do container. Quem faz esse trabalho? O container runtime. Vamos tentar fazer por nós mesmo e colocar isso na prática.

alt text

Criando Networks Namespaces

Em uma máquina virtual ubuntu, vamos brincar. Instale o pacote net-tools. Vamos criar 4 namespaces para seguir exemplo do estudo.

# Criando o namespace de redes
ubuntu@ubuntu:~$ sudo ip netns add red
ubuntu@ubuntu:~$ sudo ip netns add blue
ubuntu@ubuntu:~$ sudo ip netns add orange
ubuntu@ubuntu:~$ sudo ip netns add gray
ubuntu@ubuntu:~$ sudo ip netns
gray
orange
blue
red

# Testando as interfaces que aparecem em cada namespace
# HOST
ubuntu@ubuntu:~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:bd:d2:80 brd ff:ff:ff:ff:ff:ff

# NAMESPACES
# Nos namespaces red temos a interface loopback não tendo acesso às interfaces do host
ubuntu@ubuntu:~$ sudo ip -n red link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
ubuntu@ubuntu:~$ sudo ip -n blue link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
ubuntu@ubuntu:~$ sudo ip -n orange link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
ubuntu@ubuntu:~$ sudo ip -n gray link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00


# O mesmo vale para a tabela arp
# HOST
ubuntu@ubuntu:~$ arp
Address HWtype HWaddress Flags Mask Iface
_gateway ether 52:55:0a:00:02:02 C enp1s0
10.0.2.3 ether 52:55:0a:00:02:03 C enp1s0

# NAMESPACES
# Nenhuma saída para os namespaces
ubuntu@ubuntu:~$ sudo ip netns exec red arp
ubuntu@ubuntu:~$ sudo ip netns exec blue arp
ubuntu@ubuntu:~$ sudo ip netns exec orange arp
ubuntu@ubuntu:~$ sudo ip netns exec gray arp

# O mesmo para tabelas de rota
# HOST
ubuntu@ubuntu:~$ route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default _gateway 0.0.0.0 UG 100 0 0 enp1s0
10.0.2.0 0.0.0.0 255.255.255.0 U 100 0 0 enp1s0
link-local 0.0.0.0 255.255.0.0 U 1000 0 0 enp1s0

# NAMESPACES
ubuntu@ubuntu:~$ sudo ip netns exec red route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
ubuntu@ubuntu:~$ sudo ip netns exec blue route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
ubuntu@ubuntu:~$ sudo ip netns exec orange route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
ubuntu@ubuntu:~$ sudo ip netns exec gray route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface

Então já vimos um pouco do uso de namespaces e qual a ideia por trás disso, tanto em processos quanto network. Agora vamos por a mão na massa.

Conectando dois namespaces

Com 4 namespaces podemos então tentar conectar dois namespaces red e blue para testar. Agora vamos entrar no mundo virtual.

A proposta será esta.

conect

Do mesmo modo que conseguimos conectar duas máquinas com cabos, podemos criar os nosso cabos virtuais entre os namespaces. Um cabo esta linkado em um interface, então precisamos criar as interfaces.

Vamos criar as interfaces virtuais no host e então coloca-las disponíveis nos namespaces.

# Criando o nosso cabo virtual entre uma interface e outra
ubuntu@ubuntu:~$ sudo ip link add veth-red type veth peer name veth-blue

ubuntu@ubuntu:~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:bd:d2:80 brd ff:ff:ff:ff:ff:ff
5: veth-blue@veth-red: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether be:5c:26:df:91:c2 brd ff:ff:ff:ff:ff:ff
6: veth-red@veth-blue: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether f6:2e:b7:78:83:d0 brd ff:ff:ff:ff:ff:ff

# Agora vamos colocar essas interfaces em cada ns, pois no momento ainda esta no host
ubuntu@ubuntu:~$ sudo ip link set veth-red netns red
ubuntu@ubuntu:~$ sudo ip link set veth-blue netns blue

# CONFERINDO... Observe que as interfaces estão nos namespaces
sudo ip -n red link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
4: veth-red@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1000
link/ether f6:2e:b7:78:83:d0 brd ff:ff:ff:ff:ff:ff link-netns blue

ubuntu@ubuntu:~$ sudo ip -n blue link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: veth-blue@if4: <BROADCAST,MULTICAST> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1000
link/ether be:5c:26:df:91:c2 brd ff:ff:ff:ff:ff:ff link-netns red

# CONFERINDO...Podemos vê-las no host? Nao.
ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:bd:d2:80 brd ff:ff:ff:ff:ff:ff

# Essas interfaces não tem ips, então vamos colocar ips nelas de modo que estejam na mesma rede
ubuntu@ubuntu:~$ sudo ip -n red addr add 192.168.15.1/24 dev veth-red
ubuntu@ubuntu:~$ sudo ip -n blue addr add 192.168.15.2/24 dev veth-blue

# CONFERINDO
sudo ip netns exec red ip addr show dev veth-red
4: veth-red@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether f6:2e:b7:78:83:d0 brd ff:ff:ff:ff:ff:ff link-netns blue
inet 192.168.15.1/24 scope global veth-red
valid_lft forever preferred_lft forever
inet6 fe80::f42e:b7ff:fe78:83d0/64 scope link
valid_lft forever preferred_lft forever
sudo ip netns exec blue ip addr show dev veth-blue
3: veth-blue@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether be:5c:26:df:91:c2 brd ff:ff:ff:ff:ff:ff link-netns red
inet 192.168.15.2/24 scope global veth-blue
valid_lft forever preferred_lft forever
inet6 fe80::bc5c:26ff:fedf:91c2/64 scope link
valid_lft forever preferred_lft forever


# Vamos subir essas interfaces pois estavam down.
ubuntu@ubuntu:~$ sudo ip -n red link set veth-red up
ubuntu@ubuntu:~$ sudo ip -n blue link set veth-blue up

# E pingar para ver se funciona

ubuntu@ubuntu:~$ sudo ip netns exec red ping 192.168.15.2
PING 192.168.15.2 (192.168.15.2) 56(84) bytes of data.
64 bytes from 192.168.15.2: icmp_seq=1 ttl=64 time=0.065 ms
...
ubuntu@ubuntu:~$ sudo ip netns exec blue ping 192.168.15.1
64 bytes from 192.168.15.1: icmp_seq=1 ttl=64 time=0.027 ms
...

Agora vamos conferir as tabelas arp e e as rotas

ubuntu@ubuntu:~$ sudo ip netns exec red arp
Address HWtype HWaddress Flags Mask Iface
192.168.15.2 ether be:5c:26:df:91:c2 C veth-red
ubuntu@ubuntu:~$ sudo ip netns exec blue arp
Address HWtype HWaddress Flags Mask Iface
192.168.15.1 ether f6:2e:b7:78:83:d0 C veth-blue
# O HOST NÃO CONHECE, OU SEJA, COMPLETAMENTE ISOLADO
ubuntu@ubuntu:~$ arp
Address HWtype HWaddress Flags Mask Iface
_gateway ether 52:55:0a:00:02:02 C enp1s0
10.0.2.3 ether 52:55:0a:00:02:03 C enp1s0

# O MESMO VALE PARA AS ROTAS
ubuntu@ubuntu:~$ sudo ip netns exec red route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.15.0 0.0.0.0 255.255.255.0 U 0 0 0 veth-red
ubuntu@ubuntu:~$ sudo ip netns exec blue route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.15.0 0.0.0.0 255.255.255.0 U 0 0 0 veth-blue

Quando você deleta um namespace você deleta todos os recursos que nele estão.

Só para dar uma reiniciada para o próximo exemplo.

ubuntu@ubuntu:~$ sudo ip netns delete red
ubuntu@ubuntu:~$ sudo ip netns delete blue
ubuntu@ubuntu:~$ sudo ip netns add red
ubuntu@ubuntu:~$ sudo ip netns add blue

Conectando vários namespaces

Para dois namespaces fica fácil, mas e quando queremos ter vários namespaces como iremos fazer no Kubernetes.

Assim como criamos os cabos virtuais, podemos criar switches virtuais e conectar o namespace inteiro nesse switch. Criar um switch é a mesma coisa que criar uma rede virtual dentro do host uma vez que switches conectam dispositivos na mesma rede.

Existem várias soluções disponíveis criar essa network dentro do host. Temos por exemplo o Linux Bridge e o OvS (Open vSwitch). Vamos usar o Linux Bridge.

A primeira coisa que devemos fazer é criar uma interface de rede no host, como qualquer outra, mas sendo do tipo bridge.

ubuntu@ubuntu:~$ sudo ip link add v-net-0 type bridge
ubuntu@ubuntu:~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:bd:d2:80 brd ff:ff:ff:ff:ff:ff
7: v-net-0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 6e:33:35:a2:97:5c brd ff:ff:ff:ff:ff:ff
sudo ip link set dev v-net-0 up

Vamos agora criar um par de cabos virtuais para cada namespace sendo que uma ponta ficará dentro do namespace e sua outra ponta dentro do host que atuará como um switch.

# Criando os pares
ubuntu@ubuntu:~$ sudo ip link add veth-red type veth peer name veth-red-br
ubuntu@ubuntu:~$ sudo ip link add veth-blue type veth peer name veth-blue-br
ubuntu@ubuntu:~$ sudo ip link add veth-orange type veth peer name veth-orange-br
ubuntu@ubuntu:~$ sudo ip link add veth-gray type veth peer name veth-gray-br

# Movendo a ponta de um par para dentro de cada namespace
ubuntu@ubuntu:~$ sudo ip link set veth-red netns red
ubuntu@ubuntu:~$ sudo ip link set veth-blue netns blue
ubuntu@ubuntu:~$ sudo ip link set veth-orange netns orange
ubuntu@ubuntu:~$ sudo ip link set veth-gray netns gray

# Ativando todas as interfaces que foram para os namespaces e colocando os ips e as máscaras
# As que foram para os namespaces e colocando os ips
ubuntu@ubuntu:~$ sudo ip -n red link set veth-red up
ubuntu@ubuntu:~$ sudo ip -n blue link set veth-blue up
ubuntu@ubuntu:~$ sudo ip -n orange link set veth-orange up
ubuntu@ubuntu:~$ sudo ip -n gray link set veth-gray up

# Setando os ips das das interfaces dentro do namespaces
ubuntu@ubuntu:~$ sudo ip -n red addr add 192.168.15.1/24 dev veth-red
ubuntu@ubuntu:~$ sudo ip -n blue addr add 192.168.15.2/24 dev veth-blue
ubuntu@ubuntu:~$ sudo ip -n orange addr add 192.168.15.3/24 dev veth-orange
ubuntu@ubuntu:~$ sudo ip -n gray addr add 192.168.15.4/24 dev veth-gray

# As que permanecem no host
ubuntu@ubuntu:~$ sudo ip link set veth-red-br up
ubuntu@ubuntu:~$ sudo ip link set veth-blue-br up
ubuntu@ubuntu:~$ sudo ip link set veth-orange-br up
ubuntu@ubuntu:~$ sudo ip link set veth-gray-br up

# É aqui que acontece a mágica a junção de todo mundo.
ubuntu@ubuntu:~$ sudo ip link set veth-red-br master v-net-0
ubuntu@ubuntu:~$ sudo ip link set veth-blue-br master v-net-0
ubuntu@ubuntu:~$ sudo ip link set veth-orange-br master v-net-0
ubuntu@ubuntu:~$ sudo ip link set veth-gray-br master v-net-0

# E por fim precisamos que a v-net-0 tenha um ip da rede no prório host
ubuntu@ubuntu:~$ sudo ip addr add 192.168.15.5/24 dev v-net-0

Essa rede ainda continua privada não tem acesso fora do host. O namespace red nunca conseguirá se conectar com outras interfaces fora do seu da sua rede. Para isso é necessário definir um gateway.

O nosso host é quem tem acesso a ambas as redes então ele serviria como o nosso roteador.

Conectando namespaces com o host

Apesar das redes do namespaces se comunicarem elas não se comunicam com a rede do host se não definir um gateway

ubuntu@ubuntu:~$ ip addr show
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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:bd:d2:80 brd ff:ff:ff:ff:ff:ff`
#IP DO HOST
inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute enp1s0
valid_lft 71671sec preferred_lft 71671sec
inet6 fec0::4b95:b6e6:2660:9d70/64 scope site temporary dynamic
valid_lft 86341sec preferred_lft 14341sec
inet6 fec0::5054:ff:febd:d280/64 scope site dynamic mngtmpaddr
valid_lft 86341sec preferred_lft 14341sec
inet6 fe80::5054:ff:febd:d280/64 scope link
valid_lft forever preferred_lft forever
7: v-net-0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 6e:33:35:a2:97:5c brd ff:ff:ff:ff:ff:ff
inet 192.168.15.5/24 scope global v-net-0
valid_lft forever preferred_lft forever
inet6 fe80::6c33:35ff:fea2:975c/64 scope link
valid_lft forever preferred_lft forever
8: veth-red-br@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master v-net-0 state UP group default qlen 1000
link/ether da:96:b7:3f:34:43 brd ff:ff:ff:ff:ff:ff link-netns red
inet6 fe80::d896:b7ff:fe3f:3443/64 scope link
valid_lft forever preferred_lft forever
10: veth-blue-br@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master v-net-0 state UP group default qlen 1000
link/ether 4a:cb:ef:ec:9b:26 brd ff:ff:ff:ff:ff:ff link-netns blue
inet6 fe80::48cb:efff:feec:9b26/64 scope link
valid_lft forever preferred_lft forever
12: veth-orange-br@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master v-net-0 state UP group default qlen 1000
link/ether e6:d6:7a:e9:14:82 brd ff:ff:ff:ff:ff:ff link-netns orange
inet6 fe80::e4d6:7aff:fee9:1482/64 scope link
valid_lft forever preferred_lft forever
14: veth-gray-br@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master v-net-0 state UP group default qlen 1000
link/ether b6:e8:a4:01:63:d7 brd ff:ff:ff:ff:ff:ff link-netns gray
inet6 fe80::b4e8:a4ff:fe01:63d7/64 scope link
valid_lft forever preferred_lft forever

#TENTATIVA DE PINGAR NO HOST
ubuntu@ubuntu:~$ sudo ip netns exec red ping 10.0.2.15
ping: connect: Network is unreachable

Vamos então definir rotas para que o host seja alcançável.

# Definimos que se quisemos ir para a rede 10.0.2.0/24 teremos que ir pelo ip da v-net-0
ubuntu@ubuntu:~$ sudo ip netns exec red ip route add 10.0.2.0/24 via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec red ping 10.0.2.15
PING 10.0.2.15 (10.0.2.15) 56(84) bytes of data.
64 bytes from 10.0.2.15: icmp_seq=1 ttl=64 time=0.130 ms
64 bytes from 10.0.2.15: icmp_seq=2 ttl=64 time=0.035 ms

#Então vamos continuar e fazer isso pra todo mundo
ubuntu@ubuntu:~$ sudo ip netns exec blue ip route add 10.0.2.0/24 via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec orange ip route add 10.0.2.0/24 via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec gray ip route add 10.0.2.0/24 via 192.168.15.5

# Vamos adicionar também as rotas para sair para intenet
ubuntu@ubuntu:~$ sudo ip netns exec red ip route add default via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec blue ip route add default via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec orange ip route add default via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec gray ip route add default via 192.168.15.5

Usando o host como NAT

Mas seria possível sair para a Internet e pingar o google uma vez que o host tem acesso a Internet? Não, ainda não seria possível. Também não seria possível acessar outras máquinas na mesma rede do host.

Os pacotes vão sair do namespace com um cabeçalho com o ip de origem e precisamos transformá-los como se tivesse vindo do host e não de uma rede dentro do host. Para isso precisamos mascarar esses pacotes usando o iptables.

iptables -t nat -A POSTROUTING -s 192.168.15.0*24 -j MASQUERADE

Conectando de fora para dentro de um namespace

Se tivéssemos algum serviço rodando em um namespace como que ele conseguiria ser alcançável? Vamos imaginar um serviço que esteja usando a porta 80 do container. Na verdade ele é alcançável através do host, mas mapeado para dentro do namespace.

iptables -t nat -A PREROUNTING --dport 80 --to-destination 192.168.15.1:80 -j DNAT

Se observou bem, é exatamente como o cluster ip funciona no kubernetes.

Se quisessem mapear a porta 8080 do host para enviar para a porta 80 do container seria.

iptables -t nat -A PREROUNTING --dport 80 --to-destination 192.168.15.1:80 -j DNAT