Redes de bastidores no Kubernetes

Nota perev. : O autor do artigo original, Nicolas Leiva, é um arquiteto de soluções da Cisco que decidiu compartilhar com seus colegas, engenheiros de rede, como a rede Kubernetes funciona por dentro. Para fazer isso, ele explora sua configuração mais simples no cluster, usando ativamente o bom senso, seu conhecimento de redes e utilitários padrão do Linux / Kubernetes. Ficou volumoso, mas muito claramente.



Além do fato de o guia Kubernetes The Hard Way de Kelsey Hightower funcionar ( até na AWS! ), Eu gostei que a rede fosse mantida limpa e simples; e esta é uma ótima oportunidade para entender o papel, por exemplo, da CNI (Container Network Interface). Dito isto, acrescentarei que a rede Kubernetes não é realmente muito intuitiva, especialmente para iniciantes ... e também não se esqueça de que “ simplesmente não existe uma rede de contêineres”.

Embora já existam bons materiais sobre esse tópico (veja os links aqui ), não consegui encontrar um exemplo que combinasse tudo o necessário com as conclusões das equipes que os engenheiros de rede amam e odeiam, demonstrando o que realmente está acontecendo nos bastidores. Portanto, decidi coletar informações de várias fontes - espero que isso ajude e você entenda melhor como tudo está conectado. Esse conhecimento é importante não apenas para testar a si mesmo, mas também para simplificar o processo de diagnóstico de problemas. Você pode seguir o exemplo em seu cluster no Kubernetes The Hard Way : todos os endereços IP e configurações são obtidos de lá (a partir das confirmações de maio de 2018, antes de usar os contêineres Nabla ).

E começaremos do final, quando tivermos três controladores e três nós de trabalho:



Você pode perceber que também há pelo menos três sub-redes privadas aqui! Um pouco de paciência, e todos serão considerados. Lembre-se de que, apesar de nos referirmos a prefixos IP muito específicos, eles são simplesmente retirados do Kubernetes The Hard Way , portanto, eles têm apenas significado local e você pode escolher qualquer outro bloco de endereço para o seu ambiente, de acordo com a RFC 1918 . Para o caso do IPv6, haverá um artigo de blog separado.

Rede do host (10.240.0.0/24)


Essa é uma rede interna da qual todos os nós fazem parte. Definido pelo --private-network-ip no GCP ou pela opção --private-ip-address na AWS ao alocar recursos de computação.

Inicializando nós do controlador no GCP


 for i in 0 1 2; do gcloud compute instances create controller-${i} \ # ... --private-network-ip 10.240.0.1${i} \ # ... done 

( controllers_gcp.sh )

