
Isstio service mesh
En Namely hemos estado usando Istio durante un año. Luego se fue. Tuvimos una gran caída en el rendimiento en el clúster de Kubernetes, queríamos un rastreo distribuido y tomamos a Istio para ejecutar Jaeger y resolverlo. La malla de servicio encaja tan bien en nuestra infraestructura que decidimos invertir en esta herramienta.
Tuve que sufrir, pero lo estudiamos por todas partes. Esta es la primera publicación de una serie donde describiré cómo Istio se integra con Kubernetes y lo que aprendimos sobre su trabajo. A veces nos adentramos en la naturaleza técnica, pero no muy lejos. Además habrá más publicaciones.
¿Qué es istio?
Istio es una herramienta de configuración de malla de servicio. Lee el estado del clúster de Kubernetes y actualiza los proxies L7 (HTTP y gRPC), que se implementan como sidecar en los pods de Kubernetes. Estos sidecar son contenedores de Envoy que leen la configuración de la API Istio Pilot (y el servicio gRPC) y enrutan el tráfico a través de ella. Con el poderoso proxy L7 bajo el capó, podemos usar métricas, trazas, lógica de reintento, disyuntor, equilibrio de carga y despliegues canarios.
Comencemos desde el principio: Kubernetes
En Kubernetes, creamos con un despliegue o StatefulSet. O simplemente puede ser "vainilla" debajo sin un controlador de alto nivel. Entonces Kubernetes hace todo lo posible para mantener el estado deseado: crea pods en el clúster en el nodo, se asegura de que se inicien y reinicien. Cuando se crea un under, Kubernetes pasa por el ciclo de vida de la API, se asegura de que cada paso sea exitoso y solo entonces finalmente crea el under en el clúster.
Etapas del ciclo de vida de la API:

Gracias a Banzai Cloud por la imagen genial.
Uno de los pasos es modificar los webhooks de admisión. Esta es una parte separada del ciclo de vida en Kubernetes, donde los recursos se personalizan antes de comprometerse con el repositorio de etcd, la fuente de verdad para la configuración de Kubernetes. Y aquí Istio hace su magia.
Modificación de webhooks de admisión
Cuando se crea un sub (a través de kubectl
o Deployment
), pasa por este ciclo de vida, y el acceso modificando webhooks lo cambia antes de lanzarlo al gran mundo.
Durante la instalación de Istio, el inyector istio-sidecar se agrega como un recurso de configuración para modificar webhooks:
$ kubectl get mutatingwebhookconfiguration NAME AGE istio-sidecar-injector 87d
Y la configuración:
apiVersion: admissionregistration.k8s.io/v1beta1 kind: MutatingWebhookConfiguration metadata: labels: app: istio-sidecar-injector chart: sidecarInjectorWebhook-1.0.4 heritage: Tiller name: istio-sidecar-injector webhooks: - clientConfig: caBundle: redacted service: name: istio-sidecar-injector namespace: istio-system path: /inject failurePolicy: Fail name: sidecar-injector.istio.io namespaceSelector: matchLabels: istio-injection: enabled rules: - apiGroups: - "" apiVersions: - v1 operations: - CREATE resources: - pods
Aquí dice que Kubernetes debería enviar todos los eventos de creación de istio-sidecar-injector
al servicio istio-sidecar-injector
en el istio-system
nombres istio-system
si el espacio de nombres tiene istio-injection=enabled
. El inyector incluye dos contenedores más en PodSpec: uno temporal para establecer reglas de proxy y otro para proxy. El inyector del sidecar inserta estos contenedores de acuerdo con la plantilla del mapa de configuración del istio-sidecar-injector
del istio-sidecar-injector
. Este proceso también se llama sidecaring.
Vainas Sidecar
Los sidecar son los trucos de nuestro mago Istio. Istio hace todo tan ingeniosamente que desde el exterior es simplemente mágico, si no conoces los detalles. Y es útil conocerlos si de repente necesita depurar solicitudes de red.
Contenedores init y proxy
Kubernetes tiene contenedores de inicio temporales únicos que se pueden ejecutar antes que los principales. Agrupan recursos, migran bases de datos o, como es el caso con Istio, configuran reglas de red.
Istio usa Envoy para representar todas las solicitudes de envíos a lo largo de las rutas deseadas. Para hacer esto, Istio crea reglas de iptables
, y envían el tráfico entrante y saliente directamente a Envoy, y el servidor se envía de forma ordenada al destino. El tráfico hace un pequeño desvío, pero ha distribuido el rastreo, las métricas de consulta y la aplicación de políticas. Este archivo muestra desde el repositorio de Istio cómo Istio crea las reglas de iptables.
@jimmysongio dibujó un excelente diagrama de conexión entre las reglas de iptables y el proxy Envoy:

