Posted in

Go语言gRPC服务治理全栈方案:拦截器链+负载均衡+熔断降级+链路追踪(OpenTelemetry+Jaeger原生集成)

第一章:Go语言gRPC服务治理全栈方案概览

现代微服务架构中,gRPC凭借高性能、强类型契约和多语言支持成为主流通信协议,而Go语言以其简洁语法、原生并发模型和卓越的gRPC生态支持,成为构建云原生服务治理系统的首选语言。本章系统呈现一套可落地的全栈治理方案,覆盖服务注册发现、负载均衡、熔断限流、链路追踪、配置中心与可观测性集成等核心能力。

核心组件选型与协同关系

  • 服务注册与发现:采用 etcd 作为一致性键值存储,配合 go.etcd.io/etcd/client/v3 实现服务实例的健康心跳注册与自动摘除;
  • 流量治理层:基于 grpc-go 的拦截器(Interceptor)机制,统一注入鉴权、日志、指标采集逻辑;
  • 弹性保障:集成 github.com/sony/gobreaker 熔断器与 github.com/uber-go/ratelimit 实现请求级速率控制;
  • 可观测性:通过 OpenTelemetry Go SDK 自动注入 span,并导出至 Jaeger 或 Prometheus;

快速验证服务注册流程

在本地启动 etcd 后,执行以下代码片段完成服务注册:

// 初始化 etcd 客户端并注册服务实例
cli, _ := clientv3.New(clientv3.Config{Endpoints: []string{"localhost:2379"}})
leaseResp, _ := cli.Grant(context.Background(), 10) // 创建 10 秒租约
cli.Put(context.Background(), "/services/order-service/192.168.1.10:8080", "", clientv3.WithLease(leaseResp.ID))
// 此后需定期调用 cli.KeepAlive() 维持租约,否则实例将被自动下线

治理能力分层视图

层级 能力项 Go 生态典型实现
协议层 TLS 双向认证 grpc.CredentialsBundle + credentials.NewTLS()
传输层 连接池与重试 grpc.WithTransportCredentials + grpc_retry 中间件
业务层 元数据透传 metadata.MD{"tenant-id": "prod"} 随请求上下文传递

该方案强调“轻量嵌入、渐进增强”,所有治理能力均以 Go 模块形式封装,可按需组合引入,无需侵入业务逻辑。

第二章:拦截器链深度实践与高阶定制

2.1 gRPC拦截器原理剖析与生命周期详解

gRPC拦截器本质是服务端与客户端请求/响应链路上的中间件,通过 UnaryServerInterceptorStreamServerInterceptor 接口介入调用流程。

拦截器执行时机

  • 客户端:发起请求前 → 序列化后 → 发送前 → 接收响应后 → 反序列化前
  • 服务端:接收请求后 → 反序列化前 → 执行业务逻辑前 → 返回响应前 → 序列化后

核心生命周期阶段(服务端 Unary 场景)

func loggingInterceptor(ctx context.Context, req interface{}, 
    info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
    log.Printf("→ %s invoked with %+v", info.FullMethod, req)
    resp, err = handler(ctx, req) // 关键:调用链向下传递
    log.Printf("← %s returned: %v, error: %v", info.FullMethod, resp, err)
    return resp, err
}

handler(ctx, req) 是拦截器链的“接力点”——必须显式调用才能进入后续拦截器或最终业务方法;ctx 携带元数据与超时信息,info.FullMethod/package.Service/Method 格式完整路径。

拦截器注册顺序与执行流

阶段 客户端执行顺序 服务端执行顺序
请求前置 拦截器1 → 2 拦截器1 → 2
业务处理 Handler
响应后置 拦截器2 → 1 拦截器2 → 1
graph TD
    A[Client Request] --> B[Client Interceptor 1]
    B --> C[Client Interceptor 2]
    C --> D[gRPC Transport]
    D --> E[Server Interceptor 1]
    E --> F[Server Interceptor 2]
    F --> G[Service Handler]
    G --> F
    F --> E
    E --> D
    D --> C
    C --> B
    B --> H[Client Response]

2.2 Unary与Stream拦截器的统一抽象设计