Inicializando nós do controlador na AWS


 for i in 0 1 2; do declare controller_id${i}=`aws ec2 run-instances \ # ... --private-ip-address 10.240.0.1${i} \ # ... done 

( controllers_aws.sh )



Cada instância terá dois endereços IP: privado da rede host (controladores - 10.240.0.1${i}/24 , trabalhadores - 10.240.0.2${i}/24 ) e um público, nomeado pelo provedor de nuvem, sobre o qual falaremos mais adiante como chegar ao NodePorts .

Gcp


 $ gcloud compute instances list NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS controller-0 us-west1-c n1-standard-1 10.240.0.10 35.231.XXX.XXX RUNNING worker-1 us-west1-c n1-standard-1 10.240.0.21 35.231.XX.XXX RUNNING ... 


Aws


 $ aws ec2 describe-instances --query 'Reservations[].Instances[].[Tags[?Key==`Name`].Value[],PrivateIpAddress,PublicIpAddress]' --output text | sed '$!N;s/\n/ /' 10.240.0.10 34.228.XX.XXX controller-0 10.240.0.21 34.173.XXX.XX worker-1 ... 

Todos os nós devem poder executar ping se as políticas de segurança estiverem corretas (e se o ping instalado no host).

Rede da lareira (10.200.0.0/16)


Essa é a rede na qual os pods vivem. Cada nó de trabalho usa uma sub-rede desta rede. No nosso caso, POD_CIDR=10.200.${i}.0/24 para o worker-${i} .



Para entender como tudo está configurado, dê um passo para trás e observe o modelo de rede Kubernetes , que requer o seguinte:

  • Todos os contêineres podem se comunicar com outros contêineres sem usar o NAT.
  • Todos os nós podem se comunicar com todos os contêineres (e vice-versa) sem usar o NAT.
  • O IP que o contêiner vê deve ser o mesmo que os outros o veem.

Tudo isso pode ser implementado de várias maneiras, e o Kubernetes passa a configuração de rede para o plugin CNI .

“O plugin CNI é responsável por adicionar uma interface de rede ao namespace de rede do contêiner (por exemplo, uma extremidade de um par veth ) e fazer as alterações necessárias no host (por exemplo, conectar a segunda extremidade do veth a uma ponte). Depois, ele deve atribuir uma interface IP e configurar as rotas de acordo com a seção Gerenciamento de endereço IP, chamando o plug-in IPAM desejado. ” (da especificação de interface de rede de contêiner )



Namespace de rede


“O espaço para nome agrupa o recurso global do sistema em uma abstração que é visível aos processos nesse espaço para nome, de forma que eles tenham sua própria instância isolada do recurso global. Alterações no recurso global são visíveis para outros processos incluídos neste espaço para nome, mas não são visíveis para outros processos. ” ( na página do manual namespaces )

O Linux fornece sete namespaces diferentes ( Cgroup , IPC , Network , Mount , PID , User , UTS ). Os espaços para nome da rede ( CLONE_NEWNET ) definem os recursos de rede disponíveis para o processo: "Cada espaço para nome da rede possui seus próprios dispositivos de rede, endereços IP, tabelas de roteamento IP, diretório /proc/net , números de porta e assim por diante" ( do artigo “ Namespaces in operation ”) .

Dispositivos Ethernet virtuais (Veth)


“Um par de rede virtual (veth) oferece uma abstração na forma de um“ canal ”, que pode ser usado para criar túneis entre namespaces de rede ou para criar uma ponte para um dispositivo de rede físico em outro espaço de rede. Quando o espaço para nome é liberado, todos os dispositivos veth nele são destruídos. ” (na página do manual namespaces de rede )

Vá para o chão e veja como tudo isso se relaciona com o cluster. Primeiro, os plugins de rede no Kubernetes são diversos, e os plugins CNI são um deles ( por que não o CNM? ). O Kubelet em cada nó informa ao tempo de execução do contêiner qual plug-in de rede usar. A interface de rede de contêiner ( CNI ) está entre o tempo de execução do contêiner e a implementação da rede. E o plugin CNI já configura a rede.

“O plugin CNI é selecionado passando a --network-plugin=cni linha de comando --network-plugin=cni cni para o Kubelet. O Kubelet lê o arquivo em --cni-conf-dir (o padrão é /etc/cni/net.d ) e usa a configuração CNI deste arquivo para configurar a rede para cada arquivo. " (a partir de Requisitos de plug-in de rede )

Os binários reais do plugin CNI estão em -- cni-bin-dir (o padrão é /opt/cni/bin ).

Observe que os kubelet.service chamada --network-plugin=cni incluem --network-plugin=cni :

 [Service] ExecStart=/usr/local/bin/kubelet \\ --config=/var/lib/kubelet/kubelet-config.yaml \\ --network-plugin=cni \\ ... 

Primeiro de tudo, o Kubernetes cria um espaço para nome de rede para a lareira, mesmo antes de chamar qualquer plug-in. Isso é implementado usando o contêiner de pause especial, que "serve como o" contêiner pai "para todos os contêineres da lareira" (do artigo " O Contêiner Todo-Poderoso para Pausa ") . O Kubernetes então executa o plugin CNI para anexar o contêiner de pause à rede. Todos os contêineres de pod usam o netns desse contêiner de pause .

 { "cniVersion": "0.3.1", "name": "bridge", "type": "bridge", "bridge": "cnio0", "isGateway": true, "ipMasq": true, "ipam": { "type": "host-local", "ranges": [ [{"subnet": "${POD_CIDR}"}] ], "routes": [{"dst": "0.0.0.0/0"}] } } 

A configuração CNI usada indica o uso do plug-in de bridge para configurar a ponte do software Linux (L2) no espaço de nomes raiz chamado cnio0 (o nome padrão é cni0 ), que atua como um gateway ( "isGateway": true ).



Um par veth também será configurado para conectar a lareira à ponte recém-criada:



Para atribuir informações L3, como endereços IP, o plug - in IPAM ( ipam ) é chamado. Nesse caso, o tipo host-local é usado ", que armazena o estado localmente no sistema de arquivos host, o que garante a exclusividade dos endereços IP em um host" (a partir da host-local ) . O plug-in IPAM retorna essas informações para o plug-in anterior ( bridge ), para que todas as rotas especificadas na configuração possam ser configuradas ( "routes": [{"dst": "0.0.0.0/0"}] ). Se gw não gw especificado, ele será retirado da sub-rede . A rota padrão também é configurada no espaço para nome da rede da lareira, apontando para a ponte (que é configurada como a primeira sub-rede IP da lareira).

E o último detalhe importante: solicitamos o mascaramento ( "ipMasq": true ) para o tráfego proveniente da rede da lareira. Realmente não precisamos de NAT aqui, mas essa é a configuração do Kubernetes The Hard Way . Portanto, para ser completo, devo mencionar que as entradas nas iptables plug-in de bridge estão configuradas para este exemplo específico. Todos os pacotes da lareira, cujo destinatário não está no intervalo 224.0.0.0/4 , estarão atrás do NAT , que não atende exatamente ao requisito "todos os contêineres podem se comunicar com outros contêineres sem usar o NAT". Bem, provaremos por que o NAT não é necessário ...



Roteamento da lareira


Agora estamos prontos para personalizar os pods. Vamos examinar todos os espaços de rede dos nomes de um dos nós de trabalho e analisar um deles depois de criar a implantação do nginx partir daqui . Usaremos lsns com a opção -t para selecionar o tipo de espaço para nome desejado (ou seja, net ):

 ubuntu@worker-0:~$ sudo lsns -t net NS TYPE NPROCS PID USER COMMAND 4026532089 net 113 1 root /sbin/init 4026532280 net 2 8046 root /pause 4026532352 net 4 16455 root /pause 4026532426 net 3 27255 root /pause 

Usando a opção -i para ls , podemos encontrar seus números de inode:

 ubuntu@worker-0:~$ ls -1i /var/run/netns 4026532352 cni-1d85bb0c-7c61-fd9f-2adc-f6e98f7a58af 4026532280 cni-7cec0838-f50c-416a-3b45-628a4237c55c 4026532426 cni-912bcc63-712d-1c84-89a7-9e10510808a0 

Você também pode listar todos os namespaces de rede usando ip netns :

 ubuntu@worker-0:~$ ip netns cni-912bcc63-712d-1c84-89a7-9e10510808a0 (id: 2) cni-1d85bb0c-7c61-fd9f-2adc-f6e98f7a58af (id: 1) cni-7cec0838-f50c-416a-3b45-628a4237c55c (id: 0) 

Para ver todos os processos em execução no espaço de rede cni-912bcc63–712d-1c84–89a7–9e10510808a0 ( 4026532426 ), é possível executar, por exemplo, o seguinte comando:

 ubuntu@worker-0:~$ sudo ls -l /proc/[1-9]*/ns/net | grep 4026532426 | cut -f3 -d"/" | xargs ps -p PID TTY STAT TIME COMMAND 27255 ? Ss 0:00 /pause 27331 ? Ss 0:00 nginx: master process nginx -g daemon off; 27355 ? S 0:00 nginx: worker process 

Pode-se observar que, além da pause neste pod, lançamos o nginx . O contêiner de pause compartilha os namespaces net e ipc com todos os outros contêineres de pod. Lembre-se do PID da pause - 27255; nós retornaremos a ele.

Agora vamos ver o que o kubectl diz sobre este pod:

 $ kubectl get pods -o wide | grep nginx nginx-65899c769f-wxdx6 1/1 Running 0 5d 10.200.0.4 worker-0 

Mais detalhes:

 $ kubectl describe pods nginx-65899c769f-wxdx6 

 Name: nginx-65899c769f-wxdx6 Namespace: default Node: worker-0/10.240.0.20 Start Time: Thu, 05 Jul 2018 14:20:06 -0400 Labels: pod-template-hash=2145573259 run=nginx Annotations: <none> Status: Running IP: 10.200.0.4 Controlled By: ReplicaSet/nginx-65899c769f Containers: nginx: Container ID: containerd://4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7 Image: nginx ... 

Vemos o nome do pod - nginx-65899c769f-wxdx6 - e o ID de um de seus contêineres ( nginx ), mas nada foi dito sobre a pause . Cavar um nó de trabalho mais profundo para corresponder a todos os dados. Lembre-se de que o Kubernetes The Hard Way não usa o Docker , portanto, para obter detalhes sobre o contêiner, consultamos o utilitário console consoleerderd-ctr (consulte também o artigo " Integração do containererd com o Kubernetes, substituindo o Docker, pronto para produção " - aprox. Transfer ) :

 ubuntu@worker-0:~$ sudo ctr namespaces ls NAME LABELS k8s.io 

Conhecendo o k8s.io ( k8s.io ), é possível obter o ID do contêiner nginx :

 ubuntu@worker-0:~$ sudo ctr -n k8s.io containers ls | grep nginx 4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7 docker.io/library/nginx:latest io.containerd.runtime.v1.linux 

... e faça uma pause também:

 ubuntu@worker-0:~$ sudo ctr -n k8s.io containers ls | grep pause 0866803b612f2f55e7b6b83836bde09bd6530246239b7bde1e49c04c7038e43a k8s.gcr.io/pause:3.1 io.containerd.runtime.v1.linux 21640aea0210b320fd637c22ff93b7e21473178de0073b05de83f3b116fc8834 k8s.gcr.io/pause:3.1 io.containerd.runtime.v1.linux d19b1b1c92f7cc90764d4f385e8935d121bca66ba8982bae65baff1bc2841da6 k8s.gcr.io/pause:3.1 io.containerd.runtime.v1.linux 

O ID do contêiner nginx que termina em …983c7 corresponde ao que obtivemos do kubectl . Vamos ver se conseguimos descobrir qual contêiner de pause pertence ao pod do nginx :

 ubuntu@worker-0:~$ sudo ctr -n k8s.io task ls TASK PID STATUS ... d19b1b1c92f7cc90764d4f385e8935d121bca66ba8982bae65baff1bc2841da6 27255 RUNNING 4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7 27331 RUNNING 

Lembre-se de que os processos com o PID 27331 e 27355 estão em execução no espaço para nome da rede cni-912bcc63–712d-1c84–89a7–9e10510808a0 ?

 ubuntu@worker-0:~$ sudo ctr -n k8s.io containers info d19b1b1c92f7cc90764d4f385e8935d121bca66ba8982bae65baff1bc2841da6 { "ID": "d19b1b1c92f7cc90764d4f385e8935d121bca66ba8982bae65baff1bc2841da6", "Labels": { "io.cri-containerd.kind": "sandbox", "io.kubernetes.pod.name": "nginx-65899c769f-wxdx6", "io.kubernetes.pod.namespace": "default", "io.kubernetes.pod.uid": "0b35e956-8080-11e8-8aa9-0a12b8818382", "pod-template-hash": "2145573259", "run": "nginx" }, "Image": "k8s.gcr.io/pause:3.1", ... 

... e:

 ubuntu@worker-0:~$ sudo ctr -n k8s.io containers info 4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7 { "ID": "4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7", "Labels": { "io.cri-containerd.kind": "container", "io.kubernetes.container.name": "nginx", "io.kubernetes.pod.name": "nginx-65899c769f-wxdx6", "io.kubernetes.pod.namespace": "default", "io.kubernetes.pod.uid": "0b35e956-8080-11e8-8aa9-0a12b8818382" }, "Image": "docker.io/library/nginx:latest", ... 

Agora sabemos com certeza quais contêineres estão sendo executados neste pod ( nginx-65899c769f-wxdx6 ) e o espaço para nome da rede ( cni-912bcc63–712d-1c84–89a7–9e10510808a0 ):

  • nginx (ID: 4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7 );
  • pausa (ID: d19b1b1c92f7cc90764d4f385e8935d121bca66ba8982bae65baff1bc2841da6 ).



Como isso está ( nginx-65899c769f-wxdx6 ) conectado à rede? Usamos o PID 27255 recebido anteriormente da pause para executar comandos em seu namespace de rede ( cni-912bcc63–712d-1c84–89a7–9e10510808a0 ):

 ubuntu@worker-0:~$ sudo ip netns identify 27255 cni-912bcc63-712d-1c84-89a7-9e10510808a0 

Para esses fins, usaremos o nsenter com a opção -t que define o PID de destino e -n sem especificar um arquivo para entrar no espaço de nomes de rede do processo de destino (27255). Aqui está o que o ip link show dirá:

 ubuntu@worker-0:~$ sudo nsenter -t 27255 -n ip link show 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 3: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default link/ether 0a:58:0a:c8:00:04 brd ff:ff:ff:ff:ff:ff link-netnsid 0 

... e ifconfig eth0 :

 ubuntu@worker-0:~$ sudo nsenter -t 27255 -n ifconfig eth0 eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 10.200.0.4 netmask 255.255.255.0 broadcast 0.0.0.0 inet6 fe80::2097:51ff:fe39:ec21 prefixlen 64 scopeid 0x20<link> ether 0a:58:0a:c8:00:04 txqueuelen 0 (Ethernet) RX packets 540 bytes 42247 (42.2 KB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 177 bytes 16530 (16.5 KB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 

Isso confirma que o endereço IP obtido anteriormente pelo kubectl get pod está configurado na interface eth0 . Essa interface faz parte de um par veth , uma extremidade na lareira e a outra no espaço para nome raiz. Para descobrir a interface do segundo lado, usamos o ethtool :

 ubuntu@worker-0:~$ sudo ip netns exec cni-912bcc63-712d-1c84-89a7-9e10510808a0 ethtool -S eth0 NIC statistics: peer_ifindex: 7 

Vemos que o ifindex banquete é 7. Verifique se ele está no espaço para nome raiz. Isso pode ser feito usando o ip link :

 ubuntu@worker-0:~$ ip link | grep '^7:' 7: veth71f7d238@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cnio0 state UP mode DEFAULT group default 

Para ter certeza disso, finalmente, vamos ver:

 ubuntu@worker-0:~$ sudo cat /sys/class/net/veth71f7d238/ifindex 7 

Ótimo, agora tudo está claro com o link virtual. Usando brctl vamos ver quem mais está conectado à ponte Linux:

 ubuntu@worker-0:~$ brctl show cnio0 bridge name bridge id STP enabled interfaces cnio0 8000.0a580ac80001 no veth71f7d238 veth73f35410 vethf273b35f 

Então, a imagem é a seguinte:



Verificação de roteamento


Como realmente encaminhamos o tráfego? Vejamos a tabela de roteamento no pod de namespace da rede:

 ubuntu@worker-0:~$ sudo ip netns exec cni-912bcc63-712d-1c84-89a7-9e10510808a0 ip route show default via 10.200.0.1 dev eth0 10.200.0.0/24 dev eth0 proto kernel scope link src 10.200.0.4 

Pelo menos sabemos como chegar ao espaço para nome raiz ( default via 10.200.0.1 ). Agora vamos ver a tabela de roteamento de host:

 ubuntu@worker-0:~$ ip route list default via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.20 metric 100 10.200.0.0/24 dev cnio0 proto kernel scope link src 10.200.0.1 10.240.0.0/24 dev eth0 proto kernel scope link src 10.240.0.20 10.240.0.1 dev eth0 proto dhcp scope link src 10.240.0.20 metric 100 

Sabemos como encaminhar pacotes para um roteador VPC (o VPC possui um roteador "implícito", que geralmente possui um segundo endereço do espaço de endereço IP principal da sub-rede). Agora: o roteador VPC sabe como acessar a rede de cada lareira? Não, ele não, portanto, presume-se que as rotas serão configuradas pelo plug-in da CNI ou manualmente (como no manual). Aparentemente, o AWS CNI-plugin faz exatamente isso para nós na AWS. Lembre-se de que existem muitos plugins CNI e estamos considerando um exemplo de uma configuração de rede simples :



Imersão profunda no NAT


kubectl create -f busybox.yaml crie dois contêineres idênticos com o Replication Controller:

 apiVersion: v1 kind: ReplicationController metadata: name: busybox0 labels: app: busybox0 spec: replicas: 2 selector: app: busybox0 template: metadata: name: busybox0 labels: app: busybox0 spec: containers: - image: busybox command: - sleep - "3600" imagePullPolicy: IfNotPresent name: busybox restartPolicy: Always 

( busybox.yaml )

Temos:

 $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE busybox0-g6pww 1/1 Running 0 4s 10.200.1.15 worker-1 busybox0-rw89s 1/1 Running 0 4s 10.200.0.21 worker-0 ... 

Pings de um contêiner para outro devem ser bem-sucedidos:

 $ kubectl exec -it busybox0-rw89s -- ping -c 2 10.200.1.15 PING 10.200.1.15 (10.200.1.15): 56 data bytes 64 bytes from 10.200.1.15: seq=0 ttl=62 time=0.528 ms 64 bytes from 10.200.1.15: seq=1 ttl=62 time=0.440 ms --- 10.200.1.15 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.440/0.484/0.528 ms 

Para entender o movimento do tráfego, você pode ver os pacotes usando tcpdump ou conntrack :

 ubuntu@worker-0:~$ sudo conntrack -L | grep 10.200.1.15 icmp 1 29 src=10.200.0.21 dst=10.200.1.15 type=8 code=0 id=1280 src=10.200.1.15 dst=10.240.0.20 type=0 code=0 id=1280 mark=0 use=1 

O IP de origem do pod 10.200.0.21 é convertido no endereço IP do host 10.240.0.20.

 ubuntu@worker-1:~$ sudo conntrack -L | grep 10.200.1.15 icmp 1 28 src=10.240.0.20 dst=10.200.1.15 type=8 code=0 id=1280 src=10.200.1.15 dst=10.240.0.20 type=0 code=0 id=1280 mark=0 use=1 

No iptables, você pode ver que as contagens estão aumentando:

 ubuntu@worker-0:~$ sudo iptables -t nat -Z POSTROUTING -L -v Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination ... 5 324 CNI-be726a77f15ea47ff32947a3 all -- any any 10.200.0.0/24 anywhere /* name: "bridge" id: "631cab5de5565cc432a3beca0e2aece0cef9285482b11f3eb0b46c134e457854" */ Zeroing chain `POSTROUTING' 

