在快节奏的软件开发领域,尤其是在云原生领域,DevOps 和 SRE 团队正日益成为应用程序稳定性和增长的关键合作伙伴。
DevOps 工程师持续优化软件交付,而 SRE 团队则作为应用程序可靠性、可扩展性和顶级性能的守护者。挑战在于?这些团队需要一个最先进的可观测性解决方案,一个包含全栈洞察力的解决方案,使他们能够在潜在的干扰演变成运营挑战之前快速管理、监控和纠正这些干扰。
在我们现代化的分布式软件生态系统中,可观测性超越了简单的监控——它需要无限的数据收集、精确的处理以及将这些数据关联成可操作的见解。然而,实现这种整体视图的道路充满了障碍:从处理版本不兼容到应对限制性的专有代码。
引入 OpenTelemetry (OTel),为采用它的人带来以下好处
- 使用 OTel 摆脱供应商限制,免受供应商锁定,并确保一流的可观测性。
- 见证统一日志、指标和跟踪的和谐统一,从而提供完整的系统视图。
- 通过更丰富和增强的监控来改进您的应用程序监管。
- 拥抱向后兼容性的优势,以保护您之前的监控投资。
- 踏上 OpenTelemetry 之旅,享受轻松的学习曲线,简化入职和可扩展性。
- 依靠经过验证的、面向未来的标准,增强您对每一项投资的信心。
在本博文中,我们将探讨如何使用 Java 应用程序中的手动监控(使用 Docker),而无需重构应用程序代码的任何部分。我们将使用一个名为 Elastiflix 的应用程序。这种方法比使用 自动监控 稍微复杂一些。
这样做的优势在于,**无需使用 otel-collector**!此设置使您能够根据最适合您业务的时间线,缓慢而轻松地将应用程序迁移到使用 Elastic 的 OTel。
应用程序、先决条件和配置
我们用于本博文的应用程序名为 Elastiflix,一个电影流媒体应用程序。它包含几个用 .NET、NodeJS、Go 和 Python 编写的微服务。
在监控我们的示例应用程序之前,我们首先需要了解 Elastic 如何接收遥测数据。
Elastic 可观测性提供的所有 APM 功能都可用于 OTel 数据。其中一些功能包括
- 服务映射
- 服务详细信息(延迟、吞吐量、失败的事务)
- 服务之间的依赖关系,分布式跟踪
- 事务(跟踪)
- 机器学习 (ML) 关联
- 日志关联
除了 Elastic 的 APM 和统一的遥测数据视图之外,您还可以使用 Elastic 强大的机器学习功能来减少分析,并使用警报来帮助减少 MTTR。
先决条件
- 一个 Elastic Cloud 账户 — 立即注册
- 克隆 Elastiflix 演示应用程序 或您自己的 Java 应用程序
- Docker 的基本了解 — 可能需要安装 Docker Desktop
- Java 的基本了解
查看示例源代码
完整的源代码,包括本博文中使用的 Dockerfile,可以在 GitHub 上找到。该存储库还包含 没有监控的相同应用程序。这使您能够比较每个文件并查看差异。
特别是,我们将逐步处理以下文件
Elastiflix/java-favorite/src/main/java/com/movieapi/ApiServlet.java
以下步骤将向您展示如何在命令行或 Docker 中监控此应用程序并运行它。如果您有兴趣了解更完整的 OTel 示例,请查看此处的 docker-compose 文件 这里,它将启动整个项目。
在开始之前,让我们先看一下未经监控的代码。
分步指南
步骤 0. 登录您的 Elastic Cloud 账户
本博文假设您拥有一个 Elastic Cloud 账户 — 如果没有,请按照 说明在 Elastic Cloud 上开始。
步骤 1. 设置 OpenTelemetry
第一步是在您的 Java 应用程序中设置 OpenTelemetry SDK。您可以从将 OpenTelemetry Java SDK 及其依赖项添加到项目的构建文件(例如 Maven 或 Gradle)开始。在我们的示例应用程序中,我们使用 Maven。将以下依赖项添加到您的 pom.xml 中
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-mdc-1.0</artifactId>
<version>1.25.1-alpha</version>
</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>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp-logs</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-appender-1.0</artifactId>
<version>1.25.1-alpha</version>
</dependency>
并从 OpenTelemetry 添加以下物料清单
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>1.25.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom-alpha</artifactId>
<version>1.25.0-alpha</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
步骤 2. 添加应用程序配置
建议您将以下配置添加到应用程序的主方法中,以便在任何应用程序代码之前启动。这样做可以让您获得更多控制和灵活性,并确保 OpenTelemetry 在应用程序生命周期的任何阶段都可用。在示例中,我们将此代码放在 Spring Boot 应用程序启动之前。Elastic 支持通过 HTTP 和 GRPC 使用 OTLP。在本例中,我们使用 GRPC。
String SERVICE_NAME = System.getenv("OTEL_SERVICE_NAME");
// set service name on all OTel signals
Resource resource = Resource.getDefault().merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME,SERVICE_NAME,ResourceAttributes.SERVICE_VERSION,"1.0",ResourceAttributes.DEPLOYMENT_ENVIRONMENT,"production")));
// init OTel logger provider with export to OTLP
SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder().setResource(resource).addLogRecordProcessor(BatchLogRecordProcessor.builder(OtlpGrpcLogRecordExporter.builder().setEndpoint(System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT")).addHeader("Authorization", "Bearer " + System.getenv("ELASTIC_APM_SECRET_TOKEN")).build()).build()).build();
// init OTel trace provider with export to OTLP
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder().setResource(resource).setSampler(Sampler.alwaysOn()).addSpanProcessor(BatchSpanProcessor.builder(OtlpGrpcSpanExporter.builder().setEndpoint(System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT")).addHeader("Authorization", "Bearer " + System.getenv("ELASTIC_APM_SECRET_TOKEN")).build()).build()).build();
// init OTel meter provider with export to OTLP
SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder().setResource(resource).registerMetricReader(PeriodicMetricReader.builder(OtlpGrpcMetricExporter.builder().setEndpoint(System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT")).addHeader("Authorization", "Bearer " + System.getenv("ELASTIC_APM_SECRET_TOKEN")).build()).build()).build();
// create sdk object and set it as global
OpenTelemetrySdk sdk = OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).setLoggerProvider(sdkLoggerProvider).setMeterProvider(sdkMeterProvider).setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())).build();
GlobalOpenTelemetry.set(sdk);
// connect logger
GlobalLoggerProvider.set(sdk.getSdkLoggerProvider());
// Add hook to close SDK, which flushes logs
Runtime.getRuntime().addShutdownHook(new Thread(sdk::close));
步骤 3. 在 TracingFilter 中创建 Tracer 并启动 OpenTelemetry Span
在 Spring Boot 示例中,您会注意到我们有一个 TracingFilter 类,它扩展了 OncePerRequestFilter 类。此过滤器是放置在请求处理链前端的一个组件。它的主要作用是拦截传入的请求和传出的响应,执行诸如记录、身份验证、转换请求/响应实体等任务。因此,我们在这里所做的是拦截进入 Favorite 服务的请求,以便我们可以提取可能包含来自上游系统的跟踪信息的标头。
我们首先使用 OpenTelemetry Tracer,它是 OpenTelemetry 的核心组件,允许您创建跨度、启动和停止它们,以及添加属性和事件。在您的 Java 代码中,导入必要的 OpenTelemetry 类并在您的应用程序中创建 Tracer 的实例。
我们使用它来创建一个新的下游跨度,它将作为从上游系统中创建的跨度的子跨度继续,使用我们从上游请求中获取的信息。在我们的 Elastiflix 示例中,这将是 nodejs 应用程序。
@Override
protected void doFilterInternal(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, jakarta.servlet.FilterChain filterChain) throws jakarta.servlet.ServletException, IOException {
Tracer tracer = GlobalOpenTelemetry.getTracer(SERVICE_NAME);
Context extractedContext = GlobalOpenTelemetry.getPropagators()
.getTextMapPropagator()
.extract(Context.current(), request, getter);
Span span = tracer.spanBuilder(request.getRequestURI())
.setSpanKind(SpanKind.SERVER)
.setParent(extractedContext)
.startSpan();
try (Scope scope = span.makeCurrent()) {
filterChain.doFilter(request, response);
} catch (Exception e) {
span.setStatus(StatusCode.ERROR);
throw e;
} finally {
span.end();
}
}
步骤 4. 使用跨度监控其他有趣的代码
为了使用 Span 监控代码的特定区域,您可以使用 Tracer 的 SpanBuilder 创建 Span。要准确测量特定操作的持续时间,请确保在代码中的适当位置启动和停止 Span。使用 Tracer 提供的 startSpan 和 endSpan 方法来标记 Span 的开始和结束。例如,您可以在代码中的特定方法或操作周围创建一个 Span,如下面的 handleCanary 方法所示。
private void handleCanary() throws Exception {
Span span = GlobalOpenTelemetry.getTracer(SERVICE_NAME).spanBuilder("handleCanary").startSpan();
Scope scope = span.makeCurrent();
///.....
span.setStatus(StatusCode.OK);
span.end();
scope.close();
}
步骤 5. 向 Span 添加属性和事件
您可以使用额外的属性和事件来增强 Span,从而提供有关正在跟踪的操作的更多上下文和详细信息。属性可以是描述 Span 的键值对,而事件可以用于标记 Span 生命周期中的重要点。这也在 handleCanary 方法中展示。
private void handleCanary() throws Exception {
Span.current().setAttribute("canary", "test-new-feature");
Span.current().setAttribute("quiz_solution", "correlations");
span.addEvent("a span event", Attributes
.of(AttributeKey.longKey("someKey"), Long.valueOf(93)));
}
步骤 6. 监控后端
让我们考虑一个监控 Redis 数据库调用的示例。我们使用 Java OpenTelemetry SDK,我们的目标是创建一个跟踪,捕获每个“发布用户收藏”到数据库的操作。
以下是执行操作并收集遥测数据的 Java 方法
public void postUserFavorites(String user_id, String movieID) {
...
}
让我们逐行分析
初始化 Span
我们方法的第一行重要代码是初始化 Span。Span 表示跟踪中的单个操作,可以是数据库调用、远程过程调用 (RPC) 或您想要测量的任何代码段。
Span span = GlobalOpenTelemetry.getTracer(SERVICE_NAME).spanBuilder("Redis.Post").setSpanKind(SpanKind.CLIENT).startSpan();
设置 Span 属性
接下来,我们向 Span 添加属性。属性是提供有关 Span 额外信息的键值对。为了使后端调用在服务映射中正确显示,为后端调用类型正确设置属性至关重要。在本例中,我们将 db.system 属性设置为 redis。
span.setAttribute("db.system", "redis");
span.setAttribute("db.connection_string", redisHost);
span.setAttribute(
"db.statement",
"POST user_id " + user_id + " AND movie_id " + movieID
);
这将确保对后端 redis 后端的调用按如下所示进行跟踪
捕获操作的结果
然后,我们在 try-catch 块中执行我们感兴趣的操作。如果在操作执行期间发生异常,我们会在 Span 中记录它。
try (Scope scope = span.makeCurrent()) {
...
} catch (Exception e) {
span.setStatus(StatusCode.ERROR, "Error while getting data from Redis");
span.recordException(e);
}
关闭资源
最后,我们关闭 Redis 连接并结束 Span。
finally {
jedis.close();
span.end();
}
步骤 7. 配置日志记录
日志记录是应用程序监控和故障排除的重要组成部分。OpenTelemetry 允许您与现有的日志记录框架(如 Logback 或 Log4j)集成,以与遥测数据一起捕获日志。配置您选择的日志记录框架以捕获与已监控 Span 相关的日志。在我们的示例应用程序中,请查看 logback 配置,其中显示了如何将日志直接导出到 Elastic。
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<appender name="otel-otlp"
class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
<captureExperimentalAttributes>false</captureExperimentalAttributes>
<captureCodeAttributes>true</captureCodeAttributes>
<captureKeyValuePairAttributes>true</captureKeyValuePairAttributes>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="otel-otlp" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
步骤 8. 使用环境变量运行 Docker 镜像
如 OTEL Java 文档 中所述,我们将使用环境变量并将配置值传递进来,以使其能够连接到 Elastic Observability 的 APM 服务器。
因为 Elastic 本机接受 OTLP,所以我们只需要提供 OTEL Exporter 需要发送数据的端点和身份验证,以及一些其他环境变量。
获取 Elastic Cloud 变量
您可以从 Kibana 中的以下路径复制端点和令牌:
您需要复制以下环境变量:
OTEL_EXPORTER_OTLP_ENDPOINT
以及来自以下位置的令牌:
OTEL_EXPORTER_OTLP_HEADERS
构建 Docker 镜像
docker build -t java-otel-manual-image .
运行 Docker 镜像
docker run \
-e OTEL_EXPORTER_OTLP_ENDPOINT="REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT" \
-e ELASTIC_APM_SECRET_TOKEN="REPLACE WITH TOKEN" \
-e OTEL_RESOURCE_ATTRIBUTES="service.version=1.0,deployment.environment=production" \
-e OTEL_SERVICE_NAME="java-favorite-otel-manual" \
-p 5000:5000 \
java-otel-manual-image
您现在可以发出一些请求以生成跟踪数据。请注意,预计这些请求将返回错误,因为此服务依赖于您当前未运行的 Redis 连接。如前所述,您可以找到一个使用 docker-compose 的更完整的示例 此处。
curl localhost:5000/favorites
# or alternatively issue a request every second
while true; do curl "localhost:5000/favorites"; sleep 1; done;
步骤 9. 在 Elastic APM 中探索跟踪和日志
一旦您启动并运行,您可以 ping 已监控服务的端点(在本例中为 /favorites),您应该会看到应用程序出现在 Elastic APM 中,如下所示
它将首先跟踪吞吐量和延迟等关键指标,供 SRE 注意。
深入研究,我们可以看到所有事务的概览。
并查看特定的事务
点击日志,我们会看到日志也被提取过来了。OTel Agent 将自动提取日志并将其与跟踪关联。
这为您提供了跨日志、指标和跟踪的完整可见性!
总结
使用 OpenTelemetry 手动监控您的 Java 应用程序可以让您更好地控制要跟踪和监控的内容。通过遵循本文档中概述的步骤,您可以有效地监控 Java 应用程序的性能、识别问题并深入了解应用程序的整体运行状况。
请记住,OpenTelemetry 是一款强大的工具,正确的监控需要仔细考虑哪些指标、跟踪和日志对于您的特定用例至关重要。尝试不同的配置,利用 OpenTelemetry SDK for Java 文档,并持续迭代以实现应用程序的可观察性目标。
在本博文中,我们讨论了以下内容:
- 如何使用 OpenTelemetry 手动监控 Java
- 如何正确初始化和监控 Span
- 如何在无需收集器的情况下轻松设置来自 Elastic 的 OTLP ENDPOINT 和 OTLP HEADERS
希望这提供了一个易于理解的使用 OpenTelemetry 监控 Java 的演练过程,以及将其跟踪发送到 Elastic 的便捷性。
开发者资源
- Elastiflix 应用程序,使用 OpenTelemetry 监控不同语言的指南
- Python:自动监控,手动监控
- Java:自动监控,手动监控
- Node.js:自动监控,手动监控
- .NET:自动监控,手动监控
- Go:手动监控
- 监控 OpenTelemetry 的最佳实践
通用配置和用例资源
- 在 Elastic 上使用 OpenTelemetry 的独立性
- 使用 Elastic 和 OpenTelemetry 在 Kubernetes 上实现现代可观察性和安全性
- 使用 OpenTelemetry 和 Elastic 进行日志记录的 3 种模型
- 将免费且开放的 Elastic APM 作为 Elastic Observability 部署的一部分添加
- 通过 OpenTelemetry API 在代码中捕获自定义指标,并使用 Elastic
- 使用 OpenTelemetry 和 Elastic 为您的可观察性平台做好未来准备
- Elastic Observability:专为 Kubernetes、OpenTelemetry、Prometheus、Istio 等开放技术而构建
还没有 Elastic Cloud 账户?注册 Elastic Cloud 并尝试我上面讨论的自动监控功能。我很想知道您在使用 Elastic 获取应用程序堆栈可见性方面的体验反馈。
本文档中描述的任何功能或功能的发布和时间安排完全由 Elastic 自行决定。任何当前不可用的功能或功能可能无法按时或根本无法交付。