乔克
乔克
Published on 2024-11-15 / 21 Visits
0
0

在Kubernetes中从0打造可观测性

原文:https://k8staz.com/setting-up-open-source-observability-stack-on-kubernetes-from-scratch#heading-testing-endpoints
作者:Staz

在这篇文章中,我们将在 Kubernetes 中使用 Grafana、Prometheus、Loki、Tempo、OpenTelemetry 来搭建可观测性平台。其中 Grafana 作为操作面板,Prometheus、Loki、Tempo 作为数据源,分别用来获取指标、日志以及跟踪数据。同时,我们还将使用 Exemplars 将 trace_id 与 Java 指标相关联,使用 OpenTelemetry 对应用进行检测。

在开始之前,先简单介绍一下这些开源工具。

  1. OpenTelemetry:它是 CNCF 的 开源产品,通过使用代理来收集指标、日志和链路,然后将它们发送给其他工具,它支持多种语言集成,并且有很大的仪表功能。
  2. Prometheus:CNCF 的毕业产品,是目前主流的监控工具之一。
  3. Examplars:它可以将 trace_id 和 metrics 联系起来,可以帮助我们通过指标获取到具体日志以及链路状况,通常和 Prometheus 配合工作。
  4. Promtail:日志收集工具,将日志发送到 Loki。
  5. Loki:收集并处理日志,并且支持通过 LogQL 来查询日志,其语法和 PromQL 类似
  6. Tempo:接收 OpenTelemetry 的数据,并且可以通过 Jaeger 将其可视化
  7. Grafana:支持多种数据源的可视化面板

image.png

准备后端应用程序

在这个示例中,我们将使用 java spring boot 项目作为例子。

首先,我们使用 start.spring.io 创建一个 java spring boot 项目,它可以帮我们快速创建一个 Java 项目,并且支持在项目中添加依然和其他配置。
image.png
其中:

  • 使用 Gradle 作为构建自动化工具
  • 使用 2.7 版本的 Spring Boot
  • 使用 JAR 作为包构建格式
  • 使用 JDK11

当完成配置并生成之后,就可以将其压缩包下载下来并用 IDE 打开。
image.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 编译项目。
image.png

然后使用以下命令启动:

java -javaagent:opentelemetry-javaagent.jar -Dspring.config.location=src/main/resources/application.yml -jar build/libs/observability-0.0.1-SNAPSHOT.jar

image.png

然后可以使用 htttp://localhost:8080/failhtttp://localhost:8080/success 进行访问测试。
image.png
image.png

再来使用 localhost:8080/actuator/prometheus 来验证 Prometheus 指标是否有效。
image.png

最后,验证 metrice 和 trace_id 的关联情况。

curl -H 'Accept: application/openmetrics-text; version=1.0.0; charset=utf-8' http://localhost:8080/actuator/prometheus | grep trace_id

image.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.location=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

image.png

然后登录实例:

$ multipass shell demo

image.png

可以通过 sudo su 命令验证是否正确进去 Ubuntu 实例。

其次,使用以下命令安装 K3s:

$ curl -sfL https://get.k3s.io | sh -

集群创建完成后,将 KUBECONFIG 添加到环境变量。

$ export KUBECONFIG=/etc/rancher/k3s/k3s.yaml

检查集群是否正常运行。

$ kubectl cluster-info

image.png

然后,安装 Helm,后续都将使用它来安装应用软件。

$ snap install helm --classic

将 K3s 的 KUBECONFIG 拷贝到 ~/.kube/config 目录下。

$ kubectl config view --raw > ~/.kube/config

最后,检查 Helm 是否能正常工作。

$ helm

image.png

部署可观测性组件

在该阶段,我们将使用 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

image.png

最后,创建一个 namespace,所有应用都部署到该 namespace 下。

$ kubectl create ns observability

image.png

部署 Promtail

使用以下命令进行部署:

$ cd promtail
$ helm upgrade --install promtail grafana/promtail -n observability -f promtail.yaml

image.png

注意检查 Promtail 所指向的 Loki 地址。

部署 Loki

部署命令如下:

$ helm upgrade --install loki grafana/loki-distributed -n observability

image.png

loki-loki-distributed-gateway 这个 Service 非常重要,Promtail 将向它发送数据,Grafana 将通过它获取数据。

部署 Tempo

首先,进入 Tempo 清单所在的目录:

$ cd ../tempo

在安装 Tempo 之前,我们需要先安装 minio,命令如下:

$ kubectl apply -f minio.yaml

image.png

Minio 被部署在 default 命名空间中,因为它是一个更通用的对象存储工具,而不是直接用于观察性。

现在,使用以下命令部署 Tempo:

$ helm upgrade --install tempo grafana/tempo-distributed -n observability -f tempo.yaml

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

image.png

检查 Deployments

使用 Helm 命令查看部署的所有应用。

$ helm ls -n observability

image.png

然后,使用 kubectl 检查应用是否都启动成功。

$ kubectl get po -n observability

image.png

检查 Service 是否正常。

$ kubectl get svc -n observability

image.png

我们看到所有应用都正常部署完成。

部署后端应用

直接到仓库目录清单部署即可。

$ cd ../springboot-app

需要注意的是,为了能够让 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

image.png

检查应用是否部署成功。

$ kubectl get deploy,svc,cm -l app=springboot-app

image.png

接口测试

首先,测试 /fail 接口:http://{external-ip}:8080/fail
image.png

然后,测试 /success 接口:http://{external-ip}:8080/success
image.png

最后,测试 /actuator/prometheus 接口:http://{external-ip}:8081/actuator/prometheus
image.png

可以看到所有接口返回正常。

Grafana 测试

上面以及完成了所有的配置,接下来就在 Grafana 中验证是否能够正常使用。

首先,获取 Grafana 的访问地址。

$ kubectl get svc -n observability

image.png

在浏览器输入地址 http://{external-ip}:32656
image.png

然后,添加数据源。
image.png

我们把 Prometheus、Loki 以及 Tempo 数据源都添加上。
image.png

其中,Prometheus 的配置如下:
image.png

可以看到 Prometheus 和 Tempo 通过 Exemplars 进行关联了。

Loki 的配置如下:
image.png

可以看到 Loki 和 Tempo 通过 trace_id 进行关联了。

Tempo 的配置如下:
image.png

在这里我们将 Tempo 与 Loki 相关联,并映射我们在微服务中配置的应用标签。

测试一下

通过 Explore 可以查看应用日志。
image.png

选择 Loki 数据源。
image.png

通过 Loki,我们可以通过 label 对监控日志进行过滤。
image.png

从日志中,我们可以看到 trace 信息。
image.png

然后,我们查看 Grafana 面板。
image.png

我们选择 Spring Boot Demo,它是我们自己创建的面板。
image.png

我们可以看到应用的请求延迟,另外星星是由 Exemplar 生成。
image.png
image.png

用鼠标悬停在它上面,我们可以看到它是如何与一个 trace_id 相关联的,当点击它时,它将把我们重定向到 Tempo。
image.png

我们可以看到它产生的跟踪,我们也可以看到日志,因为它也是与 Loki 相关的,当点击时我们会看到具体信息:
image.png

屏幕被分割,但是我们可以看到具体的日志了。

最后

我们实现了指标、日志和跟踪之间的可观察性关联。这可以帮助我们在微服务的故障排除过程中,识别瓶颈,看到我们的应用指标的行为,并能够获得特定的跟踪和日志。


Comment