Seis meses atrás, concluímos a migração de todos os nossos serviços sem estado para o kubernetes. À primeira vista, a tarefa é bastante simples: você precisa implantar um cluster, escrever especificações de aplicativos e pronto. Devido à obsessão em garantir a estabilidade no trabalho de nosso serviço, tive que começar imediatamente a entender como o k8s funciona e testar vários cenários de falha. A maioria das perguntas que eu tinha sobre tudo relacionado à rede. Um desses problemas escorregadios é a operação dos Serviços no kubernetes.
A documentação nos diz:
- distribuir o aplicativo
- definir amostras de vivacidade / prontidão
- criar um serviço
- tudo funcionará: balanceamento de carga, failover etc.
Mas, na prática, tudo é um pouco mais complicado. Vamos ver como ele realmente funciona.
Pouco de teoria
Além disso, quero dizer que o leitor já está familiarizado com o dispositivo kubernetes e sua terminologia; apenas lembramos o que é um serviço.
O serviço é a essência do k8s, que descreve um conjunto de lares e métodos para acessá-los.
Por exemplo, lançamos nosso aplicativo:
apiVersion: apps/v1 kind: Deployment metadata: name: webapp spec: selector: matchLabels: app: webapp replicas: 2 template: metadata: labels: app: webapp spec: containers: - name: webapp image: defaultxz/webapp command: ["/webapp", "0.0.0.0:80"] ports: - containerPort: 80 readinessProbe: httpGet: {path: /, port: 80} initialDelaySeconds: 1 periodSeconds: 1
$ kubectl get pods -l app=webapp NAME READY STATUS RESTARTS AGE webapp-5d5d96f786-b2jxb 1/1 Running 0 3h webapp-5d5d96f786-rt6j7 1/1 Running 0 3h
Agora, para acessá-lo, precisamos criar um serviço no qual determinamos quais canais queremos acessar (seletor) e em quais portas:
kind: Service apiVersion: v1 metadata: name: webapp spec: selector: app: webapp ports: - protocol: TCP port: 80 targetPort: 80
$ kubectl get svc webapp NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE webapp ClusterIP 10.97.149.77 <none> 80/TCP 1d
Agora podemos acessar nosso serviço a partir de qualquer máquina no cluster:
curl -i http://10.97.149.77 HTTP/1.1 200 OK Date: Mon, 24 Sep 2018 11:55:14 GMT Content-Length: 2 Content-Type: text/plain; charset=utf-8
Como tudo isso funciona

Muito simplificado:
- você aplicou o kubectl especificações de implantação
- mágica acontece, cujos detalhes não são importantes neste contexto
- como resultado, nós de trabalho do aplicativo estavam em alguns nós
- uma vez que todo intervalo kubelet (agente k8s em cada nó) executa amostras de prontidão / disponibilidade de todos os pods em execução em seu nó, ele envia os resultados ao apiserver (interface com o cérebro do k8s)
- O kube-proxy em cada nó recebe notificações do apiserver sobre todas as alterações nos serviços e lares que participam nos serviços
- O kube-proxy reflete todas as alterações na configuração dos subsistemas subjacentes (iptables, ipvs)
Para simplificar, considere o método de proxy padrão - iptables. No iptables, temos para o nosso ip virtual 10.97.149.77:
-A KUBE-SERVICES -d 10.97.149.77/32 -p tcp -m comment --comment "default/webapp: cluster IP" -m tcp --dport 80 -j KUBE-SVC-BL7FHTIPVYJBLWZN
o tráfego vai para a cadeia KUBE-SVC-BL7FHTIPVYJBLWZN , na qual é distribuído entre outras 2 cadeias
-A KUBE-SVC-BL7FHTIPVYJBLWZN -m comment --comment "default/webapp:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-UPKHDYQWGW4MVMBS -A KUBE-SVC-BL7FHTIPVYJBLWZN -m comment --comment "default/webapp:" -j KUBE-SEP-FFCBJRUPEN3YPZQT
estes são os nossos pods:
-A KUBE-SEP-UPKHDYQWGW4MVMBS -p tcp -m comment --comment "default/webapp:" -m tcp -j DNAT --to-destination 10.244.0.10:80 -A KUBE-SEP-FFCBJRUPEN3YPZQT -p tcp -m comment --comment "default/webapp:" -m tcp -j DNAT --to-destination 10.244.0.11:80
Testando a falha de uma das lareiras
Meu aplicativo de teste do aplicativo da web pode alternar para o modo "erupção de erro", para isso, é necessário extrair o URL "/ err".
Os resultados de ab -c 50 -n 20.000 no meio do teste puxaram "/ err" em uma das lareiras:
Complete requests: 20000 Failed requests: 3719
O ponto aqui não é o número específico de erros (o número deles variará dependendo da carga), mas sim. Em geral, jogamos o "ruim" para fora de equilíbrio, mas no momento da troca do cliente do serviço recebemos erros. A causa dos erros é fácil de explicar: os testes de prontidão são realizados kubelet uma vez por segundo + até um curto período de tempo para disseminar informações que não responderam ao teste.
O back-end do IPVS para o kube-proxy (experimental) ajuda?
Na verdade não! Ele resolve o problema de otimização de proxy, oferece um algoritmo de balanceamento personalizado, mas não resolve o problema do processamento de falhas.
Como ser
Esse problema pode ser resolvido apenas por um balanceador que pode tentar novamente (novas tentativas). Em outras palavras, para http, precisamos de um balanceador L7. Esses balanceadores para kubernetes já estão em pleno uso, seja na forma de ingresso (implicado como um ponto na mudança para o cluster, mas em geral ele faz exatamente o que é necessário) ou como uma implementação de uma camada separada - uma malha de serviço, por exemplo.
Em nossa produção, ainda não começamos a usar malha de entrada ou de serviço devido à complexidade adicional. Tais abstrações, na minha opinião, ajudam nos casos em que você precisa configurar frequentemente um grande número de serviços. Mas, ao mesmo tempo, você "paga" a controlabilidade e a infraestrutura simples. Você gastará tempo extra para descobrir como configurar os prazos e os tempos limite de um serviço específico.
Como nós
Utilizamos serviços k8s decapitados. Esses serviços não possuem um ip virtual e, portanto, o kube-proxy e o iptables não estão envolvidos em seu trabalho. Para cada serviço desse tipo, você pode obter uma lista de lares ativos através do DNS ou da API.
Para aplicativos que interagem com outros serviços, criamos um contêiner lateral com enviado . O Evoy recebe periodicamente uma lista atualizada de pods para todos os serviços necessários através do DNS e, o mais importante, é capaz de fazer repetidas tentativas de consultar outros pods em caso de erro. Você pode executá-lo como um DaemonSet em cada nó, mas, se essa instância falhar, todos os aplicativos que o utilizam parem de funcionar. Como o consumo de recursos desse proxy é muito pequeno, decidimos usá-lo na variante de contêiner lateral.
Isso é essencialmente exatamente o que o istio faz, mas, no nosso caso, a balança mudou para a simplicidade (não há necessidade de aprender o istio, se deparar com seus erros). Talvez esse equilíbrio mude, e começaremos a usar algo como istio.
Nós da okmeter.io kubernetes definitivamente criamos raízes e acreditamos em sua distribuição posterior. O suporte para o monitoramento de k8s em nosso serviço está a caminho, fique atento!