软件技术学习笔记

个人博客,记录软件技术与程序员的点点滴滴。

使用Elastic Stack观察分布式服务—K8s: Logging, Metrics, Tracing, APM

落地分布式微服务架构时,遥测信息收集是一个重要的环节。如果没有日志收集、指标收集、分布式追踪、应用性能管理,将导致问题定位效率低下、无法自动弹性伸缩、无法自动预警等,最终可能导致微服务架构落地失败。

在K8s中,我们可以使用Elastic Stack完成这些事情。Elasticsearch完成数据存储与搜索;Kibana提供可视化的用户界面;Beats完成数据采集;APM完成分布式追踪、应用性能管理。

在K8s集成Istio之后,虽然Jaeger可以追踪到Mesh边界,但仍需在代码中转发x-request-idx-b3-...等头部信息,也没有比Elastic APM优美。同时,我们大部分时候只关注应用边界的性能,很少关注Mesh边界的性能。因此,在单独K8s或K8s + Istio集群中,Elastic Stack满足了我们的APM需求。

Elastic Stack数据流

本文将演示:

  1. 在K8s集群中,如何部署Elastic Stack (Elasticsearch、Filebeat、Kibana、Metricbeat、APM)。
  2. 在GoLang HTTP/gRPC代码中,如何集成APM。
  3. Kibana中相关用户界面。

1. 部署Elastic Stack

除了 APM,官方均已经提供了Helm Charts,使用官方的Charts都可以部署到K8s集群中。Metricbeat还需要kube-state-metrics;APM的部署见“APM Server”。下文中,非特殊说明情况下,我们把Elastic Stack部署到K8s的logging namespace中,在 helm 命令中带上 --namespace logging 参数。

如今,Elastic官网已经提供很多的Helm Charts,在K8s上部署比较以前方便多了。

Elasticsearch

ES集群部署;参考:https://github.com/elastic/helm-charts/tree/master/elasticsearch 。官方提供的Charts需要3个工作节点,否则,ES集群启动失败(状态为非健康)。

单实例部署:本地环境时,我们只需要一个单实例,节省资源。主要的改动是:1、使用LocalStorage的PV;2、修改ES的启动参数到单实例;3、去除ES自身的集群健康检查。相对于ES集群部署,statefulset.yaml和values.yaml有差异。

Elasticsearch v7.1.1单实例的StatefulSets,见文件statefulset.yaml

values.yaml的差异内容为:

# Diff in elasticsearch/values.yaml
replicas: 1

volumeClaimTemplate:
  accessModes: [ "ReadWriteOnce" ]
  storageClassName: "local-storage"
  selector:
    matchLabels:
      type: "logging-es-data"
  resources:
    requests:
      storage: 2Gi

Filebeat

Fiberbeat用来收集Docker容器日志,我们的服务日志就是通过它来搜集的。以DaemonSet的形式部署,没有什么需要特别注意的。

参考官方 https://github.com/elastic/helm-charts/tree/master/filebeat ,使用Helm两条命令搞定。

要部署到 logging namespace时,执行helm install --namespace logging --name filebeat elastic/filebeat

Metricbeat

Metricbeat需要kube-state-metrics

先部署kube-state-metrics,见 https://github.com/kubernetes/kube-state-metrics#kubernetes-deployment,执行kubectl apply -f examples/standard

再安装Elastic官网指导部署Metricbeat,见 https://github.com/elastic/helm-charts/tree/master/metricbeat

APM Server

参考本站提供的:apm-server.yaml。修改 apm-server.yaml 符合自己的要求,再执行 kubectl apply -f apm-server.yaml

Kibana

参考Elastic官方的指导: https://github.com/elastic/helm-charts/tree/master/kibana

2. GoLang代码中集成APM

Elastic Stack中,只有APM需要在业务代码中集成,其它的都是部署完成就可以使用。Elastic APM提供各种开发语言代理,只需少量代码即可集成。 GoLang集成APM,官方文件见: https://www.elastic.co/guide/en/apm/agent/go/current/instrumenting-source.html

将调用下一级微服务时,需要把当前服务接入的ctx context.Context传递到下一级调用的第一个入参Context上。否则,会造成调用链分布式追踪丢失!

GoLang HTTP客户端集成APM

关键函数是apmhttp.WrapClient(),追踪ID传输在HTTP HEAD中。

import (
    "go.elastic.co/apm"
    "go.elastic.co/apm/module/apmhttp"
    "golang.org/x/net/context/ctxhttp"
)

var tracingClient = apmhttp.WrapClient(http.DefaultClient)

func ginRequestHandler(c *gin.Context) {
        req := c.Request
        resp, err := ctxhttp.Get(req.Context(), tracingClient, userServerURL+"/api/user/v1/userInfoBySession")

        if err != nil {
            apm.CaptureError(req.Context(), err).Send()
            log.Println("ERROR   request user info")
            c.AbortWithError(http.StatusInternalServerError, errors.New("failed to query backend"))
            return
        }

        ...
}

GoLang HTTP服务端Gin集成APM

关键函数是apmgin.Middleware()

import (
    "github.com/gin-gonic/gin"
    "go.elastic.co/apm/module/apmgin"
)

func main() {
    engine := gin.New()
    engine.Use(apmgin.Middleware(engine))

    engine.GET("/healthz", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "OK",
        })
    })
}

GoLang gRPC客户端集成APM

关键函数是apmgrpc.NewUnaryClientInterceptor(),追踪ID传输在gRPC的MetaData中。

import (
    "go.elastic.co/apm/module/apmgrpc"
    "google.golang.org/grpc"
)

func connectGrpc() {
    conn, err := grpc.Dial(productServerAddress, grpc.WithInsecure(),
        grpc.WithUnaryInterceptor(apmgrpc.NewUnaryClientInterceptor()))
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
}

GoLang gRPC服务端集成APM

关键函数是apmgrpc.NewUnaryServerInterceptor()

import (
    "go.elastic.co/apm/module/apmgrpc"
    "google.golang.org/grpc"
)

func main() {
    s := grpc.NewServer(grpc.UnaryInterceptor(apmgrpc.NewUnaryServerInterceptor()))
    ...
}

3. Kibana中相关用户界面。

Kibana的数据可视化功能非常强大,基于遥测数据可以显示各种图/预警,具体见Elastic官网的Kibana介绍。下面只展示与遥测数据相关的3个基本界面。

基础设施监测

日志

APM