Por outro lado, se você remover "ipMasq": true da configuração do plugin CNI, poderá ver o seguinte (esta operação é realizada exclusivamente para fins educacionais - não recomendamos alterar a configuração em um cluster ativo!):

 $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE busybox0-2btxn 1/1 Running 0 16s 10.200.0.15 worker-0 busybox0-dhpx8 1/1 Running 0 16s 10.200.1.13 worker-1 ... 

Ping ainda deve passar:

 $ kubectl exec -it busybox0-2btxn -- ping -c 2 10.200.1.13 PING 10.200.1.6 (10.200.1.6): 56 data bytes 64 bytes from 10.200.1.6: seq=0 ttl=62 time=0.515 ms 64 bytes from 10.200.1.6: seq=1 ttl=62 time=0.427 ms --- 10.200.1.6 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.427/0.471/0.515 ms 

E neste caso - sem usar o NAT:

 ubuntu@worker-0:~$ sudo conntrack -L | grep 10.200.1.13 icmp 1 29 src=10.200.0.15 dst=10.200.1.13 type=8 code=0 id=1792 src=10.200.1.13 dst=10.200.0.15 type=0 code=0 id=1792 mark=0 use=1 

Portanto, verificamos que "todos os contêineres podem se comunicar com outros contêineres sem usar o NAT".

 ubuntu@worker-1:~$ sudo conntrack -L | grep 10.200.1.13 icmp 1 27 src=10.200.0.15 dst=10.200.1.13 type=8 code=0 id=1792 src=10.200.1.13 dst=10.200.0.15 type=0 code=0 id=1792 mark=0 use=1 

