Kubernetes监控手册11-针对部署到Pod里的应用做性能监控

秦晓辉@快猫星云 2022年12月7日

写在前面

前面系列的文章我们花费了较大篇幅,介绍了 Kubernetes 的各个组件的监控方法。从整个体系来看,Kubernetes 体系的监控还应该包含 Pod 里的应用的监控。

App 监控概述

容器里的应用要做监控,大概有两个手段,一个是埋点,一个是日志分析。跨语言的埋点方案有两个非常知名,一个是 statsd,一个是 prometheus。日志分析也有两类,一个是在端上做日志流式处理分析,类似 mtail 的方案,另一个是把日志推到中心,在中心做分析。下面我们拆开来看这几个方案的原理。

埋点监控

statsd 和 prometheus 这两类埋点方案相对更通用,不过有些语言会有自己的常用的埋点工具,比如 Java 生态里的 micrometer。一个公司通常有多种语言,跨语言的工具首推 statsd 和 prometheus,下面我们分别做一下概述。

statsd

statsd 开始被大家熟知是由于这篇文章《Measure Anything, Measure Everything image 》Etsy 的工程师使用 NodeJS 开源了这个项目,项目地址在:https://github.com/statsd/statsd

statsd 对于各种语言都有相应的 SDK,业务程序引入这些 SDK 度量自己的指标。典型的数据流如下:

statsd

应用程序通过 SDK 埋点之后,SDK 会把数据通过 UDP 协议推给监控 agent,很多 agent 都支持 statsd 协议的接收,比如 Telegraf、Datadog-agent。然后监控 agent 把数据做聚合,完事推给监控服务端。

这里使用 Telegraf 来做演示,在 Telegraf 配置文件中搜索 inputs.statsd,可以看到很多注释和样例,这里我提供一个例子:

[[inputs.statsd]]
protocol = "udp"
service_address = ":8125"
percentiles = [50.0, 90.0, 99.0, 99.9, 99.95, 100.0]
metric_separator = "_"

启用如上配置,percentiles略微有点多,可以配置的少一点,比如percentiles = [50.0, 90.0, 99.0, 100.0],这样整体计算存储压力也会小一些。重启Telegraf,Telegraf就会在8125端口监听udp协议,接收业务埋点数据的上报。

这里我写了一个简单的 Go 程序,使用 statsd 的 SDK(github.com/smira/go-statsd) 做了一个简单埋点,样例如下:

package main
import (
    "fmt"
    "math/rand"
    "net/http"
    "time"
    "github.com/smira/go-statsd"
)
var client *statsd.Client
func homeHandler(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    // random sleep
    num := rand.Int31n(100)
    time.Sleep(time.Duration(num) * time.Millisecond)
    fmt.Fprintf(w, "duration: %d", num)
    client.Incr("requests.counter,page=home", 1)
    client.PrecisionTiming("requests.latency,page=home", time.Since(start))
}
func main() {
    // init client
    client = statsd.NewClient("localhost:8125",
        statsd.TagStyle(statsd.TagFormatInfluxDB),
        statsd.MaxPacketSize(1400),
        statsd.MetricPrefix("http."),
        statsd.DefaultTags(statsd.StringTag("service", "n9e-webapi"), statsd.StringTag("region", "bj")),
    )
    defer client.Close()
    http.HandleFunc("/", homeHandler)
    http.ListenAndServe(":8000", nil)
}

这个web服务只有一个根路径,逻辑也很简单,就是随机sleep几十个毫秒当做业务处理时间。整体逻辑是这样的:首先,我们要通过statsd.NewClient初始化一个statsd的客户端,参数中指定了StatsD的Server地址(在本例中就是Telegraf的8125),指定了所有监控指标的前缀是http.,还指定了两个全局Tag,一个是service=n9e-webapi,另一个是region=bj,通过TagStyle指定了要发送的是InfluxDB样式(因为数据是发给Telegraf的,Telegraf是InfluxDB生态的)的标签。然后,在请求的具体处理逻辑里上报了两个监控指标,一个是requests.counter,另一个是requests.latency,并且,为这俩指标指定了一个指标级别的标签page=home,整体看起来还是比较简单的。

上面的Go程序编译一下,启动,会作为一个web server监听在8000端口,然后周期性请求这个web server的地址做测试,这个web server接收到请求之后,就调用statsd的sdk,statsd的sdk的核心逻辑就是把数据发给Telegraf的8125,然后就是Telegraf处理聚合逻辑,聚合之后的数据每10s(默认flush频率)发给夜莺。