在 gRPC 框架中,Unary 和 Streaming RPC 具有截然不同的调用生命周期:前者是“请求-响应”单次交互,后者涉及流式读写与状态保持。为复用拦截逻辑(如鉴权、日志、指标),需剥离协议差异,提取共性行为契约。

核心抽象接口

type Interceptor interface {
    Before(ctx context.Context, method string) (context.Context, error)
    After(ctx context.Context, err error) error
}

Before 统一注入上下文并预检;After 处理结果异常,屏蔽 UnaryServerInfoStreamServerInfo 差异。

拦截器适配策略

  • Unary 拦截器直接包装 handler 函数
  • Stream 拦截器包装 stream 实例,通过装饰 RecvMsg/SendMsg 方法实现钩子注入
特性 Unary 拦截器 Stream 拦截器
执行时机 调用前/后各一次 每次消息收发均可触发
上下文生命周期 与 RPC 生命周期一致 跨多次 RecvMsg 延续
graph TD
    A[RPC 入口] --> B{是否 Streaming?}
    B -->|Yes| C[Stream Interceptor]
    B -->|No| D[Unary Interceptor]
    C --> E[Wrap Stream]
    D --> F[Wrap Handler]
    E & F --> G[统一 Before/After 链]

2.3 鉴权与日志拦截器的零侵入集成实践

零侵入集成的核心在于将横切逻辑(鉴权、日志)与业务代码解耦,不修改原有 Controller 或 Service 层代码。

拦截器注册机制

通过 Spring Boot 的 WebMvcConfigurer 注册全局拦截器,按顺序执行:

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())  // 鉴权前置校验
                .excludePathPatterns("/public/**", "/login"); // 白名单路径
        registry.addInterceptor(new LoggingInterceptor()) // 日志记录
                .order(1); // 优先级高于鉴权拦截器(先记日志再鉴权)
    }
}

order(1) 确保日志拦截器在鉴权前捕获原始请求;excludePathPatterns 避免登录接口被重复鉴权,提升安全性与性能。

执行时序与责任分离

拦截器类型 触发时机 关键职责
日志拦截器 preHandle → afterCompletion 记录 URI、耗时、状态码
鉴权拦截器 preHandle 校验 JWT、提取用户上下文、拒绝非法请求
graph TD
    A[HTTP 请求] --> B[LoggingInterceptor.preHandle]
    B --> C[AuthInterceptor.preHandle]
    C --> D{鉴权通过?}
    D -- 是 --> E[Controller 处理]
    D -- 否 --> F[返回 401]
    E --> G[LoggingInterceptor.afterCompletion]

上下文透传设计

使用 ThreadLocal 封装请求上下文,避免参数显式传递:

public class RequestContext {
    private static final ThreadLocal<RequestContext> context = ThreadLocal.withInitial(RequestContext::new);
    private String userId; private String traceId;
    // getter/setter 省略
}

ThreadLocal 实现线程隔离,配合 afterCompletion 清理防止内存泄漏;traceId 支持全链路日志追踪。

2.4 上下文透传与元数据增强的实战编码

在微服务链路中,需将用户身份、请求追踪ID、灰度标签等上下文信息跨服务透传,并动态注入业务元数据。

数据同步机制

使用 ThreadLocal + TransmittableThreadLocal 实现异步线程间上下文继承:

public class RequestContext {
    private static final TransmittableThreadLocal<ContextMap> CONTEXT = 
        new TransmittableThreadLocal<>();

    public static void set(ContextMap map) {
        CONTEXT.set(map); // 透传核心上下文
    }

    public static ContextMap get() {
        return CONTEXT.get();
    }
}

TransmittableThreadLocal 解决了 CompletableFuture 等异步场景下 ThreadLocal 丢失问题;ContextMapConcurrentHashMap 封装,支持动态元数据写入(如 put("biz-stage", "pre-check"))。

元数据注入策略

阶段 注入项 来源
网关入口 trace-id, user-id HTTP Header
业务服务内 biz-scenario, version 注解/配置中心
日志输出前 elapsed-ms, status 拦截器自动附加

执行流程

graph TD
    A[HTTP Request] --> B[Gateway Filter]
    B --> C[注入trace-id/user-id]
    C --> D[RPC调用]
    D --> E[Service ThreadLocal捕获]
    E --> F[增强biz-tag/version]
    F --> G[Log/Metrics输出]

