【资源】
- 官网,Collector安装,kubernetes部分:opentelemetry.io/docs/collec…
- Github:github.com/open-teleme…
- Helm Chart:artifacthub.io/packages/he…
【前置文章】
【参考】
1. Opentelemetry Operator介绍
传统的Opentelemetry和Java的集成,需要在启动jar的时候,加上:java -javaagent:path/to/opentelemetry-javaagent.jar -jar myapp.jar
,但这样需要在项目的CI/CD部分改配置,那么,有没有一种方式,让部署在kubernetes中的应用,自动的加上这个agent,而项目本身保持clean呢?
答案是有的,那就是安装Opentelemetry Operator
。
【本文将演示如何通过安装Opentelemetry Operator实现distributed trace id的收集,主要步骤如下】:
-
- 安装
cert manager
,cert-manager是Kubernetes的附加组件,用于自动管理和颁发各种发行来源的TLS证书。它将确保证书有效并定期更新,并尝试在到期前的适当时间更新证书。
- 安装
-
- 安装
opentelemetry-operator
,通过helm chart进行安装。
- 安装
-
- 安装CRD
Instrumentation
,用来使opentelemetry-javaagent.jar
自动的加到我们部署在k8s中的应用pod里去。
- 安装CRD
-
- 安装CRD
OpenTelemetryCollector
,看名字也知道,是Collector,用来收集app中发送的trace信息,并通过配置的exporters的endpoint发送到后端存储起来(这里的后端可以是Tempo)。
- 安装CRD
-
- 安装两个Spring boot项目:
- pod: my-app
- pod: user-app
-
- 安装
Tempo
,用来接收trace数据并存储。
- 安装
-
- 安装
Grafana
,用来配置datasource=tempo,并在UI上进行trace链的展示。
- 安装
-
- 开始测试trace链。
2. 安装Cert Manager
参考:cert-manager.io/docs/instal…
kubectl apply -f github.com/cert-manage…
安装好后,在namespace=cert-manager下查看资源,主要看两个service:
kubectl get all -n cert-manager
3. 通过helm安装opentelemetry-operator
3.1 首先添加helm chart:
helm repo add open-telemetry open-telemetry.github.io/opentelemet…
更新chart:
helm repo update
3.2 开始安装
namespace为opentelemetry-operator-system
helm install my-opentelemetry-operator open-telemetry/opentelemetry-operator -n opentelemetry-operator-system
可以看到安装了:
- pod为opentelemetry-operator-controller-manager
- 两个service:
- opentelemetry-operator-controller-manager-metrics-service
- opentelemetry-operator-webhook-service
4. 安装CRDInstrumentation
如同上述介绍的,传统的Instrumentation,需要我们在jar启动的时候加上-javaagent。如果我们使用Custom Resource = Instrumentation的定义文件,可以自动的kubernetes运行时,给我们的应用程序pod加上-javaagent,从而使得我们的应用程序可以自动生成traceId,并发送给collector。
需要做两个配置:
-
- 配置CRD,Kind=Instrumentation。
-
- 在我们的pod yaml定义中加上annotation:
instrumentation.opentelemetry.io/inject-java: "true"
。
- 在我们的pod yaml定义中加上annotation:
首先,新建一个文件,叫instrumentation.yaml
,spec中可以定义:
exporter
: 这里定义的exporter,即我们在下一章(#5)生成的collector的service name,如果是不同的namespace下,需要写http://.:4317。propagator
:无侵入式Trace上下文传播类型,可选tracecontext, baggage, b3等等。(具体可参考:github.com/open-teleme…)。resource
:- 可配:addk8sUIDattributes, 值可以为true,表示在span中启用k8s Uids相关的标签。
- 可配:resourceAttributes, 值可以是一些属性如:"Servicename: test"或"Environment: dev"。配上的属性可以在span中包含进来。
sampler
:涉及到是否将所有的trace数据都发送给存储的后端(如Tempo)。类型有很多,如:always_on, always_off, traceidratio, parentbased_always_on...- 官网文档参考:github.com/open-teleme…。
- 另外还有一篇文章写的不错:www.aspecto.io/blog/opente…。
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: my-instrumentation
spec:
exporter:
endpoint: http://otel-daemonset-collector:4317
propagators:
- tracecontext
- baggage
- b3
sampler:
type: always_on
复制代码
在Kubernetes中安装:
kubectl apply -f instrumentation.yaml -n opentelemetry-operator-system
其实,如同上述介绍过,我们需要给我们的Pod yaml定义加上annotation(还支持NodeJS, Python等,这里只列举java的):
instrumentation.opentelemetry.io/inject-java: "true"
值可以为:
true
:表示该Pod允许被CRD Instrumentation注入agent,实现trace相关数据的自动生成。Instrumentation需要被安装在同一个namespace下。my-instrumentation
:Instrumentation的名字(也需要在同一个namespace下)。主要是为了区别如果同一个namespace下有多个Instrumentation的case。my-other-namespace/my-instrumentation
:如果需要引用的Instrumentation不在同一个namespace下,需要显示的指定namespace以及Instrumentation的名字。false
:不需要被注入。
这个注解,在下述的第#6章,app的deployment.yaml中有用到。
5. 安装CRDOpenTelemetryCollector
OpenTelemetryCollector将之前的Opentelemetry Collector pipeline的定义(如receivers, processors, exporters)统一整合到当前的CRD中。
其中,.Spec.Mode
有三种:
Sidecar
:需要在自己的app的pod定义中添加一个annotation,如:sidecar.opentelemetry.io/inject: "Boolean/String"
,可以配true,意思是在pod中自动配置collector,或是false,意思是不配置。或是当前namespace下有多个OpenTelemetryCollector的时候,这里可以写上想选择的name。DaemonSet
,这个模式下不需要添加annotation,但是,这种模式下,同一个namespace下所有的pod都会发送trace信息给这个collector。Deployment
(默认),同样的,也需要添加annotation:sidecar.opentelemetry.io/inject
。
从设计上来说,我们可以为每个pod各自配置自己的sidecar模式的collector,用来收集各个pod中的trace数据,然后再setup一个daemonset模式的collector,用来收集各个sidecar中的数据。
新建一个文件,叫openTelemetryCollector.yaml
:
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel-daemonset
spec:
mode: daemonset
hostNetwork: true
config: |
receivers:
otlp:
protocols:
grpc:
http:
processors:
exporters:
otlp:
endpoint: "http://tempo:4317"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: []
exporters: [otlp]
复制代码
在Kubernetes中安装:
kubectl apply -f openTelemetryCollector.yaml -n opentelemetry-operator-system
可以看到会生成:
- pod:otel-daemonset-collector
-->
和exporter的集成,日志在这里看。 - service:
- otel-daemonset-collector
- otel-daemonset-collector-headless
- otel-daemonset-collector-monitoring
- daemonset:otel-daemonset-collector
6. 安装一个自己的app
6.1 创建my-app项目
创建一个简单的Spring boot项目,叫my-app,有两个api:
- http://localhost:8080/version
- http://localhost:8080/dashboard?userId={userId},这个API会再调用user-app中的API
-->
http://user-app-service:8081/user-name?userId={userId}
@GetMapping("version")
public String version() {
log.info("/version, return version = my-app v1.");
return "my-app v1";
}
@GetMapping("dashboard")
public String dashboard(@RequestParam String userId) {
log.info("/dashboard, received userId = {}.", userId);
String userName = restTemplate.getForObject("http://user-app-service:8081/user-name", String.class);
log.info("get userName = {} from user-app.", userName);
return String.format("I am dashboard with userId = %s, userName = %s.", userId, userName);
}
复制代码
另外,需要在logback.xml中,自定义的pattern中,加上%X,表示获取当前的MDC,具体参考:github.com/open-teleme…:
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %X [%thread] %-5level %logger{56} %msg%n"></property>
复制代码
my-app的deployment.yaml
:
- 安装在namespace=myapp。
- image需要改。
- 可以看到有个annotation:
instrumentation.opentelemetry.io/inject-java: "opentelemetry-operator-system/my-instrumentation"
,因为我们的Instrumentation是放在namespace=opentelemetry-operator-system下的,所以这里并不是"true"(如果是同一个namespace下,可以设为true)。 - 在pod的定义中,加下了env:
otel.javaagent.debug=true
,主要是可以在my-app中打印opentelemetry相关的debug信息,在prod中需要关闭掉。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
annotations:
instrumentation.opentelemetry.io/inject-java: "opentelemetry-operator-system/my-instrumentation"
spec:
containers:
- name: my-app
image: <docuer hub username>/my-app
env:
- name: JDK_JAVA_OPTIONS
value: "-Dotel.javaagent.debug=true"
imagePullPolicy: Always
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
type: ClusterIP
selector:
app: my-app
ports:
- protocol: TCP
port: 8080
targetPort: 8080
复制代码
创建my-app这个pod,并且创建my-app-service:
kubectl apply -f deployment.yaml -n myapp
使用port-forward来转发minikube中的8080端口,以便可以在宿主机用localhost访问k8s中的service:
kubectl -n myapp port-forward service/my-app-service 8080:8080
6.2 创建user-app项目
再创建一个Spring boot项目,叫user-app,有一个api:
@GetMapping("user-name")
public String getUserName(@RequestParam String userId) {
String name = "test 001";
log.info("Received userId = {}, return userName = {}.", userId, name);
return name;
}
复制代码
创建user-app这个pod,并且创建user-app-service:
kubectl apply -f deployment.yaml -n myapp
至此,在第6章,myapp namespace下创建了以下资源:
7. Tempo和Grafana的安装
查看我之前的文章:
8. 开始测试
8.1 测试my-app的/version接口
首先在本地浏览器中调用:http://localhost:8080/version
可以在loki中看到trace_id已经打印了:
在tempo中看到调用链:
8.2 测试my-app的/dashboard口
在本地浏览器中调用:http://localhost:8080/dashboard?userId=1
在loki中看到trace_id:
在tempo中看到调用链,这里可以看到两个app的调用链是完整的:
点击每个tab详情,可以更详细的看到一些调用链以及controller等: ps.如果有调用sql,也会显示出来的。