Rede de cluster (10.32.0.0/24)


Você deve ter notado no exemplo do busybox que os endereços IP atribuídos ao busybox eram diferentes em cada caso. E se quiséssemos disponibilizar esses contêineres para comunicação de outros lares? Pode-se pegar os endereços IP atuais do pod, mas eles mudarão. Por esse motivo, você precisa configurar o recurso Service , que fará proxy de solicitações para muitos lares de vida curta.

"O serviço no Kubernetes é uma abstração que define o conjunto lógico de lareiras e as políticas pelas quais elas podem ser acessadas." (da documentação dos Serviços Kubernetes )

Existem várias maneiras de publicar um serviço; o tipo padrão é ClusterIP , que define o endereço IP do bloco CIDR do cluster (ou seja, acessível apenas a partir do cluster). Um exemplo é o complemento de cluster DNS configurado no Kubernetes The Hard Way.

 # ... apiVersion: v1 kind: Service metadata: name: kube-dns namespace: kube-system labels: k8s-app: kube-dns kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile kubernetes.io/name: "KubeDNS" spec: selector: k8s-app: kube-dns clusterIP: 10.32.0.10 ports: - name: dns port: 53 protocol: UDP - name: dns-tcp port: 53 protocol: TCP # ... 

( kube-dns.yaml )

