Introducción
En la release de Q2 de 2023 se ha incluido la capacidad de Tracing distribuido en Plataforma.
Esto nos permite obtener la trazabilidad distribuida entre sus componentes y otros componentes como microservicios que pueden invocar un API REST de plataforma y poder ver esta trazabilidad completa desde este microservicio.
Cómo acceder a la funcionalidad
Estas trazas pueden verse desde el Control Panel en la opción TRACING:
En este ejemplo se muestran las trazas de un microservicio 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, lo cual es útil para detectar cuellos de botella, interrupciones entre componentes,…
¿Cómo configurar mi microservicio para que tracee?
Instrumentación del microservicio
La instrumentación de mi microservicio permitirá que este 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.
En Plataforma se utiliza Open Telemetry para crear las trazas y spans y enviarla al colector para su tratamiento.
Tipos de instrumentación
Existen 2 tipos de instrumentación,
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. Es la que usaremos por defecto.
Luego también está la instrumentalización manual, en este caso hay que añadir en la aplicación, micro servicio las librerías 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.
Ejemplo de Instrumentación
Para los ejemplo crearemos 2 microservicios Spring Boot, uno en el que configuraré 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 .