Hoy publicamos una traducción de la tercera parte de la Guía de redes de Kubernetes. La
primera parte fue sobre pods, la
segunda sobre servicios, y hoy hablaremos sobre el equilibrio de carga y los recursos de Kubernetes del tipo Ingress.
El enrutamiento no es equilibrio de carga
En el artículo
anterior de esta serie, consideramos una configuración que consta de un par de hogares y un servicio al que se le asignó una dirección IP llamada "IP de clúster". Las consultas destinadas a hogares se enviaron a esta dirección. Aquí continuaremos trabajando en nuestro sistema de capacitación, comenzando donde nos graduamos la última vez. Recuerde que la dirección IP del clúster del servicio,
10.3.241.152
, pertenece a un rango de direcciones IP que es diferente de la utilizada en la red de hogar y de la utilizada en la red en la que se encuentran los nodos. Llamé a la red definida por este espacio de direcciones "red de servicio", aunque apenas merece un nombre especial, ya que no hay dispositivos conectados a esta red, y su espacio de direcciones, de hecho, consiste completamente en reglas de enrutamiento. Anteriormente se demostró cómo esta red se implementa sobre la base del componente Kubernetes llamado
kube-proxy e interactúa con el módulo
netfilter del kernel de Linux para interceptar y redirigir el tráfico enviado al clúster IP para trabajar.
Diagrama de redHasta ahora, hablamos de "conexiones" y "solicitudes" e incluso utilizamos el concepto difícil de interpretar de "tráfico", pero para comprender las características del mecanismo de Kubernetes Ingress, necesitamos usar términos más precisos. Por lo tanto, las conexiones y solicitudes funcionan en el 4 ° nivel del
modelo OSI (tcp) o en el 7 ° nivel (http, rpc, etc.). Las reglas de Netfilter son reglas de enrutamiento, funcionan con paquetes IP en el tercer nivel. Todos los enrutadores, incluido netfilter, toman decisiones más o menos basadas solo en la información contenida en el paquete. En general, están interesados en saber de dónde viene el paquete y hacia dónde va. Por lo tanto, para describir este comportamiento en términos del tercer nivel del modelo OSI, se debe decir que cada paquete destinado al servicio ubicado en
10.3.241.152:80
, que llega a la interfaz del nodo
eth0
, es procesado por netfilter y, de acuerdo con Las reglas establecidas para nuestro servicio se redirigen a la dirección IP de un hogar viable.
Parece bastante obvio que cualquier mecanismo que usemos para permitir que clientes externos accedan a pods debería usar la misma infraestructura de enrutamiento. Como resultado, estos clientes externos accederán a la dirección IP y al puerto del clúster, ya que son el "punto de acceso" a todos los mecanismos de los que hemos hablado hasta ahora. Nos permiten no preocuparnos sobre dónde exactamente se ejecuta en un determinado momento. Sin embargo, no es del todo obvio cómo hacer que todo funcione.
El servicio IP de clúster solo es accesible con la interfaz Ethernet del nodo. Nada fuera del clúster sabe qué hacer con las direcciones del rango al que pertenece esta dirección. ¿Cómo puedo redirigir el tráfico desde una dirección IP pública a una dirección a la que solo se puede acceder si el paquete ya llegó al host?
Si tratamos de encontrar una solución a este problema, una de las cosas que se pueden hacer en el proceso de encontrar una solución será el estudio de las reglas de netfilter utilizando la utilidad
iptables . Si hace esto, puede descubrir algo que, a primera vista, puede parecer inusual: las reglas para el servicio no se limitan a una red de origen específica. Esto significa que cualquier paquete generado en cualquier lugar que llegue a la interfaz Ethernet del nodo y tenga una dirección de destino de
10.3.241.152:80
será reconocido como conforme a la regla y será redirigido a la sub. ¿Podemos darles a los clientes un clúster de IP, quizás vinculándolo a un nombre de dominio adecuado y luego configurar una ruta que nos permita organizar la entrega de estos paquetes a uno de los nodos?
Cliente externo y clústerSi todo está configurado de esta manera, tal diseño resultará estar funcionando. Los clientes acceden a la IP del clúster, los paquetes siguen la ruta que conduce al host y luego se redirigen a la parte inferior. En este momento, puede parecerle que dicha solución puede ser limitada, pero tiene algunos problemas serios. El primero es que los nodos, de hecho, el concepto de efímero, no son particularmente diferentes a este respecto de los hogares. Ellos, por supuesto, están un poco más cerca del mundo material que los pods, pero pueden migrar a nuevas máquinas virtuales, los clústeres pueden escalar hacia arriba o hacia abajo, y así sucesivamente. Los enrutadores funcionan en el tercer nivel del modelo OSI y los paquetes no pueden distinguir entre los servicios que funcionan normalmente y los que no funcionan correctamente. Esperan que la próxima transición en la ruta sea accesible y estable. Si no se puede alcanzar el nodo, la ruta dejará de funcionar y permanecerá así, en la mayoría de los casos, mucho tiempo. Incluso si la ruta es resistente a fallas, tal esquema conducirá al hecho de que todo el tráfico externo pasa a través de un solo nodo, lo que probablemente no sea óptimo.
No importa cómo llevemos el tráfico de clientes al sistema, debemos hacerlo para que no dependa del estado de ningún nodo del clúster. Y, de hecho, no hay una manera confiable de hacerlo utilizando solo el enrutamiento, sin algún medio de administrar activamente el enrutador. De hecho, es precisamente este papel, el papel del sistema de control, el que juega kube-proxy en relación con netfilter. Extender la responsabilidad de Kubernetes a la administración de un enrutador externo probablemente no tenía mucho sentido para los arquitectos de sistemas, especialmente porque ya tenemos herramientas comprobadas para distribuir el tráfico de clientes a través de múltiples servidores. Se llaman equilibradores de carga, y no es sorprendente que sean la solución verdaderamente confiable para Kubernetes Ingress. Para entender exactamente cómo sucede esto, necesitamos levantarnos del tercer nivel de OSI y volver a hablar sobre las conexiones.
Para utilizar el equilibrador de carga para distribuir el tráfico del cliente entre los nodos del clúster, necesitamos una dirección IP pública a la que los clientes puedan conectarse, y también necesitamos las direcciones de los propios nodos a los que el equilibrador de carga puede redirigir las solicitudes. Por las razones anteriores, no podemos simplemente crear una ruta estática estable entre el enrutador de la puerta de enlace y los nodos utilizando una red basada en servicios (clúster IP).
Entre las otras direcciones con las que puede trabajar, solo se pueden anotar las direcciones de la red a la que están conectadas las interfaces Ethernet de los nodos, es decir, en este ejemplo,
10.100.0.0/24
. El enrutador ya sabe cómo reenviar paquetes a estas interfaces, y las conexiones enviadas desde el equilibrador de carga al enrutador irán a donde deberían ir. Pero si el cliente quiere conectarse a nuestro servicio en el puerto 80, entonces no podemos enviar paquetes a este puerto en las interfaces de red de los nodos.
Balanceador de carga, intento fallido de acceder al puerto 80 de la interfaz de red del hostLa razón por la que esto no se puede hacer es completamente obvia. Es decir, estamos hablando del hecho de que no hay ningún proceso esperando conexiones a las
10.100.0.3:80
(y si lo hay, definitivamente este no es el mismo proceso), y las reglas de netfilter, que, como esperábamos, interceptarían la solicitud y se lo enviarán, no trabajarán en esa dirección de destino. Responden solo a una red IP de clúster basada en servicios, es decir, a la dirección
10.3.241.152:80
. Como resultado, estos paquetes, a su llegada, no se pueden entregar a la dirección de destino, y el núcleo emitirá una respuesta
ECONNREFUSED
. Esto nos pone en una posición confusa: no es fácil trabajar con una red para la redirección de paquetes a la que se configura netfilter al redirigir datos desde la puerta de enlace a los nodos, y una red para la que el enrutamiento es fácil de configurar no es la red a la que netfilter redirige los paquetes. Para resolver este problema, puede crear un puente entre estas redes. Esto es exactamente lo que hace Kubernetes usando un servicio como NodePort.
Servicios como NodePort
El servicio que, por ejemplo, creamos en el artículo anterior, no tiene asignado un tipo, por lo que adoptó el tipo predeterminado:
ClusterIP
. Hay dos tipos más de servicios que difieren en características adicionales, y el que nos interesa en este momento es
NodePort
. Aquí hay un ejemplo de una descripción de servicio de este tipo:
kind: Service apiVersion: v1 metadata: name: service-test spec: type: NodePort selector: app: service_test_pod ports: - port: 80 targetPort: http
Los servicios de tipo
NodePort
son servicios de tipo
ClusterIP
que tienen una oportunidad adicional: el acceso a ellos se puede obtener tanto por la dirección IP asignada al host como por la dirección asignada al clúster en la red de servicios. Esto se logra de una manera bastante simple: cuando Kubernetes crea un servicio NodePort, kube-proxy asigna un puerto en el rango de 30000-32767 y abre este puerto en la interfaz
eth0
de cada nodo (de ahí el nombre del tipo de servicio -
NodePort
). Las conexiones realizadas a este puerto (llamaremos a dichos puertos
NodePort
) se redirigen a la IP del clúster del servicio. Si creamos el servicio descrito anteriormente y ejecutamos el
kubectl get svc service-test
, podemos ver el puerto asignado a él.
$ kubectl get svc service-test NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE service-test 10.3.241.152 <none> 80:32213/TCP 1m
En este caso, el servicio tiene asignado NodePort
32213
. Esto significa que ahora podemos conectarnos al servicio a través de cualquier nodo en nuestro clúster experimental en
10.100.0.2:32213
o en
10.100.0.3:32213
. En este caso, el tráfico se redirigirá al servicio.
Una vez que esta parte del sistema ha ocupado su lugar, tenemos todos los fragmentos de la tubería para equilibrar la carga creada por las solicitudes del cliente en todos los nodos del clúster.
Servicio NodePortEn la figura anterior, el cliente se conecta al equilibrador de carga a través de una dirección IP pública, el equilibrador de carga selecciona el nodo y se conecta a él en
10.100.0.3:32213
, kube-proxy acepta esta conexión y la redirige al servicio accesible a través del clúster IP
10.3.241.152:80
. Aquí, la solicitud se procesa con éxito de acuerdo con las reglas establecidas por netfilter, y se redirige al pod del servidor en la dirección
10.0.2.2:8080
. Quizás todo esto parezca un poco complicado, y hasta cierto punto lo es, pero no es fácil encontrar una solución más sencilla que admita todas las excelentes funciones que nos brindan pods y redes basadas en servicios.
Sin embargo, este mecanismo no está exento de problemas. El uso de servicios como
NodePort
brinda a los clientes acceso a servicios utilizando un puerto no estándar. A menudo, esto no es un problema, ya que el equilibrador de carga puede proporcionarles un puerto normal y ocultar
NodePort
a los usuarios finales. Pero en algunos escenarios, por ejemplo, cuando se usa un equilibrador de carga externo de la plataforma Google Cloud, puede ser necesario implementar
NodePort
clientes. Cabe señalar que dichos puertos, además, representan recursos limitados, aunque 2768 puertos son probablemente suficientes incluso para los grupos más grandes. En la mayoría de los casos, puede dejar que Kubernetes seleccione números de puerto al azar, pero puede configurarlos usted mismo si es necesario. Otro problema son algunas limitaciones con respecto al almacenamiento de direcciones IP de origen en las solicitudes. Para saber cómo resolver estos problemas, puede consultar
este material en la documentación de Kubernetes.
Ports
NodePorts
es el mecanismo fundamental por el cual todo el tráfico externo ingresa al clúster de Kubernetes. Sin embargo, ellos mismos no nos presentan una solución preparada. Por las razones anteriores, antes del clúster, si los clientes son entidades internas o externas ubicadas en una red pública, siempre se requiere tener algún tipo de equilibrador de carga.
Los arquitectos de la plataforma, al darse cuenta de esto, proporcionaron dos formas de configurar el equilibrador de carga desde la plataforma Kubernetes. Discutamos esto.
Servicios como LoadBalancer y recursos del tipo Ingress
Servicios como
LoadBalancer
y recursos del tipo
Ingress
son algunos de los mecanismos de Kubernetes más complejos. Sin embargo, no pasaremos demasiado tiempo con ellos, ya que su uso no conduce a cambios fundamentales en todo lo que hemos hablado hasta ahora. Todo el tráfico externo, como antes, ingresa al clúster a través de
NodePort
.
Los arquitectos podrían detenerse aquí, permitiendo que aquellos que crean clústeres se preocupen solo por las direcciones IP públicas y los equilibradores de carga. De hecho, en ciertas situaciones, como iniciar un clúster en servidores regulares o en casa, esto es exactamente lo que hacen. Pero en entornos que admiten configuraciones de recursos de red controlados por API, Kubernetes le permite configurar todo lo que necesita en un solo lugar.
El primer enfoque para resolver este problema, el más simple, es usar los servicios de
LoadBalancer
como
LoadBalancer
. Dichos servicios tienen todas las capacidades de servicios como
NodePort
y, además, tienen la capacidad de crear rutas completas para el tráfico entrante, basándose en el supuesto de que el clúster se ejecuta en entornos como GCP o AWS que admiten la configuración de recursos de red a través de la API.
kind: Service apiVersion: v1 metadata: name: service-test spec: type: LoadBalancer selector: app: service_test_pod ports: - port: 80 targetPort: http
Si eliminamos y volvemos a crear el servicio de nuestro ejemplo en Google Kubernetes Engine, poco después, usando el
kubectl get svc service-test
, podemos verificar que la IP externa esté asignada.
$ kubectl get svc service-test NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE openvpn 10.3.241.52 35.184.97.156 80:32213/TCP 5m
Se dice anteriormente que podremos verificar el hecho de asignar una dirección IP externa "pronto", a pesar del hecho de que la asignación de una IP externa puede tomar varios minutos, lo cual no es sorprendente dada la cantidad de recursos que deben llevarse a un estado saludable. En la plataforma GCP, por ejemplo, esto requiere que el sistema cree una dirección IP externa, reglas de redirección de tráfico, un servidor proxy de destino, un servicio de back-end y, posiblemente, una instancia de un grupo. Después de asignar una dirección IP externa, puede conectarse al servicio a través de esta dirección, asignarle un nombre de dominio e informar a los clientes. Hasta que el servicio se destruya y se vuelva a crear (para hacer esto, raramente cuando haya una buena razón), la dirección IP no cambiará.
Servicios como
LoadBalancer
tienen algunas limitaciones. Dicho servicio no se puede configurar para descifrar el tráfico HTTPS. No puede crear hosts virtuales o configurar el enrutamiento basado en rutas, por lo que no puede, utilizando configuraciones prácticas, usar un solo equilibrador de carga con muchos servicios. Estas limitaciones llevaron a la introducción de Kubernetes 1.1. Un recurso especial para configurar equilibradores de carga. Este es un recurso del tipo
Ingress . Servicios como
LoadBalancer
tienen como objetivo ampliar las capacidades de un solo servicio para admitir clientes externos. En contraste, los recursos de
Ingress
son recursos especiales que le permiten configurar de manera flexible los equilibradores de carga. La API Ingress admite el descifrado del tráfico TLS, los hosts virtuales y el enrutamiento basado en rutas. Con esta API, el equilibrador de carga se puede configurar fácilmente para que funcione con múltiples servicios de back-end.
La API de recursos del tipo
Ingress
es demasiado grande para analizar sus características aquí; además, no afecta particularmente cómo funcionan los recursos Ingress a nivel de red. La implementación de este recurso sigue el patrón habitual de Kubernetes: hay un tipo de recurso y un controlador para controlar este tipo. El recurso en este caso es el recurso
Ingress
, que describe las solicitudes a los recursos de la red. Así es como se vería la descripción de un recurso
Ingress
.
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test-ingress annotations: kubernetes.io/ingress.class: "gce" spec: tls: - secretName: my-ssl-secret rules: - host: testhost.com http: paths: - path: /* backend: serviceName: service-test servicePort: 80
El controlador de Ingress es responsable de ejecutar estas solicitudes llevando otros recursos al estado deseado. Cuando se usa Ingress, se crean servicios como
NodePort
, después de lo cual el controlador Ingress puede tomar decisiones sobre cómo dirigir el tráfico a los nodos. Existe una implementación del controlador Ingress para equilibradores de carga GCE, para equilibradores AWS, para servidores proxy populares como nginx y haproxy. Tenga en cuenta que mezclar recursos y servicios de Ingress como
LoadBalancer
puede causar problemas menores en algunos entornos. Son fáciles de manejar, pero, en general, es mejor usar Ingress incluso para servicios simples.
HostPort y HostNetwork
Lo que vamos a hablar ahora, a saber,
HostPort
y
HostNetwork
, puede atribuirse más bien a la categoría de rarezas interesantes, y no a herramientas útiles. De hecho, me comprometo a afirmar que en el 99,99% de los casos su uso puede considerarse un antipatrón, y cualquier sistema en el que se utilicen debe someterse a una verificación obligatoria de su arquitectura.
Pensé que no valía la pena hablar de ellos en absoluto, pero son algo así como las herramientas utilizadas por los recursos de Ingress para procesar el tráfico entrante, así que decidí que valía la pena mencionarlos, al menos brevemente.
Primero,
HostPort
. Esta es una propiedad de contenedor (declarada en la estructura
ContainerPort
). Cuando se escribe un cierto número de puerto, esto conduce a la apertura de este puerto en el nodo y a su redirección directamente al contenedor. No hay mecanismos de representación, y el puerto se abre solo en los nodos en los que se ejecuta el contenedor. En los primeros días de la plataforma, antes de que aparecieran los mecanismos
DaemonSet y
StatefulSet ,
HostPort
era un truco que hacía posible lanzar solo un contenedor de cierto tipo en cualquier nodo. Por ejemplo, una vez usé esto para crear un clúster
HostPort
configurando
HostPort
en
9200
y especificando tantas réplicas como nodos. , Kubernetes, -
HostPort
.
NostNetwork
, , Kubernetes ,
HostPort
.
true
, -
network=host
docker run
. , .
eth0
. , . , , , Kubernetes, - .
Resumen
Kubernetes, , Ingress. , , , Kubernetes.
Estimados lectores! Ingress?