kubectl mostra que o Service lembra dos pontos finais e os converte:

 $ kubectl -n kube-system describe services ... Selector: k8s-app=kube-dns Type: ClusterIP IP: 10.32.0.10 Port: dns 53/UDP TargetPort: 53/UDP Endpoints: 10.200.0.27:53 Port: dns-tcp 53/TCP TargetPort: 53/TCP Endpoints: 10.200.0.27:53 ... 

Como exatamente? .. iptables novamente. Vamos examinar as regras criadas para este exemplo. Sua lista completa pode ser vista com o comando iptables-save .

Assim que os pacotes são criados pelo processo ( OUTPUT ) ou chegam à interface de rede ( PREROUTING ), eles passam pelas seguintes cadeias de iptables :

 -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES -A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES 

Os seguintes destinos correspondem aos pacotes TCP enviados para a 53ª porta em 10.32.0.10 e são transmitidos ao destinatário 10.200.0.27 com a 53ª porta:

 -A KUBE-SERVICES -d 10.32.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-SVC-ERIFXISQEP7F7OF4 -A KUBE-SVC-ERIFXISQEP7F7OF4 -m comment --comment "kube-system/kube-dns:dns-tcp" -j KUBE-SEP-32LPCMGYG6ODGN3H -A KUBE-SEP-32LPCMGYG6ODGN3H -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp" -m tcp -j DNAT --to-destination 10.200.0.27:53 

