Kubnernetes 部署 Zipkin 搭配 Kafka+ElasticSearch 实现链路追踪


相关博文:

系统环境:

一、链路追踪介绍

为什么要链路追踪:

​ 随着互联网发展,分布式化应用越来越流行,微服务业务越来越复杂化,这些组件共同构成了繁杂的分布式网络,那现在的问题是一个请求经过了这些服务后其中出现了一个调用失败的问题,但具体的异常在哪个服务引起的就需要进入每一个服务里面看日志,这样的处理效率是非常低的。所以链路追踪技术孕育而生,让分布式应用引入链路,在发咋分布式环境下收集链路日志,分析服务间依赖、耗时、错误信息,这样当遇到问题时候能够快速定位问题所在。

链路追踪简介:

​ 分布式调用链其实就是将一次分布式请求还原成调用链路。显式的在后端查看一次分布式请求的调用情况,比如各个节点上的耗时、请求具体打到了哪台机器上、每个服务节点的请求状态等等。

Zipkin 简介:

​ Zipkin 是一款开源的分布式实时数据追踪系统,由基于 Google Dapper 的论文设计而来,由 Twitter 公司提供开源实现,主要功能是聚集来自各个异构系统的实时监控数据,和微服务架构下的接口直接的调用链路和系统延时问题。

img

二、组件介绍

这里需要用到下面几个组件来完成链路追踪的整个流程,先简单分别介绍一下:

Kafka:

一个高性能消息队列,这里用于接收服务链路日志信息,异步操作减少峰值。

Kibana:

分析和可视化平台,设计用于分析、展示 Elasticsearch 中数据。

ElasticSearch:

Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,能快速检索数据,这里用于存储链路日志信息。

SpringCloud Feign:

声明式的 web service 客户端,而 SpringCloud Feign 在这个基础上加上很多 SpringCloud 相关组件,当引入 Sleuth 组件时候能将链路日志信息进行服务间传递。

三、链路追踪部署流程

这里有两种部署流程,这里简单介绍下:

1、两种日志采集方式

  • 方式一: 将链路日志直接推送到 Zipkin Server 进行聚合,存储到 ElasticSearch 中,最后再用 Zipkin UI 展示链路过程。
  • 方式二: 将链路日志推送到 Kafka,然后启动 Zipkin Server 聚合日志,监听 Kafka ,如果有新的消息则进行拉取存入到 ElasticSeach,最后再用 Zipkin UI 展示链路过程。

两种方式的比较:

​ 第一种方式使用与配置起来比较简单,并且在 Kubernetes 中能够很容易的横向扩展来处理一定的链路日志数据,不过如果服务过多,链路日志数据量过大还是可能造成 Zipkin Server 的崩溃,所以比较适合服务数量不大的境中。

​ 第二种方式使用与配置比较复杂,需要 Kubernetes 集群中部署 KafkaZookeeper,通过将链路日志数据写入 Kafka 进行削峰,再由 Kafka 写入 Zipkin Server 进行聚合,所以比较适合数据量大、服务多的环境。

2、流程图

方式一:Zipkin Server + ElasticSearch

img

方式二:Zipkin Server + ElasticSearch + Kafka

img

四、准备部署环境

在实际生产环境中,一般都需要配合 Kafka 完成链路工作,所以我们将围绕第二种来介绍,需要准备下面组件:

  • Kafka: 需要拥有在 Kubernetes 环境中能访问的 Kafka 集群。
  • ElasticSearch: 需要拥有在 Kubernetes 环境中能访问的 ElasticSearch 集群。
  • Zipkin: 在 Kubernetes 中部署 Zipkin,后面将演示这个部署的过程。
  • 两个 SpringCloud 服务: 需要两个 SpringCloud 服务,通过 Feign 相互调用接口产生链路日志便于测试,后面将演示如何写测试项目部署到 Kubernetes 中。

​ 在 Kubernetes 中完成链路流程的方案,需要依赖上面各个组件,这些组件最好部署在 Kubernetes 中来保证稳定,关于如何在 Kubernetes 中部署 KafkaElasticSearchKibana 本人之前发的博文中已经有过部署流程,所以不再过度描述,如果 Kubernetes 集群外已经有能够使用的上面组件并且还能正常调用,直接调用即可。

接下来将演示在 Kubernetes 中部署 Zipkin 与两个 SpringCloud 服务,完成链路部署工作。

五、Kubernetes 部署 Zipkin

1、部署 Zipkin Server

​ Zipkin Server 是用于收集链路日志信息进行聚合,然后保存数据,并通过 UI 展示数据的组件,这里新建部署文件,然后执行 Kuberctl 命令在 Kubernetes 集群创建 Zipkin Server,并且由于 Zipkin Server 需要监听 Kafka,所以这里也要设置 Kafka 相关配置。