2.5 拦截器链性能压测与可观测性埋点验证

为验证拦截器链在高并发下的稳定性与可观测性,我们构建了分级压测场景,并在关键节点注入 OpenTelemetry 埋点。

压测指标对比(TPS & P99 延迟)

并发线程数 平均 TPS P99 延迟(ms) 埋点采样率
100 1842 42 100%
500 2103 68 25%
1000 2057 113 10%

关键埋点代码示例

// 在拦截器入口处注入 Span,关联请求 ID 与链路上下文
Span span = tracer.spanBuilder("InterceptorChain.execute")
    .setParent(Context.current().with(Span.fromContext(context)))
    .setAttribute("interceptor.order", order)
    .setAttribute("interceptor.name", this.getClass().getSimpleName())
    .startSpan();
try (Scope scope = span.makeCurrent()) {
    chain.doFilter(request, response); // 执行下游逻辑
} finally {
    span.end(); // 确保 Span 正确结束
}

该代码确保每个拦截器执行均生成可追踪的 Span,order 属性用于还原链路时序,makeCurrent() 保障子 Span 继承父上下文。采样率动态降级策略由 otel.traces.sampler 配置驱动。

链路传播流程

graph TD
    A[HTTP Request] --> B[PreAuthInterceptor]
    B --> C[LoggingInterceptor]
    C --> D[ValidationInterceptor]
    D --> E[Service Handler]
    B -.-> F[Span: PreAuth]
    C -.-> G[Span: Logging]
    D -.-> H[Span: Validation]

第三章:负载均衡策略选型与动态适配

3.1 gRPC内置负载均衡机制源码级解析

gRPC 的负载均衡并非由客户端库直接实现,而是通过 Resolver + Balancer 两层抽象协同完成,核心位于 google.golang.org/grpc/balancer 包。

Balancer 接口关键方法

type Balancer interface {
    // 接收解析后的后端地址列表(如 DNS 解析结果)
    UpdateClientConnState(ClientConnState) error
    // 处理连接状态变更(如 READY → CONNECTING)
    UpdateSubConnState(SubConn, SubConnState) error
    // 清理已废弃子连接
    Close() error
}

UpdateClientConnState 触发服务发现更新;UpdateSubConnState 驱动连接状态机迁移,决定是否触发重选。

内置 RoundRobin 实现要点

  • 维护 readySubConns 有序 slice,仅包含 CONNECTED 状态的子连接
  • 每次 Pick 时原子递增索引并取模,实现无锁轮询
  • 状态变更时自动剔除/加入 readySubConns
组件 职责 生命周期
Resolver 地址发现(DNS、etcd等) 长期运行
Balancer 连接选择与状态管理 per-ClientConn
SubConn 封装底层 TCP 连接 可复用
graph TD
    A[Resolver] -->|AddressUpdate| B[Balancer]
    B -->|Pick| C[Ready SubConn]
    C --> D[gRPC RPC Call]
    B -->|StateChange| E[UpdateSubConnState]

3.2 基于etcd的Service Discovery与权重路由实现

核心架构设计

etcd 作为强一致、高可用的分布式键值存储,天然适合作为服务注册中心。服务实例启动时向 /services/{name}/{instance-id} 写入带 TTL 的 JSON 元数据(含 IP、端口、权重、健康状态)。

权重路由实现机制

客户端通过 Watch /services/{name}/ 实时感知实例变更,并按 weight 字段加权轮询(WRR)分发请求:

# 权重路由伪代码(基于 etcd3 Python client)
instances = get_service_instances("api-gateway")  # 返回 [{"addr": "10.0.1.10:8080", "weight": 3}, ...]
total = sum(inst["weight"] for inst in instances)
r = random.randint(0, total - 1)
for inst in instances:
    r -= inst["weight"]
    if r < 0:
        return inst["addr"]  # 选中该实例

逻辑分析weight 字段在注册时由服务治理平台动态注入(如蓝绿发布时将新实例权重设为10,旧实例逐步降至0)。TTL 保障故障实例自动剔除,Watch 机制确保路由表秒级收敛。

注册元数据结构

字段 类型 说明
addr string 实例监听地址(IP:Port)
weight int 路由权重(范围1–100)
version string 语义化版本号,用于灰度路由