Enviado - Envoy Traffic
Envoy recibe todo el tráfico entrante y saliente, por lo que todo el tráfico generalmente se mueve dentro de Envoy, como en el diagrama. El proxy de Istio es otro contenedor que se agrega a todos los módulos modificados por el inyector de sidecar Istio. En este contenedor, comienza el proceso Envoy, que recibe todo el tráfico del hogar (con algunas excepciones, como el tráfico de su clúster de Kubernetes).
El proceso de Envoy descubre todas las rutas a través de la API Envoy v2, que implementa Istio.
Enviado y piloto
El enviado en sí no tiene lógica para detectar pods y servicios en un clúster. Es un plano de datos y necesita un plano de control para guiar. El parámetro de configuración Envoy solicita al host o puerto de servicio que reciba esta configuración a través de la API gRPC. Istio, a través de su servicio Piloto, cumple los requisitos para la API gRPC. Envoy se conecta a esta API en función de una configuración de sidecar implementada a través de un webhook modificador. La API tiene todas las reglas de tráfico que Envoy necesita descubrir y enrutar para el clúster. Esta es la malla de servicio.

Intercambio de datos "bajo el piloto <->"
Pilot se conecta al clúster de Kubernetes, lee el estado del clúster y espera actualizaciones. Supervisa los hogares, los servicios y los puntos finales en el clúster de Kubernetes, para luego dar la configuración correcta a todos los sidecar de Envoy conectados al Pilot. Este es el puente entre Kubernetes y Envoy.

