一、为什么需要可观测性

在云原生时代,应用部署在Kubernetes集群中,往往由多个微服务组成,每个服务可能分布在不同的Pod里。这种情况下,如果某个服务出现性能问题或者直接宕机,传统的日志查看方式就会显得力不从心。想象一下,你正在度假,突然收到报警说线上服务响应变慢,但你连问题出在哪里都不知道,更别提快速修复了。

可观测性(Observability)就是为了解决这个问题而生的。它不仅仅是监控(Monitoring),而是通过日志(Logging)、指标(Metrics)和追踪(Tracing)三个维度,让你能够像"X光"一样透视整个系统的运行状态。

举个例子,假设你的电商应用突然出现订单提交变慢的情况:

  • 日志告诉你某个Pod抛出了数据库连接超时的异常
  • 指标显示数据库的CPU使用率飙升到90%
  • 追踪发现是某个查询语句没有走索引

有了这些信息,你就能快速定位到根本原因。

二、可观测性的三大支柱

1. 日志收集

在Kubernetes中,容器日志默认是输出到stdout/stderr的,这些日志会被kubelet收集并存储在节点上。但这种方式有个明显问题——当Pod被删除或节点宕机时,日志就丢失了。

我们通常使用EFK栈(Elasticsearch + Fluentd + Kibana)来收集日志。这里以Fluentd为例展示如何配置:

# fluentd-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
data:
  fluent.conf: |
    <source>
      @type tail
      path /var/log/containers/*.log
      pos_file /var/log/fluentd-containers.log.pos
      tag kubernetes.*
      read_from_head true
      <parse>
        @type json
        time_format %Y-%m-%dT%H:%M:%S.%NZ
      </parse>
    </source>
    
    <filter kubernetes.**>
      @type kubernetes_metadata
    </filter>
    
    <match kubernetes.**>
      @type elasticsearch
      host elasticsearch
      port 9200
      logstash_format true
    </match>

这段配置做了三件事:

  1. 监控所有容器日志文件的变化
  2. 添加Kubernetes元数据(如Pod名称、命名空间)
  3. 将日志发送到Elasticsearch

2. 指标监控

Prometheus已经成为云原生监控的事实标准。它通过Pull模式从各个服务收集指标数据。在Kubernetes中,我们可以用ServiceMonitor来定义监控目标:

# servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: webapp-monitor
spec:
  selector:
    matchLabels:
      app: webapp
  endpoints:
  - port: web
    interval: 15s
    path: /metrics

这个配置告诉Prometheus:每15秒去抓取所有带有app=webapp标签的Service的/metrics端点。

3. 分布式追踪

当请求要经过多个微服务时,我们需要Jaeger这样的分布式追踪系统。以Spring Boot应用为例,只需添加依赖:

<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-extension-trace-propagators</artifactId>
</dependency>

然后在代码中创建Span:

@GetMapping("/checkout")
public String checkout(@RequestHeader HttpHeaders headers) {
    // 从请求头中提取追踪上下文
    Span span = Span.current();
    span.setAttribute("user.id", getCurrentUserId());
    
    try(Scope scope = span.makeCurrent()) {
        // 调用支付服务
        paymentService.charge();
        // 调用库存服务
        inventoryService.deduct();
        return "success";
    }
}

这样在Jaeger UI中就能看到完整的调用链了。

三、实战:全栈可观测性方案

让我们用一个电商平台的例子,把三大支柱串联起来。假设我们有这些服务:

  1. 前端服务(Node.js)
  2. 订单服务(Java)
  3. 支付服务(Go)
  4. MySQL数据库

日志配置

给所有Pod添加Fluentd边车容器:

containers:
- name: fluentd
  image: fluent/fluentd-kubernetes-daemonset:v1.14.6
  env:
  - name: FLUENT_ELASTICSEARCH_HOST
    value: "elasticsearch"
  volumeMounts:
  - name: varlog
    mountPath: /var/log

指标暴露

每个服务都需要暴露Prometheus格式的指标。以Node.js服务为例:

const promClient = require('prom-client');

// 定义自定义指标
const httpRequestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'code'],
  buckets: [0.1, 0.5, 1, 2, 5]
});

// 在中间件中记录指标
app.use((req, res, next) => {
  const end = httpRequestDuration.startTimer();
  res.on('finish', () => {
    end({ method: req.method, route: req.route.path, code: res.statusCode });
  });
  next();
});

追踪集成

在所有服务中配置OpenTelemetry:

# opentelemetry-collector.yaml
receivers:
  otlp:
    protocols:
      grpc:
exporters:
  jaeger:
    endpoint: "jaeger:14250"
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [jaeger]

四、注意事项与最佳实践

  1. 采样策略:全量追踪会产生大量数据,建议对生产环境采用动态采样

    # Jaeger采样配置
    sampling:
      type: probabilistic
      param: 0.1  # 只收集10%的请求
    
  2. 日志分级:合理使用DEBUG/INFO/WARN/ERROR级别

    // 不好的写法
    log.error("User logged in"); 
    
    // 好的写法
    if (failedAttempts > 3) {
      log.warn("Multiple login failures for user {}", username);
    }
    
  3. 标签设计:Prometheus指标标签要谨慎选择,避免高基数问题

    # 不好的标签(会导致指标爆炸)
    http_requests_total{path="/users/1234"} 
    
    # 好的标签
    http_requests_total{method="GET", status="200", route="/users/:id"}
    
  4. 安全考虑:确保Elasticsearch和Jaeger有适当的访问控制

五、技术选型对比

方案 优点 缺点 适用场景
ELK 日志搜索能力强 资源消耗大 需要全文检索的场景
Prometheus 实时告警做得好 不支持长期存储 指标监控和告警
Jaeger 调用链可视化优秀 数据量大时成本高 微服务架构调试

六、总结

在Kubernetes中实现可观测性不是简单的工具堆砌,而是要建立完整的可观测性体系。通过本文的实践方案,你可以:

  1. 通过EFK快速定位错误日志
  2. 利用Prometheus发现性能瓶颈
  3. 使用Jaeger分析跨服务调用问题

记住,好的可观测性系统应该像汽车的仪表盘——不需要你时刻盯着,但在出现问题时能第一时间给出明确指示。