Seguimiento distribuido para NodeJS usando Elastic APM
Durante mucho tiempo hubo dos opciones principales entre las que elegir, Zipkin o Jaeger, pero actualmente hay un competidor que es relativamente nuevo y que forma parte del stack de Elastic, llamado APM. Al usar Elastic APM, es posible almacenar información de seguimiento distribuida dentro de Elasticsearch y visualizarla en Kibana.
Primero lo primero, seguramente algunos ya conocen o han escuchado hablar sobre Elasticsearch o Kibana, quizás el término ELK les suena conocido y ahora se une un nuevo miembro en la familia, conocido por sus siglas en inglés como APM.
Pero qué es APM?
APM (Application Performance Management) es un software que nos permite descubrir aplicaciones, mostrar la experiencia de usuario, trazar y diagnosticar problemas a nivel de aplicación. Es una herramienta esencial para ayudar a los equipos tanto de desarrollo como de sistemas a optimizar y monitorizar el rendimiento de una aplicación.
Y qué valores mide un APM?
Los APM recogen y cuantifican cientos de valores para luego poder medir el rendimiento de una aplicación. Los más básicos pueden ser valores como la CPU, RAM, escrituras/lecturas disco o algunos más complejos como los siguientes:
Errores de aplicación: los APM tienen capacidad de filtrar los errores de aplicación y de mostrar cuando y cuantas veces han ocurrido
- Tiempos de respuesta de aplicación.
- Trazas lentas: en función de la configuración especificada al APM podemos ver las peticiones lentas y trazarlas internamente.
- Queries de Base de Datos: tiempos medios y por query de ejecución en BBDD, lo que nos permite saber si tenemos queries que pueden estar impactando en la aplicación y que son mejorables.
- Tiempos de respuesta de las llamadas externas: tiempos de respuesta en llamadas a proveedores externos como pasarelas de pago, webservice, etc.
- Experiencia de usuario: los APM mas completos permiten trazar la experiencia de usuario en la página web.
Pero detengámonos aquí, porque este artículo no se trata sobre APM, se trata de como podemos utilizar Elastic APM para almacenar información sobre nuestras aplicaciones con nodejs, así es que regresemos a lo que importa, enfoquémonos en los conceptos que se necesitan conocer para poner en marcha un servidor de APM.
1. Los agentes de APM
Los agentes de APM son en realidad parte de sus servicios que recopilan y publican datos de seguimiento en el servidor de APM. El equipo de Elastic ya ha creado un grupo de agentes para los lenguajes de programación más populares (por ejemplo, Go, Node.JS, .NET, Java, etc.). La lista completa de agentes admitidos y los documentos correspondientes se pueden encontrar en la documentación de APM.
2. El servidor de APM
APM Server es responsable de procesar las solicitudes de los agentes. Realiza validaciones, escaneos de seguridad, crea documentos válidos y los almacena en índices de Elasticsearch, es importante recalcar que Elasticsearch no es lo mismo que APM y que levantando Elasticsearch, no se levantará APM puesto que son aplicaciones diferentes.
Un ejemplo sencillo entonces de como instalar y poner en marcha un servidor de APM seria el siguiente (esto es solo un ejemplo y la configuración puede variar según el OS):
curl -L -O https://artifacts.elastic.co/downloads/apm-server/apm-server-7.9.3-amd64.deb
sudo dpkg -i apm-server-7.9.3-amd64.deb
apm-server -e -E output.elasticsearch.hosts=localhost:9200 -E apm-server.host=localhost:8200
Aunque aquí se explica mejor todo el proceso de puesta en marcha de APM server.
3. Elasticsearch
Es un motor de análisis y búsqueda distríbuido. En palabras simples, puede tratarlo como una especie de base de datos y toda la información enviada al servidor de APM se almacenará en Elasticsearch.
4. Kibana
Es la plataforma de visualización enriquecida que funciona con Elasticsearch, dicho de otra forma, es básicamente el cliente interfaz web que nos permitirá ver la información almacenada en Elasticsearch.
Agregar agente APM al servicio NodeJS
Es increíblemente fácil agregar un agente de APM a un servicio existente y requiere una cantidad mínima de cambios. En primer lugar, debe instalarse el paquete que nos permitira crear un agente que se conectara al servidor de APM (usando el paquete elastic-apm-node).
npm install elastic-apm-node --save
El siguiente paso es agregar e iniciar el agente.
const apm = require('elastic-apm-node');
apm.start({
serviceName: 'service-tracking',
serverUrl: 'http://localhost:8200',
});
Es importante que el agente se inicie antes de que ejecutar cualquier otro módulo en su aplicación Node.js y específicamente en este ejemplo, el agente ahora supervisará el rendimiento de tu aplicación y registrará las excepciones no detectadas.
Me gustaría mencionar que la función de inicio puede incluir una cantidad sorprendente de configuraciones. Por lo tanto, es posible realizar una configuración detallada de su agente. El equipo de Elastic hizo un gran trabajo con la configuración al proporcionar un enfoque bastante flexible. Por tanto, es posible utilizar la configuración global, las variables de entorno y la configuración mediante código.
Como se ve la integración (el demo)
Ahora he preparado un pequeño demo para que veamos como funciona APM y algunas de sus características:
const apm = require('elastic-apm-node');
const http = require('http');
apm.start({
serviceName: 'service-tracking',
serverUrl: 'http://localhost:8200',
});
const requestListener = (req, res) => {
const trans = apm.startTransaction('prueba', 'tipo_1');
const num = Math.floor(Math.random() * Math.floor(1000));
if (num % 2) {
trans.result = 'error';
apm.captureError(new Error('Ups, rompimos algo!'));
}
trans.result = 'success';
res.writeHead(200);
res.end('Hola mundo!');
trans.end();
};
const server = http.createServer(requestListener);
server.on('error', (e) => {
console.log(e);
});
server.listen(8080);
Yo porque me gusta exagerar, decidí poner a prueba nuestro mini servidor y lo he estresado utilizando Apache Bench utilizando el siguiente comando:
ab -k -c 1000 -n 50000 http://localhost:8080/
Y ahora, si abres en tu navegador web la dirección http://localhost:5601/app/apm deberías ser capaz de ver que se ha creado un servicio llamado service-tracking que al hacer click verás que contiene transacciones, errores y métricas.
No entraré mucho en detalles sobre como funciona cada uno de los módulos de APM porque ese es tema para otro artículo, pero la idea principal es que vean la capacidad de seguimiento que pueden lograr utilizando APM.
Conclusión
La solución Elastic APM es bastante adecuada y se puede adoptar fácilmente debido a los agentes APM ya implementados con todo tipo de "magia" en su interior que minimizan los esfuerzos requeridos por los desarrolladores de servicios. Además, esta solución elimina los pasos innecesarios para agregar recursos adicionales para la visualización si Kibana ya está implementado.
Bonus point
Si de algo sirve y quieren hacer sus propias pruebas, dejaré una copia del docker-compose.yml que utilicé para este artículo.
version: '3'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.6.1
container_name: elasticsearch
restart: always
volumes:
- ./elastic:/usr/share/elasticsearch/data
ports:
- '9300:9300'
- '9200:9200'
environment:
- bootstrap.memory_lock=true
- 'ES_JAVA_OPTS=-Xms512m -Xmx512m'
- discovery.type=single-node
- discovery.seed_hosts=es02,es03
- node.name=es01
ulimits:
memlock:
soft: -1
hard: -1
networks:
- local
kibana:
image: docker.elastic.co/kibana/kibana:7.6.1
container_name: kibana
restart: always
ports:
- 5601:5601
environment:
ELASTICSEARCH_URL: http://elasticsearch:9200
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
networks:
- local
apm:
image: docker.elastic.co/apm/apm-server:7.9.3
container_name: apm
restart: always
depends_on:
- elasticsearch
- kibana
cap_add: ["CHOWN", "DAC_OVERRIDE", "SETGID", "SETUID"]
cap_drop: ["ALL"]
ports:
- 8200:8200
networks:
- local
command: >
apm-server -e
-E apm-server.rum.enabled=true
-E setup.kibana.host=kibana:5601
-E setup.template.settings.index.number_of_replicas=0
-E apm-server.kibana.enabled=true
-E apm-server.kibana.host=kibana:5601
-E output.elasticsearch.hosts=["elasticsearch:9200"]
healthcheck:
interval: 10s
retries: 12
test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null http://localhost:8200/
networks:
local:
driver: bridge
Happy coding!
Photo by Franki Chamaki on Unsplash