Kubernetes en producción: servicios

Hace seis meses, completamos la migración de todos nuestros servicios apátridas a kubernetes. A primera vista, la tarea es bastante simple: debe implementar un clúster, escribir las especificaciones de la aplicación y listo. Debido a la obsesión por garantizar la estabilidad en el trabajo de nuestro servicio, tuve que comenzar a comprender de inmediato cómo funciona k8s y probar varios escenarios de falla. La mayoría de las preguntas que tenía sobre todo lo relacionado con la red. Uno de esos problemas resbaladizos es la operación de los Servicios en kubernetes.


La documentación nos dice:


  • implementar la aplicación
  • establecer muestras de vida / preparación
  • crear un servicio
  • entonces todo funcionará: equilibrio de carga, conmutación por error, etc.

Pero en la práctica, todo es algo más complicado. Veamos cómo funciona realmente.


Poco de teoría


Además, quiero decir que el lector ya está familiarizado con el dispositivo kubernetes y su terminología; solo recordamos qué es un servicio.


El servicio es la esencia de k8s, que describe un conjunto de hogares y métodos para acceder a ellos.


Por ejemplo, lanzamos nuestra aplicación:


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 

Ahora, para acceder a él, debemos crear un servicio en el que determinemos a qué canales queremos tener acceso (selector) y en qué puertos:


 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 

Ahora podemos acceder a nuestro servicio desde cualquier máquina en el clúster:


 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 funciona todo



Muy simplificado:


  • hiciste kubectl aplicar especificaciones de implementación
  • ocurre la magia, cuyos detalles no son importantes en este contexto
  • Como resultado, los nodos de trabajo de la aplicación resultaron estar en algunos nodos
  • una vez que cada intervalo de kubelet (agente de k8s en cada nodo) realiza muestras de vida / preparación de todos los pods que se ejecutan en su nodo, envía los resultados a un servidor (interfaz para los cerebros de k8s)
  • kube-proxy en cada nodo recibe notificaciones de un servidor sobre todos los cambios en los servicios y hogares que participan en los servicios
  • kube-proxy refleja todos los cambios en la configuración de los subsistemas subyacentes (iptables, ipvs)

Para simplificar, considere el método proxy predeterminado: iptables. En iptables, tenemos para nuestra 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 

el tráfico va a la cadena KUBE-SVC-BL7FHTIPVYJBLWZN , en la que se distribuye entre otras 2 cadenas


 -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 

Estas son nuestras vainas:


 -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 

Probar la falla de uno de los hogares


Mi aplicación de prueba de webapp puede cambiar al modo "error rash", para esto necesita extraer la url "/ err".


Los resultados de ab -c 50 -n 20,000 en el medio de la prueba tiraron "/ err" en uno de los hogares:


 Complete requests: 20000 Failed requests: 3719 

El punto aquí no es el número específico de errores (su número variará dependiendo de la carga), sino que lo son. En general, desechamos el "mal" fuera de balance, pero al momento de cambiar el cliente del servicio recibimos errores. La causa de los errores es bastante fácil de explicar: las pruebas de preparación se realizan kubelet una vez por segundo + incluso un breve período de tiempo para difundir información que no respondió a la prueba.


¿Servirá el backend IPVS para kube-proxy (experimental)?


En realidad no! Resuelve el problema de la optimización del proxy, ofrece un algoritmo de equilibrio personalizado, pero no resuelve el problema del procesamiento de fallas.


Como ser


Este problema solo puede ser resuelto por un equilibrador que pueda reintentar (reintentos). En otras palabras, para http necesitamos un equilibrador L7. Dichos equilibradores para kubernetes ya están en pleno uso, ya sea en forma de ingreso (implícito como un punto en el movimiento hacia el clúster, pero en general hace exactamente lo que necesita), o como una implementación de una capa separada: una malla de servicio, por ejemplo, istio .


En nuestra producción, todavía no hemos comenzado a utilizar el ingreso ni la malla de servicio debido a la complejidad adicional. Tales abstracciones, en mi opinión, ayudan en los casos en los que necesita configurar a menudo una gran cantidad de servicios. Pero al mismo tiempo "paga" la capacidad de control y la infraestructura simple. Dedicará más tiempo a descubrir cómo configurar el rertai y los tiempos de espera para un servicio en particular.


Como


Utilizamos servicios K8 sin cabeza. Dichos servicios no tienen una ip virtual y, en consecuencia, kube-proxy e iptables no están involucrados en su trabajo. Para cada uno de estos servicios, puede obtener una lista de hogares en vivo a través del DNS o de la API.


Para aplicaciones que interactúan con otros servicios, hacemos un contenedor de sidecar con enviado . Evoy recibe periódicamente una lista actualizada de pods para todos los servicios necesarios a través de DNS, y lo más importante, puede volver a intentar las solicitudes de otros pods en caso de error. Puede ejecutarlo como un DaemonSet en cada nodo, pero si esta instancia falla, todas las aplicaciones que lo usen dejarán de funcionar. Dado que el consumo de recursos de este proxy es bastante pequeño, decidimos usarlo en la variante de contenedor de sidecar.


Esto es esencialmente lo que hace istio, pero en nuestro caso el equilibrio se ha desplazado hacia la simplicidad (no es necesario aprender istio, encontrarse con sus errores). Quizás este equilibrio cambie, y comenzaremos a usar algo como istio.


Nosotros en okmeter.io kubernetes definitivamente hemos echado raíces, y creemos en su distribución adicional. El soporte para monitorear k8s en nuestro servicio está en camino, ¡estad atentos!

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


All Articles