服务发现流程

graph TD
    A[服务实例启动] --> B[PUT /services/api/v1/inst-001<br>TTL=30s<br>{addr, weight, version}]
    B --> C[etcd Raft同步]
    C --> D[客户端 Watch /services/api/]
    D --> E[本地缓存更新 + WRR策略重计算]

3.3 自定义LeastRequestBalancer的并发安全落地

在高并发网关场景中,LeastRequestBalancer需保障计数器更新的原子性与统计视图的一致性。

并发计数器设计

采用 atomic.Int64 替代普通 int 字段,避免竞态:

type Endpoint struct {
    Addr     string
    Requests atomic.Int64 // 当前活跃请求数(原子增减)
}

Requests 在每次请求进入时 Add(1),响应完成后 Add(-1)atomic 保证单指令级读写,无锁且零GC开销。

负载选择逻辑

func (b *LeastRequestBalancer) Pick() *Endpoint {
    var minEp *Endpoint
    var minReq int64 = math.MaxInt64
    for _, ep := range b.endpoints {
        req := ep.Requests.Load()
        if req < minReq || (req == minReq && rand.Intn(2) == 0) {
            minReq, minEp = req, ep
        }
    }
    if minEp != nil {
        minEp.Requests.Add(1)
    }
    return minEp
}

Load() 获取快照值用于比较;Add(1) 延迟至选中后执行,确保“选中即占用”,杜绝漏计。

竞态防护对比

方案 锁粒度 吞吐量 是否需 GC 预热
sync.Mutex 全局
atomic.Int64 字段级
RWMutex + map 结构级
graph TD
    A[Pick 请求] --> B{遍历 endpoints}
    B --> C[atomic.Load 取当前请求数]
    C --> D[比较并记录最小值]
    D --> E[选中 endpoint]
    E --> F[atomic.Add 1]
    F --> G[返回实例]

第四章:熔断降级与弹性保障体系构建

4.1 Circuit Breaker状态机模型与Go标准库适配

Circuit Breaker 的核心是三态状态机:ClosedOpenHalf-Open,其跃迁依赖失败计数、超时窗口与探测请求。

状态跃迁条件

  • Closed:连续成功请求重置计数器;失败达阈值(如5次)→ Open
  • Open:固定超时后自动转为 Half-Open
  • Half-Open:仅允许单个探测请求;成功则恢复 Closed,失败则重置为 Open

Go标准库适配要点

  • 利用 sync/atomic 实现无锁状态切换(int32 编码三态)
  • 借助 time.Timer 管理 Open 状态超时,避免 goroutine 泄漏
  • 使用 sync.RWMutex 保护统计桶(如滑动窗口计数器)
type State int32
const (
    Closed State = iota // 0
    Open                // 1
    HalfOpen            // 2
)

func (cb *CircuitBreaker) allow() bool {
    switch atomic.LoadInt32((*int32)(&cb.state)) {
    case Closed:
        return true
    case Open:
        if time.Since(cb.openedAt) > cb.timeout {
            // 原子尝试抢占 Half-Open 状态
            if atomic.CompareAndSwapInt32((*int32)(&cb.state), Open, HalfOpen) {
                cb.lastAttempt = time.Now()
                return true
            }
        }
        return false
    case HalfOpen:
        return cb.consecutiveSuccesses == 0 // 仅首次探测放行
    }
    return false
}

逻辑分析allow() 通过原子读取状态实现无锁判断;Open→HalfOpen 跃迁使用 CompareAndSwapInt32 保证竞态安全;consecutiveSuccesses 作为 Half-Open 下的探测标记,避免并发探测。参数 cb.timeout 控制熔断持续时间,cb.openedAt 记录进入 Open 的绝对时间戳。

状态 允许请求 自动跃迁触发条件 统计依赖
Closed 失败计数 ≥ threshold 滑动窗口计数器
Open time.Since(openedAt) > timeout 定时器
Half-Open ⚠️(仅1次) 探测结果(成功→Closed,失败→Open) consecutiveSuccesses
graph TD
    A[Closed] -->|失败≥threshold| B[Open]
    B -->|timeout到期| C[Half-Open]
    C -->|探测成功| A
    C -->|探测失败| B

4.2 基于Sentinel Go的熔断规则动态加载实战

