在Kubernetes环境中对Go应用程序的追踪
首先
最近我经常在Kubernetes上部署自己开发的应用程序,但是当我想要调查和解决性能瓶颈问题时,发现无法轻松地通过SSH登录到服务器并启动分析器来定位慢速处理的过程,让我感到困扰。
我认为,与在虚拟机上进行应用开发时相比,由于服务组件的细化程度更高,所以使用以前的方法来调查瓶颈可能会花费更多时间。
由于有一些分布式跟踪的开源软件可以了解细分的服务行为以及进行性能分析,所以我希望利用这些软件来分析部署在Kubernetes上的Go应用程序。
环境
应用程序
https://github.com/amber-lamp/opencensus-sample
使用Docker for Mac的Kubernetes集成功能搭建了本地的Kubernetes集群。
本次我们将使用由CNCF托管的Jaeger进行追踪工具。
Jaeger是什么
概述
这是在Uber Technologies开发的开源分布式跟踪系统,受到Dapper和OpenZipkin的影响。它用于监控和故障排除微服务。它似乎可以实现以下具体功能:
-
- 分散コンテキストの伝播
-
- 分散トランザクションの監視
-
- 根本原因の分析
-
- サービス依存関係の分析
- パフォーマンス/レイテンシの最適化
在OpenTracing项目中,有一套标准规范,并且为每种语言提供了相应的客户端库以遵循该规范。对于Go语言来说,可以使用名为jaeger-client-go的库。
另一方面,也存在名为OpenCensus的“用于获取服务指标和分布式追踪的库集合”,OpenCensus也有用于将追踪信息发送到Jaeger的导出器。
这两个项目似乎会进行集成,并且除了Jaeger之外,还准备了其他后端(例如Datadog或Stackdriver等)的导出器,因此发送或切换到多个后端似乎也很简单。所以我想试试使用OpenCensus的Jaeger导出器将数据发送到Jaeger。
建筑设计
我們現在來看一下架構中的每個組件。
杰格特勤人员
这是一个用于定期将Span信息发送到jaeger-collector的守护程序。它被部署在收集所有主机信息的地方,并将发送信息至目标jaeger-collector的部分进行了抽象化。
钟楼收藏家
这是一个用于对来自jaeger-agent发送的信息进行索引等转换处理并保存到数据存储的组件。
捷格查询
这是一个组件,用于获取保存在数据存储中的Trace信息并在用户界面上显示。
试试看
建立Jaeger
我想要实际搭建环境。
这次我们将使用本地 Kubernetes 集群,因此我们将使用名为 jaeger-kubernetes 的 Kubernetes 清单文件来在 Kubernetes 上搭建 Jaeger。
除了上述的方法,还提供了Kubernetes Operator,因此在Kubernetes上搭建环境的难度较低。
由于Jaeger集线器(Jaeger-collector)和Jaeger查询(Jaeger-query)需要更新/查询数据存储,因此在Kubernetes的清单文件中提供了用于Cassandra和Elasticsearch的清单文件。
卡桑德拉使用
$ kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/production/configmap.yml
$ kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/production/cassandra.yml
Elasticsearch的使用
$ kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/production-elasticsearch/configmap.yml
$ kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/production-elasticsearch/elasticsearch.yml
我们还提供了一个名为all-in-one-template的清单文件,用于将数据保存在内存中,作为测试和开发的用途。这次我想尝试使用它作为验证之用。
$ kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/all-in-one/jaeger-all-in-one-template.yml
如果在Kubernetes集群上部署上述的all-in-one模板,将会创建如下资源。
deployment.extensions/jaeger created
service/jaeger-query created
service/jaeger-collector created
service/jaeger-agent created
service/zipkin created
我将检查基于all-in-one-template清单创建的Kubernetes Service。
有一个名为jaeger-query的Service,它的TYPE为LoadBalancer,因此我将尝试在浏览器中访问它。
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
jaeger-agent ClusterIP None <none> 5775/UDP,6831/UDP,6832/UDP,5778/TCP 32m
jaeger-collector ClusterIP 10.100.80.4 <none> 14267/TCP,14268/TCP,9411/TCP 32m
jaeger-query LoadBalancer 10.99.72.71 localhost 80:30762/TCP 32m
zipkin ClusterIP None <none> 9411/TCP 32m
Jaeger代理、Jaeger收集器、Jaeger查询、UI等各个组件在Kubernetes集群上的配置是所有组件都被统一放在一个部署中。
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
jaeger 1 1 1 1 10m
请参考以下链接:https://www.jaegertracing.io/docs/1.8/getting-started/#all-in-one
只需将all-in-one-template部署到Kubernetes集群上,就准备好了,然后只需将应用程序的追踪信息发送到jaeger-agent,就可以在上述UI上进行确认了。
应用程序的设置
設定將參考opencensus-go-exporter-jaeger的範例進行。我將整個原始碼推送到GitHub,請隨時參考下面的存儲庫。
https://github.com/amber-lamp/opencensus-sample
请先导入以下两个包。
import (
~~省略~~
"contrib.go.opencensus.io/exporter/jaeger"
"go.opencensus.io/trace"
)
在main函数中,我们对exporter和sampler进行了配置。
我们在jaeger.Options的AgentEndpoint中指定了jaeger-agent的Kubernetes服务名称和端口。
此外,在trace.ApplyConfig部分,我们将DefaultSampler设置为trace.AlwaysSample()。为了进行验证,我们将所有访问信息都作为样本收集,并在UI上显示。
func main() {
exporter, err := jaeger.NewExporter(jaeger.Options{
AgentEndpoint: "jaeger-agent:6831",
Process: jaeger.Process{
ServiceName: "demo",
},
})
if err != nil {
log.Fatal(err)
}
defer exporter.Flush()
trace.RegisterExporter(exporter)
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
log.Println("Start demo server...")
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
在handler函数中开始了一个Span。我将context作为第一个参数,将用于识别Span的字符作为第二个参数。
func handler(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(context.Background(), "/handler")
defer span.End()
omikuji := omikuji(ctx)
fmt.Fprintf(w, "運勢は[%s]です", omikuji)
}
最后,我们来看一下omikuji函数中调用了handler函数。
我们将trace.StartSpan函数的第一个参数设定为handler函数开启Span时返回的context。
通过这样做,当在Jaeger的UI界面上查看时,可以看到omikuji函数开启的Span被显示为handler函数的子Span。
在函数的下面有一个childSpan.Annotate,通过这个可以在Span上进行标注。
仔细观察omikuji函数,只有在”凶”的时候才会睡眠1秒,导致处理时间变长。但是通过给结果和对应的消息添加注释信息,可以在Jaeger的用户界面上判断在哪种条件下处理变慢。
func omikuji(ctx context.Context) string{
_, childSpan := trace.StartSpan(ctx, "/omikuji")
defer childSpan.End()
t := time.Now()
var omikuji string
var msg string
if (t.Month() == 1 && t.Day() >= 1 && t.Day() <= 3){
omikuji = "大吉"
msg = "お正月は大吉"
} else {
t := t.UnixNano()
rand.Seed(t)
s := rand.Intn(6)
switch s + 1 {
case 1:
omikuji = "凶"
msg = "残念でした"
time.Sleep(time.Second)
case 2, 3:
omikuji = "吉"
msg = "そこそこでした"
case 4, 5:
omikuji = "中吉"
msg = "まあまあでした"
case 6:
omikuji = "大吉"
msg = "いいですね"
}
}
childSpan.Annotate([]trace.Attribute{
trace.StringAttribute("omikuji", omikuji),
}, msg)
return omikuji
}
我们来看一下将创建的应用程序部署在Kubernetes上会怎样。
已经将创建的Go应用程序推送到DockerHub,因此将其部署到本地的Kubernetes集群中。
虽然实际上应该从Kubernetes的清单文件中部署,但在这里我们将使用kubectl run命令进行部署。
$ kubectl run opencensus-sample --image=yuuyamad/opencensus-sample:latest
通过使用kubectl get pods命令,确认已部署的Pod是否正在运行。
由于所有Pod都处于Running状态,看起来一切都很顺利。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
jaeger-796dcb98cb-xjj26 1/1 Running 0 7m
opencensus-sample-6996b4f94b-pfskk 1/1 Running 0 39s
要访问部署在Kubernetes集群上的应用程序,通常会使用Service或Ingress等负载均衡器的功能从集群外部进行访问。但在这里,我们将使用PortForward直接访问Pod并进行确认。
$ kubectl port-forward deployment/opencensus-sample 8080 8080
总结
我使用分散追踪开源软件Jaeger来分析Go应用程序。虽然这次只追踪了一个服务,但是我觉得,如果涉及跨多个服务的情况,也可以进行类似的追踪。在运营基于微服务架构的系统时,我觉得这是必备的技术。