![](https://s7.51cto.com/oss/202303/08/b995cbf68ee67e841b137187bf2681438c5673.png)
在这篇文章中,我们将在Kubernetes中使用Grafana、Prometheus、Loki、Tempo、OpenTelemetry来搭建可观测性平台。其中Grafana作为操作面板,Prometheus、Loki、Tempo作为数据源,分别用来获取指标、日志以及跟踪数据。同时,我们还将使用Exemplars将trace_id与Java指标相关联,使用OpenTelemetry对应用进行检测。
在开始之前,先简单介绍一下这些开源工具。
- OpenTelemetry:它是CNCF的 开源产品,通过使用代理来收集指标、日志和链路,然后将它们发送给其他工具,它支持多种语言集成,并且有很大的仪表功能。
- Prometheus:CNCF的毕业产品,是目前主流的监控工具之一。
- Examplars:它可以将trace_id和metrics联系起来,可以帮助我们通过指标获取到具体日志以及链路状况,通常和Prometheus配合工作。
- Promtail:日志收集工具,将日志发送到Loki。
- Loki:收集并处理日志,并且支持通过LogQL来查询日志,其语法和PromQL类似
- Tempo:接收OpenTelemetry的数据,并且可以通过Jaeger将其可视化
- Grafana:支持多种数据源的可视化面板
![图片 图片](https://s3.51cto.com/oss/202303/08/46d8b526169324f3562425b6e2a5fb70c83471.png)
准备后端应用程序
在这个示例中,我们将使用java spring boot项目作为例子。
首先,我们使用start.spring.io创建一个java spring boot项目,它可以帮我们快速创建一个Java项目,并且支持在项目中添加依然和其他配置。
![图片 图片](https://s3.51cto.com/oss/202303/08/c6f3b8b09e4b5f3c59d284a7121b756aae858e.png)
其中:
- 使用Gradle作为构建自动化工具
- 使用2.7版本的Spring Boot
- 使用JAR作为包构建格式
- 使用JDK11
当完成配置并生成之后,就可以将其压缩包下载下来并用IDE打开。
![图片 图片](https://s5.51cto.com/oss/202303/08/19130a2972eeed465db833abed0d28bfb901dd.png)
我们先配置build.gradle,确保所有依赖是没问题的。
plugins {
id 'org.springframework.boot' version '2.7.0'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
repositories {
maven {
url = uri('https://repo.spring.io/libs-snapshot')
}
mavenCentral()
}
dependencyManagement {
imports {
mavenBom 'io.micrometer:micrometer-bom:1.9.0-SNAPSHOT'
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus:1.9.0'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'io.opentelemetry:opentelemetry-api:1.12.0'
}
tasks.named('test') {
useJUnitPlatform()
}
group = 'com.staz'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
然后我们将创建一个控制器类Controller.java,有两个端点:/fail 和 /success。该文件必须位于${project}/src/main/java/com/staz/observability/的路径下。
package com.staz.observability;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Controller {
@PostMapping("/fail")
public String fail() {
return "Fail!";
}
@GetMapping("/success")
public String success() {
return "Success!";
}
}
为了将metrics和trace_id关联起来,我们需要在${project}/src/main/java/com/staz/observability/路径下创建一个公共配置类PrometheusExemplarConfiguration.java。
package com.staz.observability;
import io.micrometer.core.instrument.Clock;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.opentelemetry.api.trace.Span;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.exemplars.DefaultExemplarSampler;
import io.prometheus.client.exemplars.tracer.otel_agent.
OpenTelemetryAgentSpanContextSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class PrometheusExemplarConfiguration {
@Bean
public PrometheusMeterRegistry prometheusMeterRegistryWithExemplar
(PrometheusConfig prometheusConfig, CollectorRegistry collectorRegistry,
Clock clock) {
return new PrometheusMeterRegistry(prometheusConfig, collectorRegistry,
clock, new DefaultExemplarSampler(new OpenTelemetryAgentSpanContextSupplier() {
@Override
public String getTraceId() {
if (!Span.current().getSpanContext().isSampled()) {
return null;
}
return super.getTraceId();
}
})
);
}
}
最后,编辑${project}/src/main/resources/目录下的配置文件application.yml:
# Enable Actuator endpoints including Prometheus
management:
endpoints:
web:
exposure:
include: health, info, prometheus
metrics:
# Exemplar metrics
distribution:
percentiles-histogram:
http.server.requests: true
minimum-expected-value:
http.server.requests: 5ms
maximum-expected-value:
http.server.requests: 1000ms
# Add trace_id in log. OpenTelemetry set this value using logger-mdc.
# https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/logger-mdc-instrumentation.md
logging:
pattern:
level: '%prefix(%mdc{trace_id:-0}) %5p'
如果想要在本地运行项目,需要下载OpenTelemetry Agent,该项目中使用的版本是1.12.1。
准备工作做完过后,我们在本地来测试一下。
首先,使用gradle build -x test编译项目。
![图片 图片](https://s3.51cto.com/oss/202303/08/f22a2ed0000ea1fcdea47793caffd20058a17b.png)
然后使用以下命令启动:
java -javaagent:opentelemetry-javaagent.jar -Dspring.config.locatinotallow=src/main/resources/application.yml -jar build/libs/observability-0.0.1-SNAPSHOT.jar
![图片 图片](https://s9.51cto.com/oss/202303/08/73499219496c03e20df106094034771b6efb17.png)
然后可以使用htttp://localhost:8080/fail和htttp://localhost:8080/success进行访问测试。
![图片 图片](https://s4.51cto.com/oss/202303/08/287a8aa548ab239f4cb5234f26901305e3aac5.png)
再来使用localhost:8080/actuator/prometheus来验证Prometheus指标是否有效。
![图片 图片](https://s9.51cto.com/oss/202303/08/c72135160167fd9291d5274116bfdf3cad6b88.png)
最后,验证metrice和trace_id的关联情况。
curl -H 'Accept: application/openmetrics-text; versinotallow=1.0.0; charset=utf-8' http://localhost:8080/actuator/prometheus | grep trace_id
![图片 图片](https://s9.51cto.com/oss/202303/08/198d99c691d3594ebb115003b5cddbe637aea6.png)
我们的Spring Boot应用程序已经准备好了,现在我们需要安装观察性工具。在此之前,我们会在本地创建一个K3s集群,所有的软件都将部署到里面。
容器化应用程序
首先,在项目根目录创建一个Dockerfile,内容如下:
# Download OpenTelemetryAgent
FROM curlimages/curl:7.81.0 AS OTEL_AGENT
ARG OTEL_AGENT_VERSION="1.12.1"
RUN curl --silent --fail -L "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v${OTEL_AGENT_VERSION}/opentelemetry-javaagent.jar" \
-o "/tmp/opentelemetry-javaagent.jar"
# Build .JAR file
FROM gradle:7.1.1-jdk11-hotspot AS BUILD_IMAGE
COPY --chown=gradle:gradle . /home/gradle/src
WORKDIR /home/gradle/src
RUN gradle build -x test --no-daemon
# Final image copying OTEL Agent and .JAR File
FROM gradle:7.1.1-jdk11-hotspot
ENV TIME_ZONE America/Lima
ENV TZ=$TIME_ZONE
ENV JAVA_OPTS "-Dspring.config.locatinotallow=src/main/resources/application.yml"
COPY --from=OTEL_AGENT /tmp/opentelemetry-javaagent.jar /otel-javaagent.jar
COPY --from=BUILD_IMAGE home/gradle/src/build/libs/*.jar app.jar
ENTRYPOINT exec java -javaagent:/otel-javaagent.jar -jar app.jar
使用以下命令构建并测试:
$ docker build --no-cache -t otel-springboot-prometheus .
$ docker run -it -p 8080:8080 otel-springboot-prometheus
待容器启动过后,使用http://localhost:8080/success验证是否可以正常使用。
创建单节点集群
首先,使用multipass创建一个Ubuntu实例:
$ multipass launch --name demo --mem 4G --disk 20G
![图片 图片](https://s3.51cto.com/oss/202303/08/d1fbb8e672c45befcc00245befa76c6ef8c117.png)
然后登录实例:
![图片 图片](https://s3.51cto.com/oss/202303/08/64b046a07345116f9e1833657bf5cbfdbf328d.png)
可以通过sudo su命令验证是否正确进去Ubuntu实例。
其次,使用以下命令安装K3s:
$ curl -sfL https://get.k3s.io | sh -
集群创建完成后,将KUBECONFIG添加到环境变量。
$ export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
检查集群是否正常运行。
![图片 图片](https://s3.51cto.com/oss/202303/08/29b645806c42f0d05808758e3c47fe19fe0384.png)
然后,安装Helm,后续都将使用它来安装应用软件。
$ snap install helm --classic
将K3s的KUBECONFIG拷贝到~/.kube/config目录下。
$ kubectl config view --raw > ~/.kube/config
最后,检查Helm是否能正常工作。
![图片 图片](https://s4.51cto.com/oss/202303/08/74c9a2981638c49249a0021012005dde280cbd.jpg)
部署可观测性组件
在该阶段,我们将使用Helm部署Prometheus、Promtail、Loki、Tempo以及Grafana,最后部署应用并验证。
以上应用都将部署到K3s中。
首先,从仓库把需要的manifests克隆下来。
$ git clone https://github.com/stazdx/otel-springboot-grafana-tools.git
$ cd otel-springboot-grafana-tools/kubernetes
然后,添加Helm仓库。
$ helm repo add grafana https://grafana.github.io/helm-charts
$ helm repo update
![图片 图片](https://s5.51cto.com/oss/202303/08/c9bf06569d3203871fa927cf8947859aaa20ad.png)
最后,创建一个namespace,所有应用都部署到该namespace下。
$ kubectl create ns observability
![图片 图片](https://s9.51cto.com/oss/202303/08/5346a5d1898e98cbdef78636f82081b5caf436.png)
部署Promtail
使用以下命令进行部署:
$ cd promtail
$ helm upgrade --install promtail grafana/promtail -n observability -f promtail.yaml
![图片 图片](https://s4.51cto.com/oss/202303/08/723707d95f77715e5a94460e779f37446b119d.png)
注意检查Promtail所指向的Loki地址。
部署Loki
部署命令如下:
$ helm upgrade --install loki grafana/loki-distributed -n observability
![图片 图片](https://s3.51cto.com/oss/202303/08/e5eed1c7055df10435e116ec230338be2cb56d.png)
loki-loki-distributed-gateway这个Service非常重要,Promtail将向它发送数据,Grafana将通过它获取数据。
部署Tempo
首先,进入Tempo清单所在的目录:
在安装Tempo之前,我们需要先安装minio,命令如下:
$ kubectl apply -f minio.yaml
![图片 图片](https://s6.51cto.com/oss/202303/08/13427c71026af3bf0ed4729ddbf62537cf5156.png)
Minio被部署在default命名空间中,因为它是一个更通用的对象存储工具,而不是直接用于观察性。
现在,使用以下命令部署Tempo:
$ helm upgrade --install tempo grafana/tempo-distributed -n observability -f tempo.yaml
![图片 图片](https://s2.51cto.com/oss/202303/08/92be64439032b7165ad2807d76e10ee42a0d59.png)
!! Grafana将通过_tempo-tempo-distributed-query-frontend:3100_来获取数据。
部署Prometheus和Grafana
Prometheus和Grafana直接使用官网仓库进行部署。
首先,添加Helm仓库。
$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update
然后,使用仓库清单进行部署。
$ cd ../prometheus-grafana
$ helm dependency update
helm upgrade --install kube-prometheus-stack -n observability .
![图片 图片](https://s6.51cto.com/oss/202303/08/d45a7dc160388ef03c530253f4b05aa2091026.png)
检查Deployments
使用Helm命令查看部署的所有应用。
$ helm ls -n observability
![图片 图片](https://s9.51cto.com/oss/202303/08/722cb84782a95b1a020139060bd2cac263ceb1.png)
然后,使用kubectl检查应用是否都启动成功。
$ kubectl get po -n observability
![图片 图片](https://s5.51cto.com/oss/202303/08/7397a4943b61574ecae0634bcb4cbf365441f1.png)
检查Service是否正常。
$ kubectl get svc -n observability
![图片 图片](https://s8.51cto.com/oss/202303/08/c51aaf5105eb59c6ef27688cfc0dad49c853dd.png)
我们看到所有应用都正常部署完成。
部署后端应用
直接到仓库目录清单部署即可。
需要注意的是,为了能够让Prometheus能够正常抓取指标,我们需要添加以下Annotations
。
annotations:
# Annotations for Prometheus - scrape config
prometheus.io/path: '/actuator/prometheus'
prometheus.io/port: 'actuator'
prometheus.io/scrape: 'true'
另外一个重要的配置就是OpenTelemetry配置,如下:
env:
- name: SERVER_PORT
value: '8080'
- name: MANAGEMENT_SERVER_PORT
value: '8081'
# Setting OTEL_EXPORTER_METRICS: none - Default: OTLP
- name: OTEL_METRICS_EXPORTER
value: none
- name: OTEL_TRACES_EXPORTER
value: otlp,logging
# Setting Tempo Distributor Service using GRPC Port -> 4317
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: http://tempo-tempo-distributed-distributor.observability.svc.cluster.local:4317
- name: OTEL_SERVICE_NAME
value: springboot-app
- name: KUBE_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: OTEL_RESOURCE_ATTRIBUTES
value: app=springboot-app
最后,我们有一个包含Spring Boot的Grafana仪表盘的配置图,这将使我们能够通过请求延迟等指标看到Exemplar与Tempo的关联。
配置检查无误后,就可以进行部署了。
$ kubectl apply -f springboot-app.yaml
![图片 图片](https://s6.51cto.com/oss/202303/08/039cab8527927da3b7e44169029eacaa061903.png)
检查应用是否部署成功。
$ kubectl get deploy,svc,cm -l app=springboot-app
![图片 图片](https://s6.51cto.com/oss/202303/08/19822c656810547b0d361679587b773634a090.png)
接口测试
首先,测试/fail接口:http://{external-ip}:8080/fail。
![图片 图片](https://s3.51cto.com/oss/202303/08/c65d4724051fdbfe6c2970246f34818ea36141.png)
然后,测试/success接口:http://{external-ip}:8080/success。
![图片 图片](https://s6.51cto.com/oss/202303/08/a6066777076844b9b9025952d3960d0493ef5a.png)
最后,测试/actuator/prometheus接口:http://{external-ip}:8081/actuator/prometheus。
![图片 图片](https://s3.51cto.com/oss/202303/08/b24c32767ac94c08ca25130580f5ed9b734632.png)
可以看到所有接口返回正常。
Grafana测试
上面以及完成了所有的配置,接下来就在Grafana中验证是否能够正常使用。
首先,获取Grafana的访问地址。
$ kubectl get svc -n observability
![图片 图片](https://s2.51cto.com/oss/202303/08/51b94a113fcf5c7a2c9420164909c519b18463.png)
在浏览器输入地址http://{external-ip}:32656。
![图片 图片](https://s6.51cto.com/oss/202303/08/8747f4d795c738b96ba04743e9f6db9f08e757.png)
然后,添加数据源。
![图片 图片](https://s7.51cto.com/oss/202303/08/d87809677703a1d5ba1554c897e4bc3870e23a.png)
我们把Prometheus、Loki以及Tempo数据源都添加上。
![图片 图片](https://s2.51cto.com/oss/202303/08/69464c84009f876ed15071cd377ea9599f7463.png)
其中,Prometheus的配置如下:
![图片 图片](https://s4.51cto.com/oss/202303/08/76f31a4014237b42d315171af9810572e2fb23.png)
!! 可以看到Prometheus和Tempo通过Exemplars进行关联了。
Loki的配置如下:
![图片 图片](https://s4.51cto.com/oss/202303/08/75dfc667402c93734d03218782b639e44ddd05.png)
!! 可以看到Loki和Tempo通过trace_id进行关联了。
Tempo的配置如下:
![图片 图片](https://s8.51cto.com/oss/202303/08/294085271d1167de48b5012975fa2424bdb45c.png)
!! 在这里我们将Tempo与Loki相关联,并映射我们在微服务中配置的应用标签。
测试一下
通过Explore可以查看应用日志。
![图片 图片](https://s9.51cto.com/oss/202303/08/a1081cf78487fb89c5d419007347bc183dd4fe.png)
选择Loki数据源。
![图片 图片](https://s3.51cto.com/oss/202303/08/f190fe1954ea947977d364bbcc3221d1715e45.png)
通过Loki,我们可以通过label对监控日志进行过滤。
![图片 图片](https://s2.51cto.com/oss/202303/08/a9c126e6335696d2ff7844fe7cf87014c4570a.png)
从日志中,我们可以看到trace信息。
![图片 图片](https://s3.51cto.com/oss/202303/08/f8461b1484023752e66168bc05e3913c63b526.png)
然后,我们查看Grafana面板。
![图片 图片](https://s6.51cto.com/oss/202303/08/1813aa130f8e2d54f31143cc07a16ebd6d2d2f.png)
我们选择Spring Boot Demo,它是我们自己创建的面板。
![图片 图片](https://s6.51cto.com/oss/202303/08/c32a998464176387e39380525f6580edcc4e64.png)
我们可以看到应用的请求延迟,另外星星是由Exemplar生成。
![图片 图片](https://s6.51cto.com/oss/202303/08/d1464c1669b1b5909d0453c58c0bcce627fe00.png)
![图片 图片](https://s8.51cto.com/oss/202303/08/76a65d699730196ff13958b500f3c8dba3e17a.png)
用鼠标悬停在它上面,我们可以看到它是如何与一个trace_id相关联的,当点击它时,它将把我们重定向到Tempo。
![图片 图片](https://s3.51cto.com/oss/202303/08/07b1c32307138259c0835938597ec7d27ff794.png)
我们可以看到它产生的跟踪,我们也可以看到日志,因为它也是与Loki相关的,当点击时我们会看到具体信息:
![图片 图片](https://s6.51cto.com/oss/202303/08/333213540f32e78bfb56738bfb08246b76a3c4.png)
屏幕被分割,但是我们可以看到具体的日志了。
最后
我们实现了指标、日志和跟踪之间的可观察性关联。这可以帮助我们在微服务的故障排除过程中,识别瓶颈,看到我们的应用指标的行为,并能够获得特定的跟踪和日志。