动态规则源接入

Sentinel Go 支持从本地文件、Nacos、Apollo 等多种数据源热加载熔断规则。推荐使用 file 源快速验证,再平滑切换至配置中心。

规则定义与加载示例

// 初始化文件规则源(支持 JSON/YAML)
ruleSource := file.NewFileRuleSource("./rules/breaker.json")
err := sentinel.LoadRulesWithDataSource(
    []flow.Rule{}, // 流控规则(此处为空)
    []circuitbreaker.Rule{
        {
            Resource:   "order-create",
            Strategy:   circuitbreaker.CbStrategySlowRequestRatio,
            RetryTimeoutMs: 10000,
            MinRequest: 10,
            StatIntervalMs: 1000,
            Threshold: 0.5,
        },
    },
    ruleSource,
)

逻辑说明:LoadRulesWithDataSource 将熔断规则注册到内存,并启动监听器自动响应文件变更;StatIntervalMs=1000 表示每秒统计一次调用指标;Threshold=0.5 即慢调用比例超 50% 触发熔断。

数据同步机制

  • 文件修改后,Sentinel Go 默认 1 秒内完成重载(通过 fsnotify 监听)
  • 配置中心场景下,需实现 datasource.DataSource 接口并注册回调
字段 含义 典型值
Strategy 熔断策略 CbStrategySlowRequestRatio
MinRequest 最小请求数(避免误触发) 10
RetryTimeoutMs 熔断后恢复等待时间 10000
graph TD
    A[配置中心/文件变更] --> B[fsnotify 或 SDK 事件通知]
    B --> C[解析新规则]
    C --> D[校验合法性]
    D --> E[原子替换内存规则]
    E --> F[生效新熔断状态]

4.3 降级兜底逻辑设计与HTTP fallback无缝桥接

当核心服务不可用时,降级逻辑需在毫秒级内接管请求,避免雪崩。关键在于状态感知 + 策略路由 + 协议适配三重协同。

数据同步机制

本地缓存与远程配置中心通过长轮询保持一致性,降级开关变更延迟

HTTP Fallback 路由策略

// 自动桥接:将 RPC 异常透明转为 HTTP GET 请求
if (rpcCallFailed && fallbackEnabled) {
    return httpClient.get("/v1/fallback/user/" + userId) // path 参数透传
                     .timeout(800, TimeUnit.MILLISECONDS)
                     .header("X-Downgrade", "true"); // 标记降级链路
}

逻辑分析:timeout 防止 fallback 拖慢整体响应;X-Downgrade 便于全链路监控归因;路径拼接确保语义一致性。

维度 主链路 Fallback 链路
协议 gRPC HTTP/1.1
SLA 99.99% 99.5%
数据时效性 实时 TTL=30s 缓存
graph TD
    A[用户请求] --> B{RPC 调用成功?}
    B -- 是 --> C[返回主服务结果]
    B -- 否 --> D[触发降级判断]
    D --> E[检查 fallback 开关 & 熔断状态]
    E -- 允许 --> F[HTTP GET 转发]
    E -- 拒绝 --> G[返回兜底静态数据]

4.4 故障注入测试与SLA保障指标验证

故障注入是验证系统韧性与SLA承诺的关键手段,需在受控环境中模拟真实异常。

注入策略设计

  • 基于服务拓扑选择注入点(如API网关、数据库连接池、消息队列消费者)
  • 按SLA目标设定故障类型与时长:延迟(95th > 200ms)、错误率(>0.5%)、超时(>3s)

示例:Chaos Mesh延迟注入

apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: delay-api-gateway
spec:
  action: delay
  mode: one
  selector:
    labels:
      app: api-gateway
  delay:
    latency: "100ms"
    correlation: "0"
    jitter: "20ms"

逻辑分析:latency设为基准延迟,jitter引入随机波动以逼近真实网络抖动;correlation=0确保每次延迟独立,避免模式化偏差。

SLA指标校验对照表

SLA维度 目标值 实测窗口 是否达标
P95响应时间 ≤150ms 5分钟滚动 ✅ 142ms
错误率 ≤0.3% 1小时 ❌ 0.68%

验证闭环流程

