
在這篇文章中,我們將在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:支持多種數據源的可視化面板

準備后端應用程序
在這個示例中,我們將使用java spring boot項目作為例子。
首先,我們使用start.spring.io創建一個java spring boot項目,它可以幫我們快速創建一個Java項目,并且支持在項目中添加依然和其他配置。

其中:
- 使用Gradle作為構建自動化工具
- 使用2.7版本的Spring Boot
- 使用JAR作為包構建格式
- 使用JDK11
當完成配置并生成之后,就可以將其壓縮包下載下來并用IDE打開。

我們先配置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編譯項目。

然后使用以下命令啟動:
java -javaagent:opentelemetry-javaagent.jar -Dspring.config.locatinotallow=src/main/resources/application.yml -jar build/libs/observability-0.0.1-SNAPSHOT.jar

然后可以使用htttp://localhost:8080/fail和htttp://localhost:8080/success進行訪問測試。

再來使用localhost:8080/actuator/prometheus來驗證Prometheus指標是否有效。

最后,驗證metrice和trace_id的關聯情況。
curl -H 'Accept: application/openmetrics-text; versinotallow=1.0.0; charset=utf-8' http://localhost:8080/actuator/prometheus | grep trace_id

我們的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

然后登錄實例:

可以通過sudo su命令驗證是否正確進去Ubuntu實例。
其次,使用以下命令安裝K3s:
$ curl -sfL https://get.k3s.io | sh -
集群創建完成后,將KUBECONFIG添加到環境變量。
$ export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
檢查集群是否正常運行。

然后,安裝Helm,后續都將使用它來安裝應用軟件。
$ snap install helm --classic
將K3s的KUBECONFIG?拷貝到~/.kube/config目錄下。
$ kubectl config view --raw > ~/.kube/config
最后,檢查Helm是否能正常工作。

部署可觀測性組件
在該階段,我們將使用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

最后,創建一個namespace,所有應用都部署到該namespace下。
$ kubectl create ns observability

部署Promtail
使用以下命令進行部署:
$ cd promtail
$ helm upgrade --install promtail grafana/promtail -n observability -f promtail.yaml

注意檢查Promtail?所指向的Loki地址。
部署Loki
部署命令如下:
$ helm upgrade --install loki grafana/loki-distributed -n observability

loki-loki-distributed-gateway這個Service非常重要,Promtail將向它發送數據,Grafana將通過它獲取數據。
部署Tempo
首先,進入Tempo清單所在的目錄:
在安裝Tempo之前,我們需要先安裝minio,命令如下:
$ kubectl apply -f minio.yaml

Minio被部署在default命名空間中,因為它是一個更通用的對象存儲工具,而不是直接用于觀察性。
現在,使用以下命令部署Tempo:
$ helm upgrade --install tempo grafana/tempo-distributed -n observability -f tempo.yaml

!! 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 .

檢查Deployments
使用Helm命令查看部署的所有應用。
$ helm ls -n observability

然后,使用kubectl檢查應用是否都啟動成功。
$ kubectl get po -n observability

檢查Service是否正常。
$ kubectl get svc -n observability

我們看到所有應用都正常部署完成。
部署后端應用
直接到倉庫目錄清單部署即可。
需要注意的是,為了能夠讓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

檢查應用是否部署成功。
$ kubectl get deploy,svc,cm -l app=springboot-app

接口測試
首先,測試/fail?接口:http://{external-ip}:8080/fail。

然后,測試/success?接口:http://{external-ip}:8080/success。

最后,測試/actuator/prometheus?接口:http://{external-ip}:8081/actuator/prometheus。

可以看到所有接口返回正常。
Grafana測試
上面以及完成了所有的配置,接下來就在Grafana中驗證是否能夠正常使用。
首先,獲取Grafana的訪問地址。
$ kubectl get svc -n observability

在瀏覽器輸入地址http://{external-ip}:32656。

然后,添加數據源。

我們把Prometheus、Loki以及Tempo數據源都添加上。

其中,Prometheus的配置如下:

!! 可以看到Prometheus和Tempo通過Exemplars進行關聯了。
Loki的配置如下:

!! 可以看到Loki和Tempo通過trace_id進行關聯了。
Tempo的配置如下:

!! 在這里我們將Tempo與Loki相關聯,并映射我們在微服務中配置的應用標簽。
測試一下
通過Explore可以查看應用日志。

選擇Loki數據源。

通過Loki,我們可以通過label對監控日志進行過濾。

從日志中,我們可以看到trace信息。

然后,我們查看Grafana面板。

我們選擇Spring Boot Demo,它是我們自己創建的面板。

我們可以看到應用的請求延遲,另外星星是由Exemplar生成。


用鼠標懸停在它上面,我們可以看到它是如何與一個trace_id相關聯的,當點擊它時,它將把我們重定向到Tempo。

我們可以看到它產生的跟蹤,我們也可以看到日志,因為它也是與Loki相關的,當點擊時我們會看到具體信息:

屏幕被分割,但是我們可以看到具體的日志了。
最后
我們實現了指標、日志和跟蹤之間的可觀察性關聯。這可以幫助我們在微服務的故障排除過程中,識別瓶頸,看到我們的應用指標的行為,并能夠獲得特定的跟蹤和日志。