Z.S.K.'s Records

Opentelemetry调研实践四(k8s中golang应用接入opentelemetry实现可观测性)

历史文章:

可观测性到底在说什么

opentelemetry架构及名词介绍

全链路追踪的TraceID与SpanID

整个opentelemetry体系还是相当复杂的,这里没办法将所有opentelemetry的东西讲清楚,直接通过case顺带opentelemetry里的概念来拆解会比较直观

这里通过一个简单的golang demo来介绍怎么接入opentelemetry以实现Metrics跟Trace的传递

由于篇幅有限,整个demo会成两部分进行,第一部分为部署篇,第二部分会来拆分主要代码实现。

链路

要说明的是,由于prometheus同时支持pull/push模式,这里主要会以pull的方式进行说明,这也是普遍采用的方式

整个请求非常简单,如下:

1
web -- > kong API -- > Golang-client -- > Golang-Server

要实现的效果为:

当在浏览器中访问URL时,经过kong API网关转发到 Client端, Client端通过http调用server端, server端正确响应后按路径返回给web

在这期间,借助opentelemetry暴露方法级的metrics,如请求时间、请求次数等度量数据以及整个链路的Trace.

说明

由于Collector的Receivers支持多种数据模型,在这里,Metrics采用的是Prometheus的pull模式进行采集,而Trace则采用的是OTLP格式进行采集,当采集数据到达Receivers后,会再经过Collector的Processors进行处理,这里使用了Batch及k8s_tagger(在较新版本中重命名为k8sprocessor),batch是基于性能考虑可对采集数据进行批量操作,k8s_tagger则是在采集原始的数据基础之上新增一些跟k8s相关的label,这部分留在后面进行详解,等processor处理完后,最终会到存储后端,显然,Metrics会落在Prometheus中,Trace的数据则存储在jeager中

这里要重点说明的是: Receivers中的prometheus(也就是左边的),不是一个真的prometheus实例,而是一个Prometheus的client,Collector从Instrumenttation获取到的数据按prometheus支持的数据模型进行转换,而右边的Prometheus才是具时存储序数据的实例,也就是最终真正存储时序metrics数据的实例.

部署

上述kong、client、server都部署在k8s中,prometheus、jaeger这里为了方便,直接使用的docker-compose all-in-one的方式在k8s集群外部署.

prometheus/jaeger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
version: "2"
services:

jaeger-all-in-one:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686"
- "14268"
- "14250"

prometheus:
container_name: prometheus
image: prom/prometheus:latest
volumes:
- ./prometheus.yaml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"

# 其中prometheus.yml的内容是定义抓取的collector中的数据
scrape_configs:
- job_name: 'otel-collector'
scrape_interval: 10s
static_configs:
- targets: ['10.198.x.x:55715'] # 55715是collector本身暴露的metrics指标
- targets: ['10.198.x.x:62034'] # 72034是collector采集到的client pull到的指标

Collector

collector这里是以daemonset部署在集群中的

这里主要说明下collector的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# otel-ds-agent-conf.yaml

apiVersion: v1
data:
otel-agent-config:
receivers:
otlp:
protocols:
grpc:
http:
prometheus: # 采用的是pull方式,因此需要配置ds.
config:
scrape_configs:
- job_name: k8s-apps
scrape_interval: 30s
scrape_timeout: 30s
honor_timestamps: true
metrics_path: /metrics
scheme: http
kubernetes_sd_configs:
- role: pod
selectors:
- role: pod
# only scrape data from pods running on the same node as collector
field: "spec.nodeName=$KUBE_NODE_NAME"
relabel_configs:
# scrape pods annotated with "prometheus.io/scrape: true"
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
regex: "true"
action: keep
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: ([^:]+)(?::\d+)?;(\d+)
# escaped $1:$2
replacement: $$1:$$2

exporters:
prometheus:
endpoint: "0.0.0.0:8889" # 8889端口用于collector以prometheus的格式保存metrics数据
namespace: otel
const_labels:
app_env: test # 给所有的metrics数据添加静态labels.
resource_to_telemetry_conversion: # 开启将collector中所有的resource labels转换成metrics中的labels
enabled: true

jaeger:
endpoint: 10.4.xx.xx:14250 # 指定jaeger的IP+Port
tls:
insecure: true
logging:
loglevel: debug # 开启 collector 的debug日志

processors:
k8s_tagger:
k8s_tagger/2:
passthrough: false
extract:
metadata:
- k8s.pod.name
- k8s.deployment.name
- k8s.namespace.name
- k8s.node.name
- k8s.pod.start_time
- k8s.pod.uid
pod_association:
- from: resource_attribute
name: ip
- from: resource_attribute
name: k8s.pod.ip
- from: resource_attribute
name: host.name
- from: connection
name: ip
- from: resource_attribute
name: k8s.pod.uid
filter:
node_from_env_var: KUBE_NODE_NAME # 限制collector agent只收集本node的数据,与上述field保持一致

batch:
extensions:
zpages: {}
service: # 指定要开启的Pipeline
extensions: [zpages]
pipelines:
metrics:
receivers: [otlp, prometheus]
processors: [k8s_tagger, batch]
exporters: [prometheus, logging]
traces:
receivers: [otlp]
processors: [k8s_tagger, batch]
exporters: [jaeger, logging]

kind: ConfigMap
metadata:
labels:
app: opentelemetry
component: otel-agent-conf
name: otel-agent-conf
namespace: opentelemetry

当然, 这个配置文件有点长,同时涉及的配置项也非常多,同时也需要阅读者对prometheus的ds配置有了解,这里不对所有的配置进行展开,感兴趣的可自行参考官方说明opentelemetry-specification

有几点个重要的参数说明如下:

  • service: service部分是真正的指定使用哪些pipeline, 如果没有在service里配置的,则不会起作用,相当于只有一个定义.
  • node_from_env_var: 由于collector是以ds的方式运行,每个实例只需要负责采集所在本机的数据即可,因此通过node_from_env_var来控制只采集本机的数据
  • k8s_tagger: k8s_tagger中的配置是给原始的metrics信息添加相关的k8s集群信息

Collector其它文件的部署由于篇幅有限,不再贴出来,可以参考官网的YAML文件

应用

然后Golang-client及Golang-server的部署,同样,可以用官方的example,是用docker-compose启动的,可通过kompose工具转换成k8s的yaml文件,这里篇幅有限就不贴出来

这里简单要说明的是,官网的example中client端的代码使用的是push的方式推送metrics到prometheus,而我们将把push方式改成pull的方式来实现collector从client中拉取metrics, 然后由prometheus server从collector中拉取。

验证

最终,在k8s集群的opentelemetry ns下会存在collector-ds、一个前端,一个后端、及一个kong api服务.

接下来会从代码的角度来阐述go应用如何通过opentelemetry暴露出来的metrics及链路信息

参考文章:

转载请注明出处https://izsk.me

Z.S.K. wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?