En los meses previos al Mundial de Fútbol de Rusia, una de las tareas a las que nos abocamos fue a testear nuestra plataformas, sometiéndolas a una demanda concurrente similar a la que esperábamos tener durante el evento, a fin de verificar su performance y resolver con tiempo los posibles issues del sistema.

Estas pruebas suelen detectar cuellos de botella que pueden ser resueltos incrementando los recursos de hardware, modificando configuraciones de los servicios y/o mejorando el código de las aplicaciones.

Los cuellos (y cómo los abrimos)

En Toolbox usamos Kubernetes para orquestar los contenedores Docker que corren las aplicaciones dentro del clúster. Durante las pruebas, detectamos un cuello de botella cuyo origen estaba en Kubernetes Ingress: un intermediario que gestiona los accesos desde el exterior hacia los servicios que hay dentro del clúster.

Ingress provee tanto el balanceo de carga, terminación de SSL y hosting virtual basado en nombres. La necesidad del mismo radica en que, si bien los pods y los servicios (que al fin y al cabo son un conjunto lógico de pods) tienen una IP para acceder a ellos, sólo son “ruteables” desde la red del clúster. Ingress permite traducir los nombres universales de Internet a los nombres de los servicios dentro del clúster. Para que funcione, sin embargo, es necesario desplegar un “controlador de Ingress”. Los hay de distintos proveedores. En Toolbox solemos usar NGINX.

En definitiva, lo que hace este controlador es lo siguiente: cada vez que un servicio externo hace un request a la aplicación, toma las URLs llamadas, las traduce y rutea hacia los pods que deben atender dichos pedidos. Así, por ejemplo, si un usuario quiere acceder a sp.tbxnet.com, NGINX envía la solicitud a los contenedores que corren Cloud Pass dentro del clúster.

NGINX Ingress Controller es un plugin adaptado a Kubernetes que cada vez que se despliega una API y se le asigna un nombre DNS externo, NGINX se autoconfigura y refresca para hacer efectivos los cambios.

Al notar que este plugin actuaba como cuello de botella en las pruebas de Stress, tuvimos que escalarlo. Esto demandó hacer un tuning de ciertas configuraciones, a fin de aumentar la cantidad de instancias que estaban corriendo. También fue necesario atender un bug (documentado) de NGINX, que causaba que el servicio hiciera un refresco de su configuración, aun cuando no se habían hecho cambios o desplegado nuevas aplicaciones.

En paralelo, hubo que tocar la configuración de los sockets de NodeJS: los canales que habilita el sistema operativo para la comunicación de las aplicaciones en sus contenedores. Concretamente, veíamos que cuando el número de conexiones aumentaba, algunas fallaban con el error ECONNRESET. Dado que la cantidad de sockets disponibles es finita, se debe hacer un uso racional de ellos. Para solucionar este problema, fue necesario configurar un límite a la cantidad de sockets que las aplicaciones podían abrir. A su vez, y dado que el proceso de cerrar y volver a abrir estos canales de comunicación es costoso en términos de recursos del sistema, hubo que configurar que cierta cantidad de sockets permaneciera abierta mediante las directivas keepalive y pool.

Mientras que NGINX provee la resolución nombres de dominio para las comunicaciones desde el exterior, existe un servicio (integrado en Kubernetes) llamado DNS que hace lo propio dentro del clúster y de la LAN. Este servicio es compartido por todas las APIs del cluster, y funciona de servidor DNS interno. Al ser interno, acelera los tiempos de respuesta, y habilita lo que se llama service discovery (el descubrimiento automatizado de nuevos servicios). Si tuviéramos una aplicación que está en el cluster y quisiéramos que se comunique con Cloud Pass, en lugar de dirigirla a sp.tbxnet.com (una URL externa), le pedimos que se conecte, por ejemplo, con cloud pass.producción (una dirección dentro del clúster), y el servicio resuelve rápidamente la conexión con Cloud Pass interna.

En este caso, el problema surgía de un frecuente acceso a ciertos servicios externos, que obligaba a llamar al servicio de DNS reiteradas veces y lo saturaba. El equipo de Desarrollo utilizó un caché de ese servicio de DNS, reduciendo así la demanda sobre el servicio y aliviando el cuello de botella.

Esto es sólo un breve listado de las últimas actualizaciones que realizamos en Cloud Pass para soportar la demanda concurrente de los usuarios durante el Mundial de Fútbol. Los aprendizajes que nos dejó este proceso ya fueron incorporadas en las plantillas de desarrollo de nuevas apps, y a las mejores prácticas de trabajo.

Autor: Agustín Castaño, Infraestructura

VOLVER