O mesmo para pacotes UDP (destinatário 10.32.0.10:53 → 10.200.0.27:53):

 -A KUBE-SERVICES -d 10.32.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU -A KUBE-SVC-TCOU7JCQXEZGVUNU -m comment --comment "kube-system/kube-dns:dns" -j KUBE-SEP-LRUTK6XRXU43VLIG -A KUBE-SEP-LRUTK6XRXU43VLIG -p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 10.200.0.27:53 

Existem outros tipos de Services no Kubernetes. Em particular, o Kubernetes The Hard Way NodePort sobre o NodePort - consulte Smoke Test: Services .

 kubectl expose deployment nginx --port 80 --type NodePort 

NodePort publica o serviço no endereço IP de cada nó, colocando-o em uma porta estática (chamada NodePort ). NodePort pode ser acessado de fora do cluster. Você pode verificar a porta dedicada (neste caso - 31088) usando o kubectl :

 $ kubectl describe services nginx ... Type: NodePort IP: 10.32.0.53 Port: <unset> 80/TCP TargetPort: 80/TCP NodePort: <unset> 31088/TCP Endpoints: 10.200.1.18:80 ... 

Agora, Under está disponível na Internet como http://${EXTERNAL_IP}:31088/ . Aqui EXTERNAL_IP é o endereço IP público de qualquer instância de trabalho . Neste exemplo, usei o endereço IP público do worker-0 . A solicitação é recebida por um host com um endereço IP interno 10.240.0.20 (o provedor de nuvem está envolvido no NAT público); no entanto, o serviço é realmente iniciado em outro host ( worker-1 , que pode ser visto pelo endereço IP do terminal - 10.200.1.18):

 ubuntu@worker-0:~$ sudo conntrack -L | grep 31088 tcp 6 86397 ESTABLISHED src=173.38.XXX.XXX dst=10.240.0.20 sport=30303 dport=31088 src=10.200.1.18 dst=10.240.0.20 sport=80 dport=30303 [ASSURED] mark=0 use=1 

