介绍
Apache SkywalkingApache基金下由国人开发的分布式链路追踪系统,Skeywalking可以为微服务,云原生应用提供可视化链路追踪服务,通过Skywalking可以方便的查看服务的调用链路和性能瓶颈。 ELK是三个项目首字母的缩写,分别为Elasticsearch、Logstash 和 Kibana,Elasticsearch 是一个搜索和分析引擎。Logstash 是服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到诸如 Elasticsearch 等“存储库”中。Kibana 则可以让用户在 Elasticsearch 中使用图形和图表对数据进行可视化。通过ELK用户可以在Kibana中方便查询应用日志,借助Elasticsearch强大的搜索能力,对于大数据量的日志也能进行快速检索。Skywalking和ELK的集成,实际上就是将日志和调用链路关联起来,在交易出问题需要排查时,可以在Skeywalking查看其调用链路以及调用时长,到具体日志,需要到ELK中进行查看,两个系统需要有个标识符能够将其关联起来,这个标识符就是Trace id
,skywalking会为每一笔交易生成一个traceid,在服务的调用链中会将该id传递到各个调用节点,服务在打印日志时,需要将该id带上,这样就能将其关联起来。
实现
我们以spring boot项目集成skywalking和elk为例介绍具体的做法
skywalking接入
spring boot需要先集成Skywalking,从官网下载skywalking压缩包并解压,其中包含一个agent
目录
agent ├── activations ├── bootstrap-plugins ├── config ├── logs ├── optional-plugins ├── optional-reporter-plugins ├── plugins └── skywalking-agent.jar
在spring boot应用程序启动命令及上以下参数
java -javaagent:/you/path/apache-skywalking-apm-bin/agent/skywalking-agent.jar -jar app.jar
默认情况下skywalking-agent会读取config文件夹下文件agent.config作为配置文件,其中collector.backend_service
指定了OAP的地址,使用的是OAP的gRPC端口,默认为11800
collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:127.0.0.1:11800}
如果不想使用配置文件,可以直接使用以下命令
java -javaagent:/you/path/apache-skywalking-apm-bin/agent/skywalking-agent.jar -Dskywalking.agent.service_name=appName -Dskywalking.collector.backend_service=127.0.0.1:11800 -jar app.jar
maven依赖
添加maven依赖
<dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>6.6</version> </dependency> <dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-logback-1.x</artifactId> <version>8.6.0</version> </dependency>
logstash-logback-encoder可以将日志输出到logstash中,apm-toolkit-logback-1.x可以在日志中插入traceid
logback配置
在resources目录下新加文件logback-spring.xml
,如果项目已创建则可直接用,完整配置如下
<?xml version="1.0" encoding="UTF-8"?> <configuration> <springProperty scope="local" name="appName" source="spring.application.name" /> <!-- <include resource="org/springframework/boot/logging/logback/base.xml"/>--> <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender"> <destination>elk.definesys.com:4567</destination> <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder"> <provider class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.logstash.TraceIdJsonProvider"> </provider> <customFields>{"app":"${appName}"}</customFields> </encoder> </appender> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout"> <pattern>%d{HH:mm:ss.SSS} %-5level logger_name:%logger{36} - [%tid] : %msg%n</pattern> </layout> </encoder> </appender> <root level="INFO"> <appender-ref ref="LOGSTASH"/> <appender-ref ref="STDOUT"/> </root> <!-- 日志过滤 --> <logger name="org.apache.catalina.core.ContainerBase*" level="WARN"/> <logger name="org.springframework.web.servlet.DispatcherServlet" level="WARN"/> <logger name="com.zaxxer.hikari.HikariDataSource" level="WARN"/> </configuration>
这里重新定义了控制台的日志格式ConsoleAppender方便演示,实际环境中,可根据需求进行定制
destination为logstash地址端口,logstash配置如下
input{ tcp { mode => "server" host => "0.0.0.0" port => 4567 codec => json_lines } } output{ elasticsearch{ hosts=>["localhost:9200"] index => "log-%{+YYYY.MM.dd}" } }
测试验证
@GetMapping(value = "orderDetail") private Response orderDetail(@RequestParam(value = "orderNo") String orderNo) { Logger logger = LoggerFactory.getLogger(ProjectController.class); logger.info("order no===>{}", orderNo); return Response.ok().data(projectService.queryOrderDetail(orderNo)); }
启动应用,访问接口,在控制台可以看到以下日志
21:53:02.170 INFO logger_name:c.d.d.s.p.c.ProjectController - [TID:eacb092e76594dcdb413adf5e37d4fae.97.16269619821690001] : order no===>DFS0009845
其中TID就是traceid,登录skywalking,在追踪界面通过traceid进行查询

可以看到调用链路,以及每个节点耗时。
登录kibana,输入TID:"eacb092e76594dcdb413adf5e37d4fae.97.16269619821690001"
进行查询