关于 Zipkin Server 配置参数,可以参考 Zipkin Server Github

zipkin-server.yaml

apiVersion: v1
kind: Service
metadata:
  name: zipkin
  labels:
    app: zipkin
spec:
  type: NodePort        #指定为 NodePort 方式暴露出口
  ports:
    - name: server
      port: 9411
      targetPort: 9411
      nodePort: 30190   #指定 Nodeport 端口
      protocol: TCP
  selector:
    app: zipkin
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: zipkin
  labels:
    name: zipkin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: zipkin
  template:
    metadata:
      labels:
        app: zipkin
    spec:
      containers:
      - name: zipkin
        image: openzipkin/zipkin:2.15
        ports:
        - containerPort: 9411
        env:
        - name: JAVA_OPTS
          value: "
                  -Xms512m -Xmx512m 
                  -Dlogging.level.zipkin=DEBUG 
                  -Dlogging.level.zipkin2=DEBUG 
                  -Duser.timezone=Asia/Shanghai
                  "
        - name: STORAGE_TYPE
          value: "elasticsearch"   #设置数据存储在ES中
        - name: ES_HOSTS        
          value: "elasticsearch-client.mydlqcloud:9200"   #ES地址
        - name: ES_INDEX           #设置ES中存储的zipkin索引名称
          value: "zipkin"
        - name: ES_INDEX_REPLICAS  #ES索引副本数
          value: "1"
        - name: ES_INDEX_SHARDS    #ES分片数量
          value: "3"
        #- name: ES_USERNAME       #如果ES启用x-pack,需要设置用户名、密码
        #  value: ""
        #- name: ES_PASSWORD
        #  value: ""
        - name: KAFKA_BOOTSTRAP_SERVERS #Kafka 地址
          value: "kafka.mydlqcloud:9092"
        - name: KAFKA_TOPIC             #Kafka Topic名称,默认为"zipkin"
          value: "zipkin"
        - name: KAFKA_GROUP_ID          #Kafka 组名,默认为"zipkin"
          value: "zipkin"       
        - name: KAFKA_STREAMS           #消耗Topic的线程数,默认为1
          value: "1"     
        resources: 
          limits:
            cpu: 1000m
            memory: 512Mi
          requests:
            cpu: 500m
            memory: 256Mi

部署 Zipkin-Server 到 Kubernetes

  • -n:指定应用部署的 Namespace

    $ kubectl apply -f zipkin-server.yaml -n mydlqcloud

​ 部署完成后就能通过集群 IP + NodePort 端口访问 Zipkin UI。可以看到 Zipkin 页面,这里我的 Kubernetes 集群地址为 192.168.2.11 ,上面指定的 NodePort 端口为 30190,所以输入地址 http://192.168.2.11:30190 访问 Zipkin UI,本人模拟写入一段链路数据来测试,不过只能看到 UI 界面 Traces 面板有服务间调用信息,点击 Dependency Links 没有发现服务间关联图标相关信息。

imgimg

​ 经过分析后原来是因为 Zipkin Server 只是负责收集聚合、存储数据到数据库,并不会分析服务间依赖关系,需要展示这一部分图标还需要部署 zipkin-dependencies 后再回到当前 UI 界面查看。

2、部署 Zipkin-Dependencies

​ zipkin-dependencies 是一个聚合数据依赖关系的服务,这里启动服务后它会自动从 ElasticSearch 中获取索引,分析依赖关系然后再以 zipkin索引名称-dependency-yyyy-mm-dd 命名创建新索引存入 ElasticSearch。

​ 并且这个服务内置 Crond 定时任务,默认每隔一小时会执行分析 ElasticSearch 中索引关系的任务(在 Kubernetes 中将其设置一个 Job 任务来使用也是可以的,因为它每次启动时候都会先进行分析依赖数据,当然也可以用容器内部的 Crond 来执行定时任务)。

注意:下面 yaml 中一定要设置 command 命令来启用 crond 定时任务,否则之后执行一次分析依赖关系任务后程序自动关闭。

zipkin-dependencies.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: zipkin-dependencies
  labels:
    name: zipkin-dependencies
