Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

Equilibrio de carga e escalado de conexións de longa duración en Kubernetes
Este artigo axudarache a comprender como funciona o equilibrio de carga en Kubernetes, o que ocorre ao escalar conexións de longa duración e por que deberías considerar o equilibrio no lado do cliente se usas HTTP/2, gRPC, RSockets, AMQP ou outros protocolos de longa duración. . 

Un pouco sobre como se redistribúe o tráfico en Kubernetes 

Kubernetes ofrece dúas abstraccións convenientes para implementar aplicacións: Servizos e Implementos.

Os despregamentos describen como e cantas copias da súa aplicación deberían executarse nun momento dado. Cada aplicación está implantada como un Pod e asígnaselle un enderezo IP.

Os servizos teñen unha función similar a un equilibrador de carga. Están deseñados para distribuír o tráfico en múltiples pods.

A ver como se ve.

  1. No seguinte diagrama podes ver tres instancias da mesma aplicación e un equilibrador de carga:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  2. O equilibrador de carga chámase Servizo e asígnaselle un enderezo IP. Calquera solicitude entrante redirixirase a un dos módulos:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  3. O escenario de implantación determina o número de instancias da aplicación. Case nunca terás que expandirte directamente baixo:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  4. Cada pod ten asignado o seu propio enderezo IP:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

É útil pensar nos servizos como unha colección de enderezos IP. Cada vez que accede ao servizo, selecciónase un dos enderezos IP da lista e úsase como enderezo de destino.

Parece así.

  1. Recíbese unha solicitude curl 10.96.45.152 ao servizo:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  2. O servizo selecciona un dos tres enderezos de pod como destino:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  3. O tráfico redirixe a un pod específico:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

Se a túa aplicación consta dun frontend e un backend, terás tanto un servizo como unha implementación para cada un.

Cando o frontend fai unha solicitude ao backend, non necesita saber exactamente cantos pods serve o backend: pode haber un, dez ou cen.

Ademais, o frontend non sabe nada sobre os enderezos dos pods que serven ao backend.

Cando o frontend fai unha solicitude ao backend, utiliza o enderezo IP do servizo backend, que non cambia.

Así se ve.

  1. Under 1 solicita o compoñente back-end interno. En lugar de seleccionar un específico para o backend, fai unha solicitude ao servizo:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  2. O servizo selecciona un dos módulos de backend como enderezo de destino:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  3. O tráfico vai do Pod 1 ao Pod 5, seleccionado polo servizo:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  4. Menores de 1 non saben exactamente cantas vainas como menos de 5 se agochan detrás do servizo:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

Pero como distribúe exactamente as solicitudes o servizo? Parece que se usa o equilibrio round-robin? Imos descubrir. 

Equilibrio nos servizos de Kubernetes

Os servizos de Kubernetes non existen. Non hai ningún proceso para o servizo que teña asignado un enderezo IP e un porto.

Podes verificalo iniciando sesión en calquera nodo do clúster e executando o comando netstat -ntlp.

Nin sequera poderás atopar o enderezo IP asignado ao servizo.

O enderezo IP do servizo está situado na capa de control, no controlador, e rexistrado na base de datos - etcd. O mesmo enderezo é usado por outro compoñente: kube-proxy.
Kube-proxy recibe unha lista de enderezos IP para todos os servizos e xera un conxunto de regras de iptables en cada nodo do clúster.

Estas regras din: "Se vemos o enderezo IP do servizo, necesitamos modificar o enderezo de destino da solicitude e envialo a un dos pods".

O enderezo IP do servizo utilízase só como punto de entrada e non é atendido por ningún proceso que escoite ese enderezo IP e ese porto.

Vexamos isto

  1. Considere un grupo de tres nodos. Cada nodo ten pods:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  2. As vainas atadas pintadas de beis forman parte do servizo. Como o servizo non existe como proceso, móstrase en gris:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  3. O primeiro pod solicita un servizo e debe ir a un dos pods asociados:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  4. Pero o servizo non existe, o proceso non existe. Como funciona?

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  5. Antes de que a solicitude abandone o nodo, pasa polas regras de iptables:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  6. As regras de iptables saben que o servizo non existe e substitúen o seu enderezo IP por un dos enderezos IP dos pods asociados a ese servizo:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  7. A solicitude recibe un enderezo IP válido como enderezo de destino e procesarase normalmente:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  8. Dependendo da topoloxía da rede, a solicitude chega finalmente ao pod:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