De piloto a kubernetes
Cuando se crean o actualizan pods, servicios o puntos finales en Kubernetes, Pilot se entera de ello y envía la configuración necesaria a todas las instancias de Envoy conectadas.
¿Qué configuración se está enviando?
¿Qué configuración obtiene el Envoy del Istio Pilot?
De manera predeterminada, Kubernetes resuelve sus problemas de red con un sevice
(servicio) que administra los endpoint
. La lista de puntos finales se puede abrir con el comando:
kubectl get endpoints
Esta es una lista de todas las direcciones IP y puertos en el clúster y sus direcciones (generalmente son pods creados a partir de una implementación). Es importante saber Istio para configurar y enviar datos de ruta a Envoy.
Servicios, oyentes y rutas.
Cuando crea un servicio en un clúster de Kubernetes, incluye accesos directos mediante los cuales se seleccionarán todos los pods adecuados. Cuando envía tráfico a la dirección IP del servicio, Kubernetes selecciona el tráfico para este tráfico. Por ejemplo, el comando
curl my-service.default.svc.cluster.local:3000
primero encontrará la IP virtual asignada al my-service
en el espacio de nombres default
, y esta IP reenviará el tráfico a un sub que coincida con la etiqueta del servicio.
Istio y Envoy cambian ligeramente esta lógica. Istio configura Envoy en función de los servicios y puntos finales en el clúster de Kubernetes y utiliza las funciones de enrutamiento inteligente y equilibrio de carga de Envoy para omitir el servicio de Kubernetes. En lugar de enviar una IP a la vez, Envoy se conecta directamente al hogar de IP. Para hacer esto, Istio asigna la configuración de Kubernetes a la configuración de Envoy .
Los términos Kubernetes, Istio y Envoy son ligeramente diferentes, y no está claro de inmediato qué comen.
Servicios
Un servicio en Kubernetes se asigna a un clúster en Envoy. El clúster Envoy contiene una lista de puntos finales , es decir, la IP (o nombres de host) de las instancias para procesar solicitudes. Para ver la lista de clústeres configurados en el sidecar-pod de istioctl proxy-config cluster < >
, ejecute istioctl proxy-config cluster < >
. Este comando muestra el estado actual de las cosas en términos del hogar. Aquí hay un ejemplo de uno de nuestros entornos:
$ istioctl proxy-config cluster taxparams-6777cf899c-wwhr7 -n applications SERVICE FQDN PORT SUBSET DIRECTION TYPE BlackHoleCluster - - - STATIC accounts-grpc-gw.applications.svc.cluster.local 80 - outbound EDS accounts-grpc-public.applications.svc.cluster.local 50051 - outbound EDS addressvalidator.applications.svc.cluster.local 50051 - outbound EDS
Todos los mismos servicios están en este espacio de nombres:
$ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) accounts-grpc-gw ClusterIP 10.3.0.91 <none> 80/TCP accounts-grpc-public ClusterIP 10.3.0.202 <none> 50051/TCP addressvalidator ClusterIP 10.3.0.56 <none> 50051/TCP
¿Cómo sabe Istio qué protocolo usa el servicio? Configura protocolos para manifiestos de servicio por el campo de name
en la entrada del puerto.
$ kubectl get service accounts-grpc-public -o yaml apiVersion: v1 kind: Service metadata: name: accounts-grpc-public spec: ports: - name: grpc port: 50051 protocol: TCP targetPort: 50051
Si hay grpc
o el prefijo grpc-
, Istio configurará el protocolo HTTP2 para el servicio. A través de la amarga experiencia, aprendimos cómo Istio usa el nombre del puerto cuando las configuraciones de proxy están dañadas porque no especifican los prefijos http o grpc ...
Si usa kubectl y la página de reenvío del puerto de administración en Envoy, puede ver que Pilot implementa los puntos finales de cuenta-grpc-public como un clúster en Envoy con el protocolo HTTP2. Esto confirma nuestras suposiciones:
$ kubectl -n applications port-forward otherpod-dc56885ff-dqc6t 15000:15000 & $ curl http://localhost:15000/config_dump | yq r - ... - cluster: circuit_breakers: thresholds: - {} connect_timeout: 1s eds_cluster_config: eds_config: ads: {} service_name: outbound|50051||accounts-grpc-public.applications.svc.cluster.local http2_protocol_options: max_concurrent_streams: 1073741824 name: outbound|50051||accounts-grpc-public.applications.svc.cluster.local type: EDS ...
El puerto 15000 es la página de administración de Envoy , disponible en todos los sidecar.
Oyentes
Los oyentes reconocen los puntos finales de Kubernetes para pasar el tráfico a los hogares. El servicio de verificación de dirección tiene un punto final aquí:
$ kubectl get ep addressvalidator -o yaml apiVersion: v1 kind: Endpoints metadata: name: addressvalidator subsets: - addresses: - ip: 10.2.26.243 nodeName: ip-10-205-35-230.ec2.internal targetRef: kind: Pod name: addressvalidator-64885ccb76-87l4d namespace: applications ports: - name: grpc port: 50051 protocol: TCP
Por lo tanto, el hogar de verificación de dirección tiene un oyente en el puerto 50051:
$ kubectl -n applications port-forward addressvalidator-64885ccb76-87l4d 15000:15000 & $ curl http://localhost:15000/config_dump | yq r - ... dynamic_active_listeners: - version_info: 2019-01-13T18:39:43Z/651 listener: name: 10.2.26.243_50051 address: socket_address: address: 10.2.26.243 port_value: 50051 filter_chains: - filter_chain_match: transport_protocol: raw_buffer ...
Rutas
En Istio, en lugar del objeto estándar de Kubernetes Ingress, se VirtualService
un recurso personalizado más abstracto y eficiente: VirtualService
. VirtualService asigna rutas a clústeres aguas arriba al vincularlas a la puerta de enlace. Así es como se usa Kubernetes Ingress con un controlador Ingress.
En Namely, usamos Istio Ingress-Gateway para todo el tráfico interno de GRPC:
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: grpc-gateway spec: selector: istio: ingressgateway servers: - hosts: - '*' port: name: http2 number: 80 protocol: HTTP2 --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: grpc-gateway spec: gateways: - grpc-gateway hosts: - '*' http: - match: - uri: prefix: /namely.address_validator.AddressValidator retries: attempts: 3 perTryTimeout: 2s route: - destination: host: addressvalidator port: number: 50051
A primera vista, no entenderá nada en el ejemplo. Esto no es visible, pero el despliegue Istio-IngressGateway registra qué puntos finales son necesarios en istio: ingressgateway
. En este ejemplo, IngressGateway enruta el tráfico para todos los dominios a través del puerto 80 a través de HTTP2. VirtualService implementa rutas para esta puerta de enlace, coincide con el /namely.address_validator.AddressValidator
prefijo y pasa el addressvalidator
través del puerto 50051 al servicio ascendente addressvalidator
una regla de reintento en dos segundos.
Si redirigimos el puerto de pod de Istio-IngressGateway y vemos la configuración de Envoy, veremos qué hace VirtualService:
$ kubectl -n istio-system port-forward istio-ingressgateway-7477597868-rldb5 15000 ... - match: prefix: /namely.address_validator.AddressValidator route: cluster: outbound|50051||addressvalidator.applications.svc.cluster.local timeout: 0s retry_policy: retry_on: 5xx,connect-failure,refused-stream num_retries: 3 per_try_timeout: 2s max_grpc_timeout: 0s decorator: operation: addressvalidator.applications.svc.cluster.local:50051/namely.address_validator.AddressValidator* ...
Lo que buscamos en Google mientras cavamos en Istio
Se produce el error 503 o 404
Las razones son diferentes, pero generalmente son:
- Las aplicaciones de sidecar no pueden contactar a Pilot (verifique que Pilot esté funcionando).
- El manifiesto de servicio de Kubernetes tiene un protocolo no válido.
- La configuración VirtualService / Envoy escribe la ruta al clúster ascendente incorrecto. Comience con el servicio perimetral, donde espera tráfico entrante, y examine los registros de Envoy. O use algo como Jaeger para encontrar errores.
¿Qué significa NR / UH / UF en los registros proxy de Istio?
- NR - Sin ruta.
- UH - Upstream Insalubre (inoperable upstream).
- UF - Falla aguas arriba (falla aguas arriba).
Lea más en el sitio web de Envoy .
Sobre alta disponibilidad con Istio
- Agregue NodeAffinity a los componentes de Istio para distribuir uniformemente los hogares en diferentes zonas de disponibilidad y aumentar el número mínimo de réplicas.
- Inicie la nueva versión de Kubernetes con la función Horizontal Pod Autoscaling. Los hogares más importantes se escalarán según la carga.
¿Por qué no termina el cronjob?
Cuando se completa la carga de trabajo principal, el contenedor del sidecar continúa funcionando. Para evitar el problema, desactive el sidecar en cronjobs agregando la anotación sidecar.istio.io/inject: “false”
a PodSpec.
¿Cómo instalar Istio?
Usamos Spinnaker para implementaciones, pero usualmente tomamos los últimos gráficos de Helm, los conjuramos, usamos la helm template -f values.yml
y comprometemos archivos en Github para ver los cambios antes de aplicarlos a través de kubectl apply -f -
. Esto es para no cambiar accidentalmente el CRD o la API en diferentes versiones.
Gracias a Bobby Tables y Michael Hamrah por ayudarme a escribir esta publicación.