spec:
  replicas: 1
  selector:
    matchLabels:
      app: zipkin-dependencies
  template:
    metadata:
      labels:
        app: zipkin-dependencies
    spec:
      containers:
      - name: zipkin
        image: openzipkin/zipkin-dependencies:2.3.1
        ports:
        - containerPort: 80
        command: ["sh","-c","crond -f"]         
        env:
        - name: JAVA_OPTS
          value: "-Xms512m -Xmx512m"
        - name: STORAGE_TYPE
          value: "elasticsearch"
        - name: ES_HOSTS
          value: "elasticsearch-client.mydlqcloud:9200"
        - name: ES_INDEX        #设置ES中存储的zipkin索引名称
          value: "zipkin"
        #- name: ES_USERNAME    #如果ES启用x-pack,需要设置用户名、密码
        #  value: ""
        #- name: ES_PASSWORD
        #  value: ""
        resources: 
          limits:
            cpu: 1000m
            memory: 512Mi
          requests:
            cpu: 500m
            memory: 256Mi

部署 Zipkin-Dependencies 到 Kubernetes

  • -n:指定应用部署的 Namespace

    $ kubectl apply -f zipkin-dependencies.yaml -n mydlqcloud

部署完成后再次打开 Zipkin UI 界面,点击进入 Dependency Links,然后按下分析按钮后出现链路依赖关系图。

img

六、Kubernetes 部署 SpringCloud 服务

​ 这里在 Kubernetes 中部署基于 SpringBoot 的sleuth-service-providersleuth-service-customer服务,引入 SpringCloud FeignSprinCloud SleuthZipkinKafka、等组件,两个服务间相互调用产生链路日志信息。

1、创建 sleuth-service-provider 服务

Maven 引入相关 Jar 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>club.mydlq</groupId>
    <artifactId>sleuth-service-provider</artifactId>
    <version>0.0.1</version>
    <name>sleuth-service-provider</name>
    <description>sleuth service provider</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--SpringBoot Web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--SpringBoot Actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--Seluth-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <!--Zipkin-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <!--Kafka-->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Provider 服务配置文件

创建 application.yaml 配置文件加入下面配置:

server:
  port: 8080

management:
  server:
    port: 8081
  endpoints:
    web:
      exposure:
        include: "*"

spring:
  application:
    name: sleuth-service-provider
  sleuth:
    sampler:
      probability: 1.0   #采集率,最大1.0(百分百采集),默认0.1(百分之十采集)
  zipkin:
    #base-url: http://192.168.2.11:30190    #Zipkin Server地址,这里使用了Kafka所以被注掉
    sender:
      type: kafka     #指定发送到kafka,还可以指定Rabbit、Web
    service:
      name: ${spring.application.name} #Zipkin链路日志中收集的服务名称
  kafka:
    bootstrap-servers: kafka.mydlqcloud:9092  #Kubernetes中Kakfa 地址,当然也可以指定 Kubernetes集群外的 Kafka 地址

Controller 类中写一个供 Consumer 使用 Feign 调用的接口

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SleuthController {

    @GetMapping("/")
    public String getSleuthInfo() {
        return "链路测试";
    }

}

启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

2、创建 sleuth-service-customer 服务

Maven 引入相关 Jar 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>club.mydlq</groupId>
    <artifactId>sleuth-service-customer</artifactId>
    <version>0.0.1</version>
    <name>sleuth-service-customer</name>
    <description>sleuth service customer</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--SpringBoot Web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--SpringBoot Actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--Feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <!--Seluth-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <!--Zipkin-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <!--Kafka-->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Customer 服务配置文件

创建 application.yaml 配置文件加入下面配置:

server:
  port: 8080

management:
  server:
    port: 8081
  endpoints:
    web:
      exposure:
        include: "*"

spring:
  application:
    name: sleuth-service-customer
  sleuth:
    sampler:
      probability: 1.0    #采集率,最大1.0(百分百采集),默认0.1(百分之十采集)
  zipkin:
    #base-url: http://192.168.2.11:30190    #Zipkin Server地址,这里使用了Kafka所以被注掉
    sender:
      type: kafka     #指定发送到kafka,还可以指定Rabbit、Web
    service:
      name: ${spring.application.name} #Zipkin链路日志中收集的服务名称
  kafka:
    bootstrap-servers: kafka.mydlqcloud:9092  #Kubernetes中Kakfa地址,当然也可以指定Kubernetes集群外的Kafka地址

Feign 调用 Provider 服务的接口类

配置 Feign 接口指向调用 sleuth-service-provider 服务接口

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "http://sleuth-service-provider:8080", url = "http://sleuth-service-provider:8080")
public interface ProviderService {

    @GetMapping("/")
    public String getSleuthInfo();

}

Controller 类中写一个调用 Feign 的接口

import club.mydlq.k8s.feign.ProviderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
public class SleuthController {

    @Autowired
    private ProviderService providerService;

    @GetMapping("/")
    public String getInfo(){
        return providerService.getSleuthInfo();
    }

}

启动类

引入 @EnableFeignClients 注解开启 Feign

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

3、准备 Kubernetes 部署文件