¿Pode equilibrar a carga de iptables?

Non, os iptables úsanse para filtrar e non foron deseñados para equilibrar.

Non obstante, é posible escribir un conxunto de regras que funcionen como pseudo-equilibrador.

E isto é exactamente o que se implementa en Kubernetes.

Se tes tres pods, kube-proxy escribirá as seguintes regras:

  1. Seleccione o primeiro subconxunto cunha probabilidade do 33%, se non, vai á seguinte regra.
  2. Escolla a segunda cunha probabilidade do 50%, se non vai á seguinte regra.
  3. Seleccione o terceiro debaixo.

Este sistema fai que cada pod sexa seleccionada cunha probabilidade do 33%.

Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

E non hai garantía de que o Pod 2 sexa elixido a continuación despois do Pod 1.

Nota: iptables usa un módulo estatístico con distribución aleatoria. Así, o algoritmo de balance baséase na selección aleatoria.

Agora que entendes como funcionan os servizos, vexamos escenarios de servizos máis interesantes.

As conexións de longa duración en Kubernetes non se escalan por defecto

Cada solicitude HTTP do frontend ao backend é atendida por unha conexión TCP separada, que se abre e pecha.

Se o frontend envía 100 solicitudes por segundo ao backend, ábrense e péchanse 100 conexións TCP diferentes.

Pode reducir o tempo de procesamento de solicitudes e a carga abrindo unha conexión TCP e usándoa para todas as solicitudes HTTP posteriores.

O protocolo HTTP ten unha función chamada HTTP keep-alive ou reutilización da conexión. Neste caso, úsase unha única conexión TCP para enviar e recibir varias solicitudes e respostas HTTP:

Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

Esta función non está activada por defecto: tanto o servidor como o cliente deben estar configurados en consecuencia.

A configuración en si é sinxela e accesible para a maioría das linguaxes e ambientes de programación.

Aquí tes algunhas ligazóns a exemplos en diferentes idiomas:

Que pasa se usamos keep-alive nun servizo de Kubernetes?
Supoñamos que tanto o frontend como o backend soportan a permanencia.

Temos unha copia do frontend e tres copias do backend. O frontend fai a primeira solicitude e abre unha conexión TCP ao backend. A solicitude chega ao servizo, seleccionase un dos módulos backend como enderezo de destino. O backend envía unha resposta e o frontend a recibe.

A diferenza da situación habitual na que a conexión TCP se pecha despois de recibir unha resposta, agora mantense aberta para máis solicitudes HTTP.

Que pasa se o frontend envía máis solicitudes ao backend?

Para reenviar estas solicitudes, utilizarase unha conexión TCP aberta, todas as solicitudes irán ao mesmo backend onde foi a primeira solicitude.

Non debería iptables redistribuír o tráfico?

Non neste caso.

Cando se crea unha conexión TCP, pasa polas regras de iptables, que seleccionan un backend específico onde irá o tráfico.

Dado que todas as solicitudes posteriores están nunha conexión TCP xa aberta, as regras de iptables xa non se chaman.

A ver como se ve.

  1. O primeiro pod envía unha solicitude ao servizo:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  2. Xa sabes o que vai pasar despois. O servizo non existe, pero hai regras de iptables que procesarán a solicitude:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  3. Seleccionarase un dos módulos de backend como enderezo de destino:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  4. A solicitude chega ao pod. Neste punto, establecerase unha conexión TCP persistente entre os dous pods:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  5. Calquera solicitude posterior do primeiro pod pasará pola conexión xa establecida:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

O resultado é un tempo de resposta máis rápido e un maior rendemento, pero perde a capacidade de escalar o backend.

Aínda que teñas dous pods no backend, cunha conexión constante, o tráfico sempre irá a un deles.

