
基于 Kubernetes 注解的 OpenTelemetry 动态发现:打造自服务的可观测方案
背景
在云原生时代,Kubernetes 已经成为容器编排的事实标准,越来越多的企业将其作为核心基础设施来运行和管理现代化应用。然而,随着微服务架构的普及和容器化工作负载的动态性增强,传统的监控和可观测性工具逐渐暴露出局限性——静态配置难以应对快速变化的环境,手动维护监控规则的成本也变得越来越高。
传统的监控方式通常依赖于集中式的配置管理,即由管理员预先定义好需要监控的目标和服务。这种方式不仅缺乏灵活性,还容易导致配置更新滞后,无法及时反映集群中的实际状态,还伴随着出错的风险。这种挑战,即使是作为开放标准的可观测性框架 OpenTelemetry 也无法避免。
不过 OpenTelemetry 通过 Reciver Creator 和 Kubernetes Observer 特性可以解决这个问题,实现基于 Kubernetes 注解的 OpenTelemetry Collector 动态发现功能。
工作原理
- Kubernetes Observer 负责检测 Pod 及其元数据(如暴露的端口、注解等)。Observer 除了支持 Kubernetes API,还支持 Docker API(1.24+)、ECS API、ECS Tasks 元数据端点、Host。
- Receiver Creator 根据这些信息动态实例化适当的接收器(如文件日志接收器)。Receiver Creator 可以支持 log、metrics 和 trace 接收器的创建。
这一功能通过将监控配置的控制权下放至开发人员,显著简化了可观测性管理的复杂性。
管理员可以预先配置 Observer 和 Receiver Creator ,并定义默认的接收器(Receiver)模板。开发人员只需在工作负载的 Pod 上添加对应的注解,即可实现自动化的监控。无论是启用特定的接收器,还是自定义日志解析规则,都可以通过简单的注解完成,而无需修改全局配置或重启 Collector 实例。
方案优势:
- 自服务 :用户无需依赖管理员即可为自己的工作负载配置监控。
- 动态更新 :更改即时生效,无需重启 Collector。
- 灵活性 :支持指标和日志采集,并允许自定义配置。
接下来我们将演示基于 Kubernetes 注解的 OpenTelemetry Collector 动态发现功能,实现日志采集以及检索。
演示
环境
- K3s v1.28.13+k3s1
- Loki v3.3.2
- Grafana 11.5.1
- OpenTelemetry Collector v0.119.0
1. 安装 Loki 和 Grafana
使用 Helm 安装 Loki,配置如下:
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm install loki grafana/loki \ default/otel-collector ⎈
--namespace loki \
--create-namespace \
-f values.yaml
#values.yaml
loki:
auth_enabled: false
commonConfig:
replication_factor: 1
useTestSchema: true
storage:
type: filesystem
deploymentMode: SingleBinary
singleBinary:
replicas: 1
# Zero out replica counts of other deployment modes
backend:
replicas: 0
read:
replicas: 0
write:
replicas: 0
ingester:
replicas: 0
querier:
replicas: 0
queryFrontend:
replicas: 0
queryScheduler:
replicas: 0
distributor:
replicas: 0
compactor:
replicas: 0
indexGateway:
replicas: 0
bloomCompactor:
replicas: 0
bloomGateway:
replicas: 0
安装 Grafana。
helm install grafana grafana/grafana
通过 port forward 可以访问 Grafana http://localhost:3000 (Helm 的安装结果中可以找到用户名和密码)。
POD_NAME="$(kubectl get pod -l app.kubernetes.io/name=grafana -o jsonpath='{.items[0].metadata.name}')"
kubectl --namespace default port-forward $POD_NAME 3000
在 Grafana 中配置 Loki 数据源,指向上面部署 Loki。
2. 部署 OpenTelemetry Collector
与上次不同,这回通过 Helm 部署,并进行必要的设置:
- 设置运行方式为 DaemonSet,因为 Otel Collector 需要运行在各个节点上,以便采集 Pod 的日志。
- 指定 Otel Collector 使用的 ServiceAccount,并为其分配权限。Kubernetes Observer 需要访问 Kubernetes API,因此需要相应的权限。
- 提供 Otel Collector 的配置:配置 receiver_creator(receiver)、k8s_observer(extension) 以及 otlphttp/loki(exporter)等。
除了使用默认的 filelog 接收器模板外,在配置中还提供了另外一个模板。这个模板用于采集 Pod 名为 busybox 的容器的日志,日志路径为 /var/log/pods/namespace_pod_uid_container_name/*.log
。并且为这些日志添加了一个名为 attributes.log.template
的字段,值为 busybox
。
config:
receivers:
receiver_creator:
watch_observers: [k8s_observer]
discovery:
enabled: true
receivers:
filelog/busybox:
rule: type == "pod.container" && container_name == "busybox"
config:
include:
- /var/log/pods/`pod.namespace`_`pod.name`_`pod.uid`/`container_name`/*.log
include_file_name: false
include_file_path: true
operators:
- id: container-parser
type: container
- type: add
field: attributes.log.template
value: busybox
最终 Helm 的配置如下。
#values.yaml
serviceAccount:
create: false
name: otelcontribcol
presets:
kubernetesAttributes:
enabled: true
mode: daemonset
image:
repository: otel/opentelemetry-collector-contrib
tag: 0.119.0
config:
receivers:
receiver_creator:
watch_observers: [k8s_observer]
discovery:
enabled: true
receivers:
filelog/busybox:
rule: type == "pod.container" && container_name == "busybox"
config:
include:
- /var/log/pods/`pod.namespace`_`pod.name`_`pod.uid`/`container_name`/*.log
include_file_name: false
include_file_path: true
operators:
- id: container-parser
type: container
- type: add
field: attributes.log.template
value: busybox
extensions:
health_check: {}
k8s_observer:
auth_type: serviceAccount
node: ${env:K8S_NODE_NAME}
observe_pods: true
observe_nodes: true
observe_services: true
observe_ingresses: true
exporters:
debug:
verbosity: basic
otlphttp/loki:
endpoint: "http://loki.loki:3100/otlp"
tls:
insecure: true
service:
pipelines:
logs:
receivers: [receiver_creator]
exporters: [debug, otlphttp/loki]
extensions: [health_check, k8s_observer]
extraVolumes:
- name: varlog
hostPath:
path: /var/log/pods
extraVolumeMounts:
- name: varlog
mountPath: /var/log/pods
securityContext:
privileged: true
使用上面的配置来部署 OpenTelemetry Collector。
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update
helm install otel-collector-filelog open-telemetry/opentelemetry-collector \
--namespace otel-collector \
--create-namespace \
-f values.yaml
3. 部署示例应用
在 default 命名空间下我们部署两个应用:一个是 Java 应用,另一个是 busybox 应用。
其中 Java 应用添加注解 io.opentelemetry.discovery.logs/enabled: 'true'
,以启用自动发现,使用默认的模板进行日志采集;而 busybox 应用不添加注解。
Java 应用启动了一个监听在 8080 端口的 web 服务:
kubectl apply -n default -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-sample
spec:
replicas: 1
selector:
matchLabels:
app: java-sample
template:
metadata:
labels:
app: java-sample
annotations:
io.opentelemetry.discovery.logs/enabled: 'true'
spec:
containers:
- name: java-sample
image: addozhang/spring-boot-rest
imagePullPolicy: Always
ports:
- containerPort: 8080
EOF
busybox 应用启动了一个循环输出 “hello” 的容器:
kubectl apply -n default -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
labels:
run: busybox
name: busybox
spec:
containers:
- args:
- sh
- -c
- while true; do echo "hello"; sleep 1; done
image: busybox
name: busybox
resources: {}
restartPolicy: Never
EOF
通过监控 OpenTelemetry Collector 的日志可以看到,Kubernetes Observer 发现了 Pod ,并动态创建 FileLog Receiver。
2025-02-15T14:05:16.688Z info receivercreator@v0.119.0/observerhandler.go:127 stopping receiver {"kind": "receiver", "name": "receiver_creator", "data_type": "logs", "receiver": {}, "endpoint_id": "k8s_observer/32214501-1871-425d-b464-6008ca7e3e22/java-sample"}
2025-02-15T14:05:40.690Z info receivercreator@v0.119.0/observerhandler.go:201 starting receiver {"kind": "receiver", "name": "receiver_creator", "data_type": "logs", "name": "filelog/e6c5ca2a-6c3f-4ae4-89a6-34ca5cd9a3fa_java-sample", "endpoint": "10.42.0.142", "endpoint_id": "k8s_observer/e6c5ca2a-6c3f-4ae4-89a6-34ca5cd9a3fa/java-sample", "config": {"include":["/var/log/pods/default_java-sample-77b6d8f9c5-5zzh5_e6c5ca2a-6c3f-4ae4-89a6-34ca5cd9a3fa/java-sample/*.log"],"include_file_name":false,"include_file_path":true,"operators":[{"id":"container-parser","type":"container"}]}}
2025-02-15T14:05:40.691Z info adapter/receiver.go:41 Starting stanza receiver {"kind": "receiver", "name": "receiver_creator", "data_type": "logs", "name": "filelog/e6c5ca2a-6c3f-4ae4-89a6-34ca5cd9a3fa_java-sample/receiver_creator{endpoint=\"10.42.0.142\"}/k8s_observer/e6c5ca2a-6c3f-4ae4-89a6-34ca5cd9a3fa/java-sample"}
2025-02-15T14:05:40.893Z info fileconsumer/file.go:265 Started watching file {"kind": "receiver", "name": "receiver_creator", "data_type": "logs", "name": "filelog/e6c5ca2a-6c3f-4ae4-89a6-34ca5cd9a3fa_java-sample/receiver_creator{endpoint=\"10.42.0.142\"}/k8s_observer/e6c5ca2a-6c3f-4ae4-89a6-34ca5cd9a3fa/java-sample", "component": "fileconsumer", "path": "/var/log/pods/default_java-sample-77b6d8f9c5-5zzh5_e6c5ca2a-6c3f-4ae4-89a6-34ca5cd9a3fa/java-sample/0.log"}
2025-02-15T14:05:46.689Z info receivercreator@v0.119.0/observerhandler.go:201 starting receiver {"kind": "receiver", "name": "receiver_creator", "data_type": "logs", "name": "filelog/busybox", "endpoint": "10.42.0.143", "endpoint_id": "k8s_observer/815adda3-c0de-43a0-be25-75d8e14c5b0c/busybox", "config": {"include":["/var/log/pods/`pod.namespace`_`pod.name`_`pod.uid`/`container_name`/*.log"],"include_file_name":false,"include_file_path":true,"operators":[{"id":"container-parser","type":"container"},{"field":"attributes.log.template","type":"add","value":"busybox"}]}}
2025-02-15T14:05:46.691Z info adapter/receiver.go:41 Starting stanza receiver {"kind": "receiver", "name": "receiver_creator", "data_type": "logs", "name": "filelog/busybox/receiver_creator{endpoint=\"10.42.0.143\"}/k8s_observer/815adda3-c0de-43a0-be25-75d8e14c5b0c/busybox"}
2025-02-15T14:05:46.892Z info fileconsumer/file.go:265 Started watching file {"kind": "receiver", "name": "receiver_creator", "data_type": "logs", "name": "filelog/busybox/receiver_creator{endpoint=\"10.42.0.143\"}/k8s_observer/815adda3-c0de-43a0-be25-75d8e14c5b0c/busybox", "component": "fileconsumer", "path": "/var/log/pods/default_busybox_815adda3-c0de-43a0-be25-75d8e14c5b0c/busybox/0.log"}
4. 验证
通过 port-forward 可以访问 Java 应用,触发日志输出。
在 Grafana 中可以检索两个容器的日志。在 busybox 容器的日志中,可以看到自定义模板添加的属性 log_template: busybox。
在 Java 应用的日志中,可以看到套用了默认模板的日志属性。
总结
本文详细探讨了如何利用 OpenTelemetry Collector 的基于 Kubernetes 注解的动态发现功能 ,在云原生环境中实现高效、灵活的日志采集和监控。通过结合 Kubernetes Observer 和 Receiver Creator ,管理员可以预先配置通用的监控规则,而开发人员只需通过简单的注解即可为自己的工作负载启用监控或日志采集。这种方式不仅大幅降低了配置管理的复杂性,还实现了真正的“自服务”可观测性。