Investigação de fatores de latência de rede em um cluster Kubernetes
11
Junho
2019
Realizei testes para investigar a latência de rede do cluster Kubernetes da minha empresa.
Eu gostaria de responder algumas perguntas:
- Se eu acessar diretamente o nó que está rodando o pod, a latência será menor do que se eu acessar o mesmo serviço utilizando como bridge um segundo nó da rede?
- E se eu colocar um load balancer distribuindo a carga entre todos os nós da rede? Qual é o aumento na latência?
- E por último, qual é o aumento de latência que o serviço Ambassador (https://www.getambassador.io) adiciona aos requests?
Para responder essas e outras perguntas, rodei os seguintes testes:
- Acesso ao serviço diretamente pelo nó que está rodando o pod
- Acesso ao serviço utilizando um nó que não contém o pod como bridge
- Acesso via load balancer atrelado diretamente ao serviço
- Acesso via load balancer atrelado ao ambassador
- Acesso via load balancer atrelado ao ambassador com HTTPS
Para realizar esses testes, configurei um service e um deployment no meu cluster com a imagem nginxdemos/hello:plain-text (https://hub.docker.com/r/nginxdemos/hello/).
Teste 1) Acesso direto ao nó contendo o pod
Para realizar esse teste, depois de subir o deployment no namespace labs
, rodei os seguintes comandos:
$ kubectl get pods -n labs -o wide
NAME READY STATUS RESTARTS AGE IP NODE
# ... informações de outros pods omitidas ...
hello-nginx-plaintext-7bc68d7db-vlbb2 1/1 Running 0 14d 100.96.67.21 ip-172-20-58-134.ec2.internal
Rodei também o seguinte comando:
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
# ... informações de outros nós omitidas ...
ip-172-20-58-134.ec2.internal Ready node 18d v1.10.11 54.91.3.252 Debian GNU/Linux 8 (jessie) 4.4.121-k8s docker://17.3.2
Juntando as informações dos dois comandos acima, eu determinei que o IP externo do nó onde está rodando o pod hello-nginx-plaintext-7bc68d7db-zqjls é 54.91.3.252.
Para acessar diretamente o nó, sem intermediários, eu configurei o service com o tipo NodePort, na porta 30193:
apiVersion: v1
kind: Service
metadata:
name: hello-nginx-plaintext
namespace: labs
spec:
selector:
project: hello-nginx-plaintext
type: http-server
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30193
Depois de configurar o Security Groups da instância EC2 dentro do painel da AWS para permitir conexões Inbound na porta 30193, eu estava pronto para rodar o comando abaixo:
$ (for i in {1..30}; do time curl 54.91.3.252:30193; done) 2>&1 | grep real | awk '{ print $2 }'
0m0.446s
0m0.409s
0m0.410s
0m0.923s
0m0.408s
0m0.408s
0m0.411s
0m0.410s
0m0.409s
0m0.415s
0m0.337s
0m0.290s
0m0.391s
0m0.410s
0m0.299s
0m0.417s
0m0.416s
0m0.436s
0m0.479s
0m0.410s
0m0.409s
0m0.410s
0m0.409s
0m0.348s
0m0.369s
0m0.409s
0m0.408s
0m0.411s
0m0.410s
0m1.301s
- Média: 447.3ms
- Desvio padrão: 191.2ms
Teste 2) Acesso a um nó que não contém o pod
Segui quase os mesmos passos do teste anterior, mas dessa vez peguei o IP externo de um outro nó qualquer do cluster, diferente daquele que está hospedando o pod.
Rodei o comando novamente:
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
# ... informações de outros nós omitidas ...
ip-172-20-56-156.ec2.internal Ready node 1h v1.10.11 3.89.105.70 Debian GNU/Linux 8 (jessie) 4.4.121-k8s docker://17.3.2
Rodei novamente o curl, dessa vez apontando para o IP 3.89.105.70.
$ (for i in {1..30}; do time curl 3.89.105.70:30193; done) 2>&1 | grep real | awk '{ print $2 }'
0m0.368s
0m0.411s
0m0.406s
0m0.411s
0m0.409s
0m0.410s
0m0.410s
0m0.409s
0m0.297s
0m0.420s
0m0.410s
0m0.409s
0m0.410s
0m0.306s
0m0.410s
0m0.512s
0m0.407s
0m0.411s
0m1.388s
0m0.454s
0m0.410s
0m0.410s
0m0.307s
0m0.347s
0m0.370s
0m0.409s
0m0.410s
0m0.408s
0m0.410s
0m0.410s
- Média: 432.0ms
- Desvio padrão: 185.3ms
Teste 3) Acesso via load balancer atrelado diretamente ao serviço
Para fazer esse teste, alterei o service para o tipo LoadBalancer. Assim, o IP do LoadBalancer da AWS apontará para todos os nós do meu cluster, e balanceará os requests entre todos os nós.
apiVersion: v1
kind: Service
metadata:
name: hello-nginx-plaintext-2
namespace: labs
spec:
type: LoadBalancer
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
selector:
project: hello-nginx-plaintext
type: http-server
Depois de criado o serviço, obtive o DNS do load balancer:
$ kubectl get svc -n labs
hello-nginx-plaintext-3 LoadBalancer 100.67.114.224 acf840d7a8c4811e990020aec1ba06c5-1407280343.us-east-1.elb.amazonaws.com 80:32714/TCP 1m
Rodei novamente o curl, agora para o destino acf840d7a8c4811e990020aec1ba06c5-1407280343.us-east-1.elb.amazonaws.com:
$ (for i in {1..30}; do time curl acf840d7a8c4811e990020aec1ba06c5-1407280343.us-east-1.elb.amazonaws.com; done) 2>&1 | grep real | awk '{ print $2 }'
0m0.388s
0m0.408s
0m0.409s
0m0.409s
0m0.410s
0m0.410s
0m0.407s
0m0.316s
0m0.402s
0m0.409s
0m0.396s
0m0.424s
0m0.408s
0m0.411s
0m0.409s
0m0.407s
0m0.411s
0m0.410s
0m0.410s
0m0.408s
0m1.436s
0m0.406s
0m0.413s
0m0.409s
0m0.409s
0m0.410s
0m0.410s
0m0.409s
0m0.408s
0m0.410s
- Média: 439.4ms
- Desvio padrão: 189.1ms
Esse resultado mostra que o LoadBalancer não aumenta a latência de modo significativo.
Teste 4) Acesso via load balancer atrelado ao ambassador
Agora, configuramos o serviço Ambassador (https://www.getambassador.io/) em nosso cluster, e expusemos o serviço do Ambassador via load balancer (assim como no Teste 3).
Sendo assim, temos agora o seguinte fluxo: Cliente -> Load Balancer -> Ambassador -> Serviço hello-nginx-plaintext
A configuração do serviço hello-nginx-plaintext ficou assim:
apiVersion: v1
kind: Service
metadata:
name: hello-nginx-plaintext
namespace: labs
annotations:
getambassador.io/config: |
---
apiVersion: ambassador/v0
kind: Mapping
name: hello-nginx-plaintext-mapping
host: hello-nginx-plaintext.lb1.tecnologiafox.com.br
prefix: /
service: hello-nginx-plaintext.labs
spec:
selector:
project: hello-nginx-plaintext
type: http-server
ports:
- port: 80
targetPort: 80
A configuração do serviço Ambassador foge do escopo deste artigo, mas pode ser consultada em https://www.getambassador.io/.
Agora o curl terá como alvo o domínio hello-nginx-plaintext.lb1.tecnologiafox.com.br.
$ (for i in {1..30}; do time curl hello-nginx-plaintext.lb1.tecnologiafox.com.br; done) 2>&1 | grep real | awk '{ print $2 }'
0m0.423s
0m0.310s
0m0.406s
0m0.410s
0m0.409s
0m0.411s
0m0.409s
0m0.408s
0m0.352s
0m0.316s
0m0.357s
0m0.409s
0m0.410s
0m0.409s
0m0.375s
0m0.444s
0m0.407s
0m0.412s
0m0.409s
0m0.408s
0m0.411s
0m2.458s
0m0.306s
0m0.410s
0m0.310s
0m0.408s
0m0.409s
0m0.410s
0m0.409s
0m0.444s
- Média: 462.3ms
- Desvio padrão: 378.83ms
Teste 5) Acesso via load balancer atrelado ao ambassador com HTTPS
Nosso Ambassador está configurado com Let’s Encrypt para permitir conexões HTTPS. Sendo assim, fizemos teste similar ao Teste 4, mas agora apontando para a URL https://hello-nginx-plaintext.lb1.tecnologiafox.com.br:
$ (for i in {1..30}; do time curl https://hello-nginx-plaintext.lb1.tecnologiafox.com.br; done) 2>&1 | grep real | awk '{ print $2 }'
0m0.853s
0m0.715s
0m0.921s
0m0.813s
0m0.824s
0m0.818s
0m0.822s
0m0.820s
0m0.819s
0m0.819s
0m0.813s
0m1.336s
0m0.920s
0m0.820s
0m0.819s
0m0.921s
0m1.229s
0m0.819s
0m0.767s
0m0.763s
0m0.821s
0m0.720s
0m0.835s
0m0.859s
0m0.866s
0m2.048s
0m0.821s
0m0.812s
0m0.825s
0m0.717s
- Média: 891.8ms
- Desvio padrão: 253.3ms
Conclusões
Considerando o desvio padrão, não tivemos mudanças significativas dos testes 1 ao 4. Esse é um resultado positivo, que reforça a impressão de que o LoadBalancer da AWS e também o serviço Ambassador não aumentam, de forma significativa, a latência do nosso cluster Kubernetes.
Por outro lado, o teste 5 mostra que utilizar HTTPS em vez de HTTP aumenta consideravelmente (mais de 400ms) a latência do request. Mas isso já é um fato conhecido, e não trouxe nenhuma surpresa. Lembremos, ainda, que os clientes HTTPS normalmente utilizam a funcionalidade Keep-Alive do protocolo, para preservar o handshake e evitar grandes latências no cenário de múltiplas chamadas subsequentes.