Hi~朋友,关注置顶防止错过消息
为什么需要链路追踪
随着应用的增多,各个应用之间的调用关系也错综复杂,在多应用之间排查问题的代价也越来越高,任何一个系统的异常都有可能影响到其他系统,为了快速定位问题和发现问题,我们需要将所有应用的调用链进行可视化,以一个直观的方式展示系统之间的调用关系以及各个接口的响应时间。
随着现在监控系统的日趋完善,很多大公司都已经有了可观测性系统,而链路追踪可能只是很小的一部分,但却很重要,因为这可以帮助开发很轻易的定位到底哪个环节出了问题。
链路追踪系统的选型
目前市面上开源的链路追踪系统还是比较多的,主要有以下几种:
- pinpoint
- zipkin
- jaeger
- skywalking
我们当时在选型链路追踪系统的时候主要根据以下标准:
- 社区活跃程度要高
- 客户端对应用代码的侵入性要足够低,甚至要做到零侵入
- 要配套易用性的 UI(暂时没有额外的精力开发页面)
- 支持的客户端语言尽可能的多,但是对Java语言的支持性(我们大部分的应用都是Java应用)要足够强
- 由于本人和公司大部分的开发人员都是Java,为了以后定制化开发的方便,所以链路系统的开发语言是Java语言
依据公司现有的架构和上述标准,我们最终选择了skywalking作为我们的链路系统,选型的时候我们是尽可能技术栈统一,简单易用,避免技术栈异构给整个架构带来的不稳定。
现有的基础设施
- 我们线上应用均跑在k8s集群内
- 日志系统是采用的现在主流的EFK
关于对接的步骤
第一阶段
- 基于现在开源的agent追踪能够追踪的请求
- 将traceId与打印到日志中,实现请求与日志的关联,并完成一个traceId可以获取所有系统的请求日志
第二阶段
- 适配一些特殊场景的追踪,这里需要自定义开发Agent,比如在我们的使用场景中用到了AWS的SQS,开源的Agent中没办法对SQS的请求进行链路追踪,预计下周一开始开发,后续我这边测试稳定了会将代码开放到github 上,如果大家有需要讨论或者使用的可以后台私信我。
应用如何对接
我们需要对k8s的Deployment配置文件进行改造,需要将Agent挂载进我们的容器,以便启动的时候我们的Java应用程序可以加载到Agent,在这里我们是通过init容器的方式来实现:
代码语言:javascript复制apiVersion: apps/v1
kind: Deployment
metadata:
namespace: application-test
name: xxxx
labels:
app: xxxx
spec:
replicas: 2
selector:
matchLabels:
app: xxxx
strategy:
...
template:
metadata:
labels:
app: xxxx
spec:
restartPolicy: Always
volumes:
- name: skywalking-agent
emptyDir: { }
initContainers:
- name: skywalking-agent
image: xxxxx:java-agent-8.14.0-alpine
volumeMounts:
- name: skywalking-agent
mountPath: /skywalking-agent
command: [ "/bin/sh"]
args: [ "-c", "cp -R /skywalking/agent /skywalking-agent/; cp /skywalking-agent/agent/optional-plugins/apm-trace-ignore-plugin-8.14.0.jar /skywalking-agent/agent/plugins/"]
containers:
- name: xxxx
image: DEPLOY_IMAGE
...
volumeMounts:
- name: skywalking-agent
mountPath: /skywalking-agent
envFrom:
- configMapRef:
name: xxxxx
- init容器中的image我们是基于公有仓库的镜像拉取了一份推到了我们自己的私有仓库,后续拉取都是基于我们的私有仓库的镜像
- volumes和volumeMounts:通过volumes和volumeMounts我们可以实现目录共享,可以将skywalking-agent容器中的agent jar共享给我们的应用程序容器,方便应用程序在启动的时候能够加载到agent
envFrom.configMapRef.name:指定了我们环境变量的ConfigMap,在该ConfigMap中有几个环境变量需要我们去定义:
- JAVA_OPTS:这里定义了JVM启动时的一些参数,里面必须包含一个-javaagent:/skywalking-agent/agent/skywalking-agent.jar,表示启用我们的skywalking java agent
- SWAGENTCOLLECTORBACKENDSERVICES: skywalking server服务器的请求地址
- SWAGENTNAME:定义service name
上述配置没问题以后,当我们完成Deployments部署以后,在我们的 Pod中可以看到init容器和应用容器都被正常运行,而且在 Skywalking的UI页面上看到我们的Service。
如何将traceId对接进日志
- 首先需要在你的应用的maven或者gradle配置文件中增加以下依赖:
maven
代码语言:javascript复制<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.14.0</version>
</dependency>
gradle
代码语言:javascript复制implementation("org.apache.skywalking:apm-toolkit-logback-1.x:8.14.0")
2. 改造你的logback配置文件
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<configurationscan="true"scanPeriod="30 seconds">
<propertyname="JSON_LOG_PATTERN"
value='{
"timestamp": "%d{yyyy-MM-dd HH:mm:ss.SSS}",
"traceId": "%tid",
"skyWalkingContext": "%sw_ctx",
"thread": "%t",
"level": "%level",
"class": "%logger",
"content": "%msg",
"exception": "%exception"
}'/>
<!-- add converter for %tid -->
<conversionRuleconversionWord="tid"converterClass="org.apache.skywalking.apm.toolkit.log.logback.v1.x.LogbackPatternConverter"/>
<!-- add converter for %sw_ctx -->
<conversionRuleconversionWord="sw_ctx"converterClass="org.apache.skywalking.apm.toolkit.log.logback.v1.x.LogbackSkyWalkingContextPatternConverter"/>
<springProfilename="local,test,prod">
<appendername="console"class="ch.qos.logback.core.ConsoleAppender">
<encoderclass="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<pattern>
<pattern>
${JSON_LOG_PATTERN}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<rootlevel="INFO">
<appender-refref="console"/>
</root>
</springProfile>
</configuration>
- 修改traceId的取值,并且新增了skyWalkingContext,具体的含义大家可以去参考官网,这里不详细讲述。
日志在改造完成以后,重新部署以后我们就可以看到traceId和skyWalkingContext,如下图:
我们拿着traceId去查Skywalking页面上可以看到整个请求链路(如下图),同样的我们在Skywalking页面上拿到的traceId也可以去日志系统中拉取到所有的日志。
总结
当系统随着业务的发展变得错综复杂时,我们则需要借助一些工具来可视化我们的整个系统,作为开发我们需要学会"偷懒",学会利用工具来帮我们减少定位问题的时间,学会利用工具梳理我们系统中还不完善的点去改进优化它,帮助系统跑的更快更稳。