Pódese solucionar isto?

Dado que Kubernetes non sabe como equilibrar as conexións persistentes, esta tarefa corresponde a ti.

Os servizos son unha colección de enderezos IP e portos chamados puntos finais.

A súa aplicación pode obter unha lista de puntos finais do servizo e decidir como distribuír as solicitudes entre eles. Podes abrir unha conexión persistente a cada pod e equilibrar as solicitudes entre estas conexións mediante round-robin.

Ou aplica máis algoritmos de equilibrio complexos.

O código do lado do cliente responsable do equilibrio debería seguir esta lóxica:

  1. Obtén unha lista de puntos finais do servizo.
  2. Abre unha conexión persistente para cada punto final.
  3. Cando teña que facer unha solicitude, use unha das conexións abertas.
  4. Actualiza regularmente a lista de puntos finais, crea outras novas ou pecha conexións persistentes antigas se a lista cambia.

Así se verá.

  1. En lugar de que o primeiro pod envíe a solicitude ao servizo, podes equilibrar as solicitudes no lado do cliente:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  2. Debes escribir un código que pregunte que pods forman parte do servizo:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  3. Unha vez que teñas a lista, gárdaa no lado do cliente e utilízaa para conectarte aos pods:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

  4. Vostede é o responsable do algoritmo de equilibrio de carga:

    Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

Agora xorde a pregunta: este problema só se aplica ao mantemento HTTP?

Equilibrio de carga do lado do cliente

HTTP non é o único protocolo que pode usar conexións TCP persistentes.

Se a súa aplicación usa unha base de datos, non se abrirá unha conexión TCP cada vez que necesite facer unha solicitude ou recuperar un documento da base de datos. 

Pola contra, ábrese e úsase unha conexión TCP persistente coa base de datos.

Se a túa base de datos está implantada en Kubernetes e o acceso se proporciona como un servizo, atoparás os mesmos problemas descritos na sección anterior.

Unha réplica de base de datos cargarase máis que as demais. Kube-proxy e Kubernetes non axudarán a equilibrar as conexións. Debes ter coidado de equilibrar as consultas na túa base de datos.

Dependendo da biblioteca que use para conectarse á base de datos, pode ter diferentes opcións para resolver este problema.

A continuación móstrase un exemplo de acceso a un clúster de bases de datos MySQL desde Node.js:

var mysql = require('mysql');
var poolCluster = mysql.createPoolCluster();

var endpoints = /* retrieve endpoints from the Service */

for (var [index, endpoint] of endpoints) {
  poolCluster.add(`mysql-replica-${index}`, endpoint);
}

// Make queries to the clustered MySQL database

Hai moitos outros protocolos que usan conexións TCP persistentes:

  • WebSockets e WebSockets seguros
  • HTTP / 2
  • gRPC
  • RSockets
  • AMQP

Xa deberías estar familiarizado coa maioría destes protocolos.

Pero se estes protocolos son tan populares, por que non hai unha solución de equilibrio estandarizada? Por que ten que cambiar a lóxica do cliente? Existe unha solución nativa de Kubernetes?

Kube-proxy e iptables están deseñados para cubrir os casos de uso máis comúns cando se implementan en Kubernetes. Isto é por comodidade.

Se estás a usar un servizo web que expón unha API REST, estás de sorte; neste caso, non se usan conexións TCP persistentes, podes usar calquera servizo de Kubernetes.

Pero unha vez que comeces a usar conexións TCP persistentes, terás que descubrir como distribuír uniformemente a carga entre os backends. Kubernetes non contén solucións preparadas para este caso.

Non obstante, certamente hai opcións que poden axudar.

Equilibrar conexións de longa duración en Kubernetes

Hai catro tipos de servizos en Kubernetes:

  1. ClústerIP
  2. NodePort
  3. LoadBalancer
  4. Sen cabeza

Os tres primeiros servizos funcionan baseándose nun enderezo IP virtual, que é usado por kube-proxy para construír regras de iptables. Pero a base fundamental de todos os servizos é un servizo sen cabeza.