如果所有的日志汇总到同一个索引下,那么可以查到所有节点上的相关日志,我们注意到日志里面有两个字段

这个是logback配置文件中配置了自定义字段
<springProperty scope="local" name="appName" source="spring.application.name" /> .... <customFields>{"app":"${appName}"}</customFields>
appName引用了application.properties中的spring.application.name属性,建议加上该字段,可以根据应用名称进行日志搜索
DevOps
上面介绍了spring boot中集成skywalking和elk的方案,但在实际生产中,如何将这个方案做到通用,做到通用有以下几个要求
- 通用,所有应用都可以通过统一的步骤,统一的模板进行集成
- 透明,日志模块相对较为独立,并且方案较为统一,上面的方法是和应用绑定在一起,耦合性较强,并不推荐在应用中直接集成,应用只需负责输出log即可,集成的步骤可以在devops 打包阶段由脚本完成
目前系统devops流水线包括以下工具
- 应用会被制作成docker镜像运行在docker中
- jenkins负责执行脚本进行编译打包部署
- kubernetes作为应用运行平台
- elk和skywalking都是运行在kubernetes环境中
应用打包
我们只需将以下三个文件打入应用中就可以将一个未集成的spring boot应用改造成集成好的应用
- logback-spring.xml文件
- logstash-logback-encoder-xx.jar logstash插件包
- apm-toolkit-logback-1.x skywalking插件包
创建一个空目录,把涉及到的资源放到该目录下
apm-toolkit-logback-1.x-8.1.0.jar app.jar logback-spring.xml logstash-logback-encoder-6.6.jar
在该目录下创建bash脚本package.sh
脚本如下
mkdir -p BOOT-INF/lib/ mkdir -p BOOT-INF/classes/ cp -rf apm-toolkit-logback-1.x-8.1.0.jar BOOT-INF/lib/ cp -rf logstash-logback-encoder-6.6.jar BOOT-INF/lib/ cp -rf logback-spring.xml BOOT-INF/classes/ jar -uvf0 app.jar BOOT-INF/lib/apm-toolkit-logback-1.x-8.1.0.jar jar -uvf0 app.jar BOOT-INF/lib/logstash-logback-encoder-6.6.jar jar -uvf0 app.jar BOOT-INF/classes/logback-spring.xml
执行./package.sh
对应用进行重新打包,将相关资源更新到应用中,脚本执行完执行以下命令启动应用,登录skywalking和kibana验证结果
java -javaagent:/you/path/skywalking-agent/skywalking-agent.jar -jar app.jar
docker镜像
我们需要把skywalking的agent目录放到镜像中,可以创建一个空目录,把涉及到的资源全部放到该目录下
. ├── Dockerfile ├── skywalking-agent │ ├── activations │ ├── bootstrap-plugins │ ├── config │ ├── logs │ ├── optional-plugins │ ├── optional-reporter-plugins │ ├── plugins │ └── skywalking-agent.jar └── starfire-service-1.0.jar
- Dockerfile
dockerfile文件如下,基础镜像为openjdk:8-jdk-alpine
,在dockerfile中做了日期处理,将容器的日期改为中国上海时区
FROM openjdk:8-jdk-alpine MAINTAINER definesys.com RUN apk add --no-cache tzdata && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone && \ apk add --update-cache curl bash libc6-compat vim COPY skywalking-agent/ /usr/local/skywalking-agent ENV LANG=zh_CN.UTF-8 ENV TZ=Asia/Shanghai VOLUME /tmp ADD starfire-service-1.0.jar app.jar RUN echo "Asia/Shanghai" > /etc/timezone EXPOSE 8080 ENTRYPOINT ["java","-javaagent:/usr/local/skywalking-agent/skywalking-agent.jar","-Djava.security.egd=file:/dev/./urandom","-Dfile.encoding=UTF-8", "-jar","app.jar"]
- starfire-service-1.0.jar这个为应用文件
- skywaling-agent/config文件中的agent.config文件后期可以通过configmap挂载进行覆盖
- 容器入口命令ENTRYPOINT指定了skywalking的javaagent
执行以下命令构建镜像
docker build -t starfire-service:v1.0 .
构建完后启动应用进行测试
➜ docker run -it --rm starfire-service:v1.0 DEBUG 2021-07-23 11:49:41:939 main AgentPackagePath : The beacon class location is jar:file:/usr/local/skywalking-agent/skywalking-agent.jar!/org/apache/skywalking/apm/agent/core/boot/AgentPackagePath.class. INFO 2021-07-23 11:49:41:950 main SnifferConfigInitializer : Config file found in /usr/local/skywalking-agent/config/agent.config. ....
从日志可以看出skywalking配置已经生效,但如果每个应用在构建镜像时都把skywalking构建到镜像中其实违反了我们上面提到的通用原则,要做到通用,建议**构建一个包含skywalking agent的基础镜像,应用从该基础镜像再进行构建**
no comment untill now