Introducción
Se ha configurado una nueva funcionalidad en Onesait Platform que permite obtener la trazabilidad distribuida entre sus componentes y otros componentes como los microservicios que pueden invocar a un api Rest de plataforma o a un device client,… y poder ver esta trazabilidad completa desde este microservicio.
Estas trazas pueden verse desde el control panel.
En este caso por ejemplo se mostrarían las trazas de un micro servicio que invoca a otro microservicio y este a su vez a un servicio Rest expuesto desde el Api Manager de plataforma, en las trazas se ve cuanto tiempo se ha empleado en cada span, esto es util por ejemplo para detectar la raíz de problemas como cuellos de botella interrupciones entre componentes,…
¿Qué es la instrumentación?
La instrumentación de una aplicación, microservicio, etc, … es adaptarla para que genere una información de trazas y span que enviará al colector, estas trazas y span comparten un contexto por lo que puede trazarse desde el inicio hasta el fin el camino recorrido, el tiempo en cada punto del camino y además contiene información del tipo clave valor, para luego poder interpretar todo y poder sacar conclusiones.
Tipos de instrumentación
Existen dos tipos de instrumentación, se utiliza Open Telemetry que es la tecnología empleada para crear las trazas y spans y enviarla al colector para su tratamiento, utilizada en la Onesait Platform.
Está la instrumentación Automática, consiste en utilizar el agente de Open Telemetry para que de forma no intrusiva obtener las trazas sin tener que tocar el código del micro servicio, aplicación,.. desde el que se pretendan obtener las trazas, ya que el agente se encarga de escuchar las distintas librerías más importantes que se suelen utilizar y de estas generar las trazas.
Luego también está la instrumentalización Manual, en este caso hay que añadir en la aplicación, micro servicio las librerias con el SDK de Opentelemetry e implementar como se van a generar las distintas trazas. Este caso es más útil cuando se pretenden generar ciertos tipos de trazas con información especifica o más customizada.
Aquí se puede encontrar la información necesaria para poder implementar los dos tipos de instrumentación:
Ejemplos Instrumentación
Para los ejemplo crearemos dos micro servicios a partir de proyectos creados con spring boot de java.
Uno para la instrumentación automática y otro para la manual.
Instrumentación Automática
Para este tipo de instrumentación tan sólo hay que arrancar junto a la aplicación el agente de Open Telemetry, el cual es altamente configurable por parametría.
En el ejemplo más sencillo tan sólo se tendría que especificar donde se encuentra el jar:
-javaagent:/path/opentelemetry-javaagent.jar
El nombre del servicio, así será como aparece en las trazas
-Dotel.resource.attributes=service.name=Onesait-Platform-Microservice
Y finalmente la dirección del colector que recogerá las trazas y spans generados por el agente
-Dotel.exporter.otlp.endpoint:https://lab.onesaitplatform.com/otelcollector/
El agente puede descargarse con esta url
También existen agentes para otras tecnologías no sólo java.
Instrumentación Manual
En este caso habría que añadir código a la aplicación para generar las trazas.
añadir estas dependencias al fichero pom.xml:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-api</artifactId> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk</artifactId> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-otlp</artifactId> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-semconv</artifactId> <version>1.27.0-alpha</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-bom</artifactId> <version>1.27.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Definir cierta información en el fichero application.properties
otel.config.trace-id-ratio-based: 1.0 otel.exporter.otlp.endpoint: https://lab.onesaitplatform.com/otelcollector/ service.name: Onesait-Platform-Microservice
En este caso se configura el ratio base , la url donde se encuentra el colector de OTel y en nombre del servicio
Finalmente en el microservicio lo que se hace es invocar un servicio rest generado con el api manager de plataforma, entonces la finalidad es crear una traza que envuelva esta llamada.
@Autowired private TracingConfiguration tracingConfiguration; private static final Logger log = LoggerFactory.getLogger(ExampleRestController.class); private Tracer tracer; @PostConstruct public void init() { tracer = tracingConfiguration.getOpenTelemetry().getTracer(ExampleRestController.class.getPackageName()); } @Autowired HttpServletRequest request; //@RequestHeader(required = false) HttpHeaders headers @GetMapping(value = "/getAll") public void getAll() throws IOException { request.getHeaderNames(); Context extractedContext = tracingConfiguration.getOpenTelemetry().getPropagators().getTextMapPropagator() .extract(Context.current(), request, Utils.getter); // the span is created try (Scope scope = extractedContext.makeCurrent()) { Span span = tracer.spanBuilder("example span with context propagation").setSpanKind(SpanKind.SERVER) .startSpan(); try (Scope ignored = span.makeCurrent()) { URL url = new URL("https://lab.onesaitplatform.com/api-manager/server/api/v1/customer/"); // from here you would be making a call to a platform rest api that queries the // information of an entity for example HttpURLConnection con = (HttpURLConnection) url.openConnection(); // adding information to the span is optional but recommended span.setAttribute(SemanticAttributes.HTTP_URL, url.toString()); span.setAttribute(SemanticAttributes.HTTP_METHOD, "GET"); span.setAttribute("component", "http"); // Inject the request with the *current* Context, which contains our current // Span. tracingConfiguration.getOpenTelemetry().getPropagators().getTextMapPropagator() .inject(Context.current(), con, Utils.setter); // the necessary information is added to the request con.setRequestProperty("accept", "application/json"); con.setRequestProperty("X-OP-APIKey", "6bfb064cb62548b78d7...."); con.setRequestMethod("GET"); int status = con.getResponseCode(); log.info("status: " + status); } finally { // the span is closed span.end(); } } }
En este ejemplo
Se obtiene la traza
@PostConstruct public void init() { tracer = tracingConfiguration.getOpenTelemetry().getTracer(ExampleRestController.class.getPackageName()); }
Luego se obtiene el contexto por si este microservicio fuese invocado desde otro que ya hubiese creado una traza previa, con esto se consigue que no se parta la traza en fragmentos que partan de distintos servicios y todos parten desde una llamada incial.
Context extractedContext = tracingConfiguration.getOpenTelemetry().getPropagators().getTextMapPropagator() .extract(Context.current(), request, Utils.getter);
Después dentro del contexto se crea un span con información especifica que el desarrollador quiere que se muestre en las trazas
try (Scope scope = extractedContext.makeCurrent()) { Span span = tracer.spanBuilder("example span with context propagation").setSpanKind(SpanKind.SERVER) .startSpan();
Al invocar al servicio rest se añade la información del contexto actual para que el próximo componente de la traza tenga el contexto previo y pueda decidir si crear una traza nueva o continuar con la actual, esto se consigue con los propagators
tracingConfiguration.getOpenTelemetry().getPropagators().getTextMapPropagator() .inject(Context.current(), con, Utils.setter);
Para finalizar es siempre recomendable cerrar el span.
} finally { // the span is closed span.end(); }
Si vamos a las trazas y expandimos el detalle se comprueba que la información especifica añadida por el desarrollador es visible.
Para comprobar que no se pierde el contexto con esta implementación se creo otro micro servicio Onesait-Platform-Microservice2 idéntico al mostrado pero que llamaba a Onesait-Platform-Microservice en lugar de invocar al servicio rest de plataforma, con lo que se muestra en las trazas primero el servicio Onesait-Platform-Microservice2 → Onesait-Platform-Microservice → api-manager .