O pacote é enviado do trabalhador-0 para o trabalhador-1 , onde encontra seu destinatário:

 ubuntu@worker-1:~$ sudo conntrack -L | grep 80 tcp 6 86392 ESTABLISHED src=10.240.0.20 dst=10.200.1.18 sport=14802 dport=80 src=10.200.1.18 dst=10.240.0.20 sport=80 dport=14802 [ASSURED] mark=0 use=1 

Esse circuito é ideal? Talvez não, mas funciona. Nesse caso, as regras do iptables programadas são as seguintes:

 -A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx:" -m tcp --dport 31088 -j KUBE-SVC-4N57TFCL4MD7ZTDA -A KUBE-SVC-4N57TFCL4MD7ZTDA -m comment --comment "default/nginx:" -j KUBE-SEP-UGTFMET44DQG7H7H -A KUBE-SEP-UGTFMET44DQG7H7H -p tcp -m comment --comment "default/nginx:" -m tcp -j DNAT --to-destination 10.200.1.18:80 

Em outras palavras, o endereço para o destinatário dos pacotes com porta 31088 é transmitido em 10.200.1.18. A porta também está transmitindo, de 31088 a 80.

Não tocamos em outro tipo de serviço - o LoadBalancer - que disponibiliza publicamente o serviço usando um balanceador de carga do provedor de nuvem, mas o artigo já era grande.

Conclusão


Pode parecer que há muita informação, mas apenas tocamos a ponta do iceberg. No futuro, vou falar sobre IPv6, IPVS, eBPF e alguns plugins CNI atuais interessantes.

PS do tradutor


Leia também em nosso blog:

Source: https://habr.com/ru/post/pt420813/


All Articles