O servizo sen cabeza non ten ningún enderezo IP asociado e só proporciona un mecanismo para recuperar unha lista de enderezos IP e portos dos pods (endpoints) asociados a el.

Todos os servizos baséanse no servizo sen cabeza.

O servizo ClusterIP é un servizo sen cabeza con algunhas incorporacións: 

  1. A capa de xestión asígnalle un enderezo IP.
  2. Kube-proxy xera as regras iptables necesarias.

Deste xeito, pode ignorar o kube-proxy e utilizar directamente a lista de puntos finais obtidos do servizo sen cabeza para equilibrar a carga da súa aplicación.

Pero como podemos engadir unha lóxica similar a todas as aplicacións despregadas no clúster?

Se a túa aplicación xa está implantada, esta tarefa pode parecer imposible. Non obstante, hai unha opción alternativa.

Service Mesh axudarache

Probablemente xa notaches que a estratexia de equilibrio de carga no lado do cliente é bastante estándar.

Cando se inicia a aplicación, ela:

  1. Obtén unha lista de enderezos IP do servizo.
  2. Abre e mantén un grupo de conexións.
  3. Actualiza periodicamente o grupo engadindo ou eliminando puntos finais.

Unha vez que a solicitude quere facer unha solicitude, ela:

  1. Selecciona unha conexión dispoñible usando algunha lóxica (por exemplo, round-robin).
  2. Executa a solicitude.

Estes pasos funcionan tanto para conexións WebSockets, gRPC e AMQP.

Pode separar esta lóxica nunha biblioteca separada e usala nas súas aplicacións.

Non obstante, pode usar mallas de servizo como Istio ou Linkerd.

Service Mesh aumenta a súa aplicación cun proceso que:

  1. Busca automaticamente enderezos IP do servizo.
  2. Proba conexións como WebSockets e gRPC.
  3. Equilibra as solicitudes utilizando o protocolo correcto.

Service Mesh axuda a xestionar o tráfico dentro do clúster, pero require bastante recursos. Outras opcións son usar bibliotecas de terceiros como Netflix Ribbon ou proxies programables como Envoy.

Que pasa se ignoras os problemas de equilibrio?

Podes optar por non usar o balance de carga e aínda non notar ningún cambio. Vexamos algúns escenarios de traballo.

Se tes máis clientes que servidores, este non é un problema tan grande.

Digamos que hai cinco clientes que se conectan a dous servidores. Aínda que non haxa equilibrio, empregaranse os dous servidores:

Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

É posible que as conexións non estean distribuídas uniformemente: quizais catro clientes conectados ao mesmo servidor, pero hai moitas posibilidades de que se utilicen ambos os dous servidores.

O máis problemático é o escenario contrario.

Se tes menos clientes e máis servidores, os teus recursos poden estar infrautilizados e aparecerá un posible pescozo de botella.

Digamos que hai dous clientes e cinco servidores. No mellor dos casos, haberá dúas conexións permanentes a dous servidores de cada cinco.

Os servidores restantes estarán inactivos:

Equilibrio de carga e escalado de conexións de longa duración en Kubernetes

Se estes dous servidores non poden xestionar as solicitudes dos clientes, a escala horizontal non axudará.

Conclusión

Os servizos de Kubernetes están deseñados para funcionar na maioría dos escenarios de aplicacións web estándar.

Non obstante, unha vez que comeza a traballar con protocolos de aplicación que usan conexións TCP persistentes, como bases de datos, gRPC ou WebSockets, os servizos xa non son axeitados. Kubernetes non ofrece mecanismos internos para equilibrar as conexións TCP persistentes.

Isto significa que debes escribir aplicacións tendo en conta o equilibrio do lado do cliente.

Tradución elaborada polo equipo Kubernetes aaS de Mail.ru.

Que máis ler sobre o tema:

  1. Tres niveis de escalado automático en Kubernetes e como usalos de forma eficaz
  2. Kubernetes no espírito da piratería cun modelo para a súa implementación.
  3. A nosa canle de Telegram sobre transformación dixital.

Fonte: www.habr.com

Engadir un comentario