使用Elastic Stack观察分布式服务—K8s: Logging, Metrics, Tracing, APM
落地分布式微服务架构时,遥测信息收集是一个重要的环节。如果没有日志收集、指标收集、分布式追踪、应用性能管理,将导致问题定位效率低下、无法自动弹性伸缩、无法自动预警等,最终可能导致微服务架构落地失败。
在K8s中,我们可以使用Elastic Stack完成这些事情。Elasticsearch完成数据存储与搜索;Kibana提供可视化的用户界面;Beats完成数据采集;APM完成分布式追踪、应用性能管理。
在K8s集成Istio之后,虽然Jaeger可以追踪到Mesh边界,但仍需在代码中转发x-request-id
与x-b3-...
等头部信息,也没有比Elastic APM优美。同时,我们大部分时候只关注应用边界的性能,很少关注Mesh边界的性能。因此,在单独K8s或K8s + Istio集群中,Elastic Stack满足了我们的APM需求。
本文将演示:
- 在K8s集群中,如何部署Elastic Stack (Elasticsearch、Filebeat、Kibana、Metricbeat、APM)。
- 在GoLang HTTP/gRPC代码中,如何集成APM。
- 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个基本界面。