在 Kubernetes 中部署服务需要两个部署的 yaml 文件,分别为 sleuth-service-provider.yamlsleuth-service-customer.yaml

sleuth-service-provider.yaml

apiVersion: v1
kind: Service
metadata:
  name: sleuth-service-provider
spec:
  type: NodePort
  ports:
    - name: server
      nodePort: 31005
      port: 8080
      targetPort: 8080
    - name: management
      nodePort: 31006
      port: 8081
      targetPort: 8081
  selector:
    app: sleuth-service-provider
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleuth-service-provider
  labels:
    app: sleuth-service-provider
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sleuth-service-provider
  template:
    metadata:
      name: sleuth-service-provider
      labels:
        app: sleuth-service-provider
    spec:
      restartPolicy: Always     #为了方便测试,设置镜像每次都从新拉取新镜像
      containers:
        - name: sleuth-service-provider
          image: registry.cn-beijing.aliyuncs.com/mydlq/sleuth-service-provider:0.0.1
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
              name: server
            - containerPort: 8081
              name: management
          resources:
            limits:
              memory: 256Mi
              cpu: 1000m
            requests:
              memory: 128Mi
              cpu: 500m

sleuth-service-customer.yaml

apiVersion: v1
kind: Service
metadata:
  name: sleuth-service-customer
spec:
  type: NodePort        #通过NodePort方式暴露服务
  ports:
    - name: server
      nodePort: 31007   #设置NodePort端口为31007
      port: 8080
      targetPort: 8080
    - name: management
      nodePort: 31008
      port: 8081
      targetPort: 8081
  selector:
    app: sleuth-service-customer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleuth-service-customer
  labels:
    app: sleuth-service-customer
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sleuth-service-customer
  template:
    metadata:
      name: sleuth-service-customer
      labels:
        app: sleuth-service-customer
    spec:
      restartPolicy: Always      #为了方便测试,设置镜像每次都从新拉取新镜像
      containers:
        - name: sleuth-service-customer
          image: registry.cn-beijing.aliyuncs.com/mydlq/sleuth-service-customer:0.0.1
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
              name: server
            - containerPort: 8081
              name: management
          resources:
            limits:
              memory: 256Mi
              cpu: 1000m
            requests:
              memory: 128Mi
              cpu: 500m

4、部署服务到 Kubernetes 集群

执行 Kubectl 命令,部署两个服务到 Kubernetes 集群中:

-n:指定应用部署的 Namespace

#部署 sleuth-service-provider 服务
$ kubectl apply -f sleuth-service-provider.yaml -n mydlqcloud

#部署 sleuth-service-customer 服务
$ kubectl apply -f sleuth-service-customer.yaml -n mydlqcloud

七、测试查看链路信息

​ 两个 SpringBoot 应用部署完成以后,将进行测试,首先需要调用服务 sleuth-service-customer 接口,让其通过 Feign 调用 Kubernetes 中服务 sleuth-service-provider 产生链路日志写入 Kafka,然后再查看 Kibana 中查看 ElasticSearch 中是否有对应以 zipkin 命名的索引生成,且能查询到链路日志。最后在查看 Zipkin UI 是否有链路信息。

各组件信息:

  • Kubernetes 地址:192.168.2.11
  • Zipkin UI NodePort 地址:192.168.2.11:30190
  • Kibana UI NodePort 地址:192.168.2.11:31537
  • sleuth-service-customer NodePort 端口:192.168.2.11:31007

(1)、调用服务接口产生链路日志

输入地址:http://192.168.2.11:31007 调用 sleuth-service-customer 接口,由于上面设置的链路日志采集率为 0.1 ,所以调用十次接口才会生成一条链路日志信息,所以这里我们最好多访问写次数。

(2)、配置 Kibana 索引模式,方便在 Kibana 中查看链路日志信息

本人这里 KibanaElasticSearch 部署在 Kubernetes 中,且 Kibana 地址为:192.168.2.11:31537,打开这个地址访问 Kibana,然后进入 Management 选项,配置 Index Patterns ,将 zipkin 索引加入其中。

img

配置完成后进入 Discocer 界面查看链路日志数据:

img

(3)、进入 Zipkin UI 查看链路信息

如果上面 Kibana 中已经能看到链路日志信息了,代表链路日志信息已经存入 Kibana 中,否则请检查 KafkaZipkin Server 配置是否正确。

如果都没有问题,那么访问 Zipkin UI,输入地址:http://192.168.2.11:30190,选择一小时内的数据,点击查找按钮后能搜索出来几条链路数据,点开查看,能看到这个链路携带的日志信息。

imgimg

—END—

这些信息有用吗?
Do you have any suggestions for improvement?

Thanks for your feedback!