在页面上,应该可以看到http_requests_latency和http_requests_counter打头的相关指标,比如http_requests_latency_mean这个指标,会看到这个指标有如下几个标签:

  • ident: VM-0-4-centos 这个标签其实是Telegraf原始的host标签,夜莺的规范里叫ident,所以做了一下rename
  • metric_type: timing 这个显然是把statsd的数据类型也做为标签了,其他数据类型还有gauge、counter、set等
  • page: home 这是我们代码里附到监控指标后面的标签,Telegraf自动帮解析出来了
  • service: n9e-webapi NewClient时候附加的全局默认标签
  • region: bj NewClient时候附加的全局默认标签

在 Kubernetes 中,要把支持 statsd 协议的 agent(这里就是 Telegraf),和应用程序放到一个 Pod 中,即 sidecar 架构,这样走 UDP 协议最为稳妥。

prometheus

使用 prometheus 来埋点,也是常见做法,除了埋点 SDK 和 statsd 不同之外,指标暴露方式和数据流向也不同。使用 prometheus 埋点,应用程序会暴露/metrics接口,然后等待监控系统来拉取,监控系统来拉取又有两种方式,一个是使用 Categraf 这样的工具,和应用程序部署到一起做成 sidecar,另一个是应用程序所在的 Pod 打上特殊的 annotation,用 prometheus agent mode 来做动态服务发现,只抓取那些带有相关 annotation 的 Pod 的监控数据。

Nightingale 的官方文档中提供了一个使用 Prometheus 埋点的样例,链接在这里

对于如何抓取的问题,sidecar 模式比较简单,不做赘述。重点看一下服务发现的方式,我来演示一下这个过程。

首先,假设某个应用埋点了,并且上线的时候写好了 annotation,那自然是好的,如果没有打上 annotation,也可以现搞,应用无需重启,样例如下:

kubectl annotate pod nightingale-nwebapi-5df5cd7cd6-rc5p4 -n n9e prometheus.io/scrape=true
kubectl annotate pod nightingale-nwebapi-5df5cd7cd6-rc5p4 -n n9e prometheus.io/metric_path=/metrics
kubectl annotate pod nightingale-nwebapi-5df5cd7cd6-rc5p4 -n n9e prometheus.io/scrape_port=18000

这样一来,Pod 就会带有如下注解:

prometheus.io/scrape=true
prometheus.io/metric_path=/metrics
prometheus.io/scrape_port=18000

后面我们就为 prometheus agent mode 增加一个抓取 job,抓取这些带有 prometheus.io/scrape 的 Pod 的监控指标,抓取规则如下:

- job_name: "kubernetes-pods"

  kubernetes_sd_configs:
    - role: pod

  relabel_configs:
    # "prometheus.io/scrape = true" annotation.
    - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
      action: keep
      regex: true

    # "prometheus.io/metric_path = <metric path>" annotation.
    - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_metric_path]
      action: replace
      target_label: __metrics_path__
      regex: (.+)

    # "prometheus.io/scrape_port = <port>" annotation.
    - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_scrape_port]
      action: replace
      regex: ([^:]+)(?::\d+)?;(\d+)
      replacement: $1:$2
      target_label: __address__
    - action: labelmap
      regex: __meta_kubernetes_pod_label_(.+)
    - source_labels: [__meta_kubernetes_namespace]
      action: replace
      target_label: namespace
    - source_labels: [__meta_kubernetes_pod_name]
      action: replace
      target_label: pod

sidecar 的模式和中心服务发现的方式,如何选型呢?大家可以参考这个视频的介绍:Scaling Prometheus Metrics in Kubernetes with Telegraf | DZone.com Webinar

日志监控

日志监控的两种方式,一个在端上,一个是中心,端上的方式采用 Categraf mtail 插件,具体可以参考 categraf mtail introduction

中心的方式,又有两种,一个是流式处理,日志来了之后,根据预定义的处理规则进行聚合;另一种是周期性查询日志内容生成指标,都是很大的话题,留待后续介绍。

相关文章

关于作者

本文作者秦晓辉Flashcat合伙人,文章内容是Flashcat技术团队共同沉淀的结晶,作者做了编辑整理,我们会持续输出监控、稳定性保障相关的技术文章,文章可转载,转载请注明出处,尊重技术人员的成果。

开源版
Flashcat
Flashduty