graph TD
  A[定义SLA阈值] --> B[执行定向故障注入]
  B --> C[采集实时指标]
  C --> D[比对Prometheus告警规则]
  D --> E[生成SLA合规报告]

第五章:OpenTelemetry+Jaeger原生集成终极指南

环境准备与依赖对齐

确保 OpenTelemetry SDK 版本 ≥ 1.28.0(Java)或 ≥ 1.25.0(Go),Jaeger 后端需启用 OTLP 接收器(v1.49+)。官方推荐部署 Jaeger All-in-One 作为开发验证入口:

docker run -d --name jaeger \
  -e COLLECTOR_OTLP_ENABLED=true \
  -p 14250:14250 -p 16686:16686 \
  jaegertracing/all-in-one:1.49

Java 应用零配置自动注入

使用 OpenTelemetry Java Agent 并通过 JVM 参数直接对接 Jaeger:

-javaagent:/path/to/opentelemetry-javaagent.jar \
-Dotel.exporter.otlp.endpoint=http://localhost:4317 \
-Dotel.traces.exporter=otlp \
-Dotel.resource.attributes=service.name=inventory-api

此时无需修改任何业务代码,所有 Spring MVC、gRPC、JDBC 调用均自动采集 span。

Go 应用手动 instrumentation 示例

main.go 中初始化 tracer 并指向本地 Jaeger:

import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"

exp, _ := otlptracehttp.New(context.Background(),
  otlptracehttp.WithEndpoint("localhost:4317"),
  otlptracehttp.WithInsecure(),
)

配合 otelhttp.NewTransport() 封装 HTTP 客户端,实现跨服务链路透传。

关键配置项对照表

配置项 Jaeger 原生模式 OpenTelemetry OTLP 模式 推荐值
协议端口 6831/6832 (Thrift) 4317 (gRPC) / 4318 (HTTP) 4317
认证方式 无默认认证 支持 TLS + bearer token --insecure(开发)
采样策略 probabilistic via JAEGER_SAMPLER_TYPE otel.traces.sampler + otel.traces.sampling.rate 0.1

Jaeger UI 中的 span 标签增强实践

在 trace 上下文中注入业务维度标签:

Span.current().setAttribute("user.tier", "premium");
Span.current().setAttribute("db.statement.type", "SELECT");
Span.current().setAttribute("http.route", "/api/v1/orders/{id}");

这些标签在 Jaeger UI 的 Trace Detail 页面中可直接用于过滤与聚合分析。

多语言服务链路串联验证

部署 Python Flask(OTel Python SDK)、Node.js Express(OTel JS SDK)和 Java Spring Boot 三个服务,统一配置 OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:4317。发起一次跨服务请求后,在 Jaeger UI 中可观察到完整调用链,包含各语言 span 的 net.peer.iphttp.status_codeexception.message 等标准语义属性。

性能压测下的数据一致性保障

使用 wrk -t4 -c100 -d30s http://localhost:8080/api/order 对接入 OTel 的服务施加压力,同时监控 Jaeger Collector 的 /metrics 端点:重点关注 otelcol_exporter_enqueue_failed_metric_points_total{exporter="otlp"} 是否持续为 0,以及 jaeger_collector_queue_length 是否稳定在

自定义 span 生命周期控制

在关键业务逻辑中显式创建 child span:

Span parent = Span.current();
SpanBuilder builder = tracer.spanBuilder("process-payment")
    .setParent(Context.current().with(parent));
Span span = builder.startSpan();
try {
    // 执行支付逻辑
} finally {
    span.end(); // 必须显式结束,否则 span 不上报
}

本地调试技巧:OTLP 数据抓包验证

启动 tcpdump -i lo -w otlp.pcap port 4317,随后触发一次 API 请求,用 Wireshark 打开 pcap 文件并过滤 grpc 流量,可直观查看 protobuf 编码的 ExportTraceServiceRequest 结构,确认 span 名称、duration、attributes 是否符合预期。

生产环境 TLS 加密部署要点

将 Jaeger Collector 配置为启用 mTLS:

# collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        tls:
          cert_file: /etc/tls/server.crt
          key_file: /etc/tls/server.key

客户端侧同步配置 otel.exporter.otlp.certificateotel.exporter.otlp.private_key,确保证书链完整可信。

在 Kubernetes 和微服务中成长,每天进步一点点。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注