亚马逊AWS官方博客

如何利用指标和日志排查App Mesh相关网络问题

Amazon App Mesh是亚马逊云科技的托管服务网格解决方案。通过使用Sidecar部署方式,App Mesh可以在不对应用程序进行更改的前提下,提供网络流量控制,流量加密和可观测性,帮助您轻松运行服务,而无需修改应用源代码。

Amazon App Mesh使用开源的Envoy代理来接管进出容器的网络流量,以达到进行流量控制的目的。但由于流量需要通过Envoy进行转发,在相对复杂的网络环境下,Envoy和应用自身产生的非正常返回值(如503, 504等)会混杂在一起,导致很难判断具体故障。

Envoy有着完善的可观测性功能,会以通用的Prometheus格式暴露指标。管理员可以通过指标直观的查看Envoy的运行状况并定位故障。Envoy也同时提供访问日志功能,可以更详细的查找每条请求的来源,目标,返回值等基本信息。对于非正常返回的请求,也会通过自定义Header或在报错中包含的方式,提示问题根因。

本文会介绍如何在Amazon Elastic Kubernetes Service (以下简称Amazon EKS) 环境下收集App Mesh暴露的指标和日志,并通过这些资源分析问题。其他运行环境(Amazon Elastic Container Service,Amazon EC2)下仅有采集方式不同,分析方式相对一致,故在此不再赘述。

先决条件

  • 拥有管理员权限的亚马逊云科技账号
  • 正在运行的Amazon EKS集群,版本为1.21以上
  • 已安装kubectlhelm客户端
  • 使用者对Amazon EKS和Amazon App Mesh有基本了解

部署示例应用

部署Amazon App Mesh Controller

在Amazon EKS环境,可以使用Amazon App Mesh Controller创建并管理Amazon App Mesh资源,并自动为启动的Pod注入Envoy。Amazon App Mesh Controller可以通过Helm Chart部署。请参考此文档以部署Amazon App Mesh Controller。

部署示例应用

亚马逊云科技提供了示例应用以演示Amazon App Mesh的功能。运行以下命令以部署howto-k8s-connection-pools应用,该应用会演示连接池功能。请将123456789012替换为您的12位亚马逊云科技账户ID,us-west-2替换为您Amazon EKS  集群所在的区域。

cd ~
git clone https://github.com/aws/aws-app-mesh-examples
cd aws-app-mesh-examples/walkthroughs/howto-k8s-connection-pools/
export AWS_ACCOUNT_ID=123456789012
export AWS_DEFAULT_REGION=us-west-2
./deploy.sh

这一部署脚本会构建应用镜像,推送至Amazon ECR,并将应用部署到Amazon EKS。Amazon App Mesh Controller会自动在Pod中插入Envoy。

可以通过kubectl get all -n howto-k8s-connection-pools检查是否部署完成:

部署完成后,将创建的Service打上标签以便下一步进行服务发现。运行下列命令:

kubectl label svc color-green -n howto-k8s-connection-pools package=howto-k8s-connection-pools
kubectl label svc color-red -n howto-k8s-connection-pools package=howto-k8s-connection-pools
kubectl label svc color-paths -n howto-k8s-connection-pools package=howto-k8s-connection-pools
kubectl label svc ingress-gw -n howto-k8s-connection-pools package=howto-k8s-connection-pools

利用Prometheus抓取Amazon App Mesh数据平面指标

Amazon App Mesh采用Envoy作为数据平面。Envoy通过/stats接口提供数百个指标以展现Envoy自身和处理的连接状况。连接会根据其状况(返回值,是否触发连接池,是否重试,中断原因等)被添加到某项指标中。 Envoy支持通过statsd和Prometheus格式暴露指标。在Amazon EKS环境下,可以使用Prometheus收集指标,并使用Grafana可视化分析。

安装Prometheus

使用kube-prometheus快速部署完整的Prometheus集群。该方案会部署Prometheus Operator,并通过Operator部署Prometheus实例。同时会部署node-exporter, kube-state-metrics, alertmanager和Grafana,快速构建完整的监控体系。在终端运行以下命令:

cd ~/environment/
git clone https://github.com/prometheus-operatorkube-prometheus.git --branch release-0.11
cd kube-prometheus

默认情况下,创建的Prometheus没有持久化存储,会导致数据在Pod重启后丢失。可参考文档 编辑manifests/prometheus-prometheus.yaml,自行为Prometheus添加持久化存储。

为访问Prometheus查询指标,需要将Prometheus通过负载均衡器对外暴露。修改manifests/prometheus-service.yaml

apiVersion: v1
kind: Service
...
spec:
 ...
 sessionAffinity: ClientIP # 删除该⾏
 type: LoadBalancer # 添加该⾏

默认情况下,Prometheus无法抓取来自其他Namespace的内容。需要为Prometheus提供更多权限。将manifestsprometheus-clusterRole.yaml替换成以下内容:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    app.kubernetes.io/component: prometheus
    app.kubernetes.io/instance: k8s
    app.kubernetes.io/name: prometheus
    app.kubernetes.io/part-of: kube-prometheus
    app.kubernetes.io/version: 2.36.1
  name: prometheus-k8s
rules:
- apiGroups:
  - ""
  resources:
  - nodes/metrics
  verbs:
  - get
- nonResourceURLs:
  - /metrics
  verbs:
  - get
- apiGroups:
  - ""
  resources:
  - services
  - endpoints
  - pods
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - networking.k8s.io
  resources:
  - ingresses
  verbs:
  - get
  - list
  - watch

修改完成后,进行部署:

kubectl apply --server-side -f manifests/setup
until kubectl get servicemonitors --all-namespaces ; do date; sleep 1; echo ""; done
kubectl apply -f manifests/

组件默认会部署在monitoring命名空间。部署完成后,可使kubectl get po -n monitoring检查Pod运行状况:

可以通过kubectl get svc prometheus-k8s -n monitoring获取访问Prometheus的地址,并通过http://<Prometheus地址>:9090/访问:

利用Prometheus获取Envoy指标

Envoy默认将指标通过/stats/prometheus接口以Prometheus兼容格式将指标暴露出来。通过配置ServiceMonitor,Prometheus可以通过服务发现的形式,自动抓取Service对应的Pod,从而捕获Envoy暴露的指标。

创建servicemonitor.yaml,内容如下:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: howto-k8s-connection-pools
  namespace: monitoring
  labels:
    package: howto-k8s-connection-pools
spec:
  endpoints:
  - targetPort: 9901
    path: /stats/prometheus
    interval: 5s
  namespaceSelector:
    any: true
  selector:
    matchLabels:
      package: howto-k8s-connection-pools

运行kubectl apply -f servicemonitor.yaml将其部署至集群。部署完成后,在Prometheus界面中的StatusTargets应当可以看到运行的Pod。   返回Graph,运行查询envoy_server_live{},结果数量应与目前部署Pod数量一致。   此时,Envoy的指标已被Prometheus所捕获,可以通过Prometheus对指标进行分析。

启用Envoy访问日志

Amazon App Mesh允许用户快速启用Envoy访问日志。默认日志的格式为:

[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%"
%RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION%
%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%"
"%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%"\n

可以参考此Envoy文档 获取日志各字段的详细含义。其中对故障排查作用较大的为RESPONSE_CODERESPONSE_FLAGS,这两个指标描述了返回值和非正常返回时的报错原因。

可以通过修改Virtual Node的参数以启用访问日志。运行以下命令编辑创建好的Virtual Node:

kubectl edit virtualnode green -n howto-k8s-connection-pools

apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualNode
metadata:
...
spec:
...
  serviceDiscovery:
    dns:
      hostname: color-green.howto-k8s-connection-pools.svc.cluster.local
  logging: # 加入以下内容
    accessLog:
      file:
        path: "/dev/stdout"

该配置生效后,Envoy会将访问日志输出至标准输出,可以通过kubectl logs <Pod名> -c envoy 命令查看。

可以通过Fluent Bit等日志收集器将日志收集到其他存储,或使用Log Hub等日志解决方案进行收集。

注:目前通过Amazon App Mesh Controller创建的Virtual Node无法自定义日志格式。

通过指标和日志进行分析

下面会以连接池场景为例,测试如何根据指标和日志分析错误原因。样例应用已经为green Virtual Node配置了连接池,详细配置可以通过kubectl get virtualnode green -n howto-k8s-connection-pools -o yaml查看。具体配置如下所示:

  listeners:
  - connectionPool:
      http:
        maxConnections: 10
        maxPendingRequests: 10

可以看到,该配置允许最多10个并发请求,在连接池中还可以有10个请求在等待。

在部署示例应用时,同时在default命名空间部署了一个fortio Pod。Fortio 是一个负载测试组件,可以进行基于http或grpc的负载测试。您可以利用部署的fortio生成远超于该并发数的流量以进行测试,以模拟流量过载的场景。运行以下命令:

FORTIO=$(kubectl get pod -l "app=fortio" --output=jsonpath={.items..metadata.name})
kubectl exec -it $FORTIO -- fortio load -allow-initial-errors  -c 30 -qps 9000 -t 600s http://color-green.howto-k8s-connection-pools:8080

此时您可以新建一个终端窗口,模拟其他用户访问:

FORTIO=$(kubectl get pod -l "app=fortio" --output=jsonpath={.items..metadata.name})
kubectl exec -it $FORTIO -- fortio load --curl http://color-green.howto-k8s-connection-pools:8080

多运行几次,会发现访问出错,错误日志如下所示:

$ kubectl exec -it $FORTIO -- fortio load --curl http://color-green.howto-k8s-connection-pools:8080
10:04:04 W http_client.go:942> [0] Non ok http code 503 (HTTP/1.1 503)
HTTP/1.1 503 Service Unavailable
x-envoy-overloaded: true
content-length: 81
content-type: text/plain
date: Thu, 01 Dec 2022 10:04:04 GMT
server: envoy

upstream connect error or disconnect/reset before headers. reset reason: overflow

根据返回HTTP头中的server字段,可以判断该报错是由Envoy返回。您可以从报错信息和Envoy添加的自定义HTTP头 x-envoy-overloaded: true 中了解到错误原因是overflow。通过查询Envoy文档,可以发现该错误来源于断路器,当连接池满时会产生该报错。

您可以检查Envoy访问日志以进一步确认:

[2022-12-01T10:04:07.584Z] "GET / HTTP/1.1" 503 UO 0 81 0 - "-" "fortio.org/fortio-1.39.0-pre4" "93739f95-2dbe-90c3-9f68-b9b829cf652c" "color-green.howto-k8s-connection-pools:8080" "-"

可以看到,该请求返回值是503,错误码是UO。通过查询Envoy访问日志文档,UO错误码代表Upstream overflow (circuit breaking) in addition to 503 response code.,同样显示断路器开启,导致Envoy直接返回503。

除了访问日志外,您还可以通过指标查找错误原因。由于断路器工作在Cluster层面,您可以从Cluster的指标列表中找到相关指标,在Prometheus上运行: envoy_cluster_circuit_breakers_default_cx_open{pod~="green-.*"}

此时可以看到,标注为envoy_cluster_name="cds_ingress_howto-k8s-connection-pools_green_howto-k8s-connection-pools_http_8080"的值为1。根据App Mesh的命名规则,带Ingress前缀的Cluster的作用是Envoy向应用发送入站请求。该指标置1意味着入站断路器开启,Envoy阻断了发往应用的请求。

为检查应用本身的运行状况,可以运行下列查询:

  • envoy_cluster_upstream_rq_timeout{pod~="green-.*", envoy_cluster_name="cds_ingress_howto-k8s-connection-pools_green_howto-k8s-connection-pools_http_8080"}
  • envoy_cluster_upstream_cx_connect_fail{pod~="green-.*", envoy_cluster_name="cds_ingress_howto-k8s-connection-pools_green_howto-k8s-connection-pools_http_8080"}}

这两条查询会反馈Envoy向应用发出的请求是否超时或连接失败。可以看到,这两次查询返回值都是0,也就意味着应用正常工作。

综合以上分析可以得知,此时应用仍然正常工作,但受断路器限制,连接在Envoy处由于连接池满,被提前终止,从而返回503错误。通过修改Virtual Node中的ConnectionPool配置,问题解决。

常见的故障分析流程

从刚才的示例中您可以看到,Envoy会通过多种手段暴露错误信息以便排查。在真实环境,不知道错误来源的情况下,可以按照以下步骤进行排查:

  1. 如果可以获得报错时的完整响应Header和消息,则Envoy会尽可能的将错误原因通过自定义Header(一般为x-envoy开头)返回。可以根据错误原因查找对应的指标或日志错误码。
  2. 可以通过分布式跟踪,日志搜索或指标监控确定报错时访问到的Pod,通过查找访问日志确定问题来源和根因。
  3. 如果无法获得访问日志,则需要通过指标查询来确认具体问题根因。由于涉及到的指标较多,建议提前对关键的报错指标设置告警。

清理

为防止创建的资源产生额外费用,您可以清理创建的资源。

  1. 在终端中,运行kubectl delete all -n howto-k8s-connection-pools以删除部署的示例应用,负载均衡器及Amazon App Mesh资源。
  2. 运行以下命令以删除部署的Prometheus实例。
cd ~/environment/kube-prometheus
kubectl delete -f manifests/
kubectl delete -f manifests/setup
  1. 进入Amazon ECR控制台,删除创建的镜像仓库。

总结

Envoy提供了指标,错误消息和访问日志三种方式,来帮助管理员分析异常返回的具体原因。本文简单介绍了如何使用Prometheus监控Envoy指标,如何开启访问日志,以及在故障发生时如何利用这三种手段快速定位问题。限于篇幅,本文并未详细介绍Envoy的其他指标和日志含义,各位可以从Envoy官方文档和社区资源中自行探索。

扩展阅读

本篇作者

于昺蛟

亚马逊云科技现代化应用解决方案架构师,负责亚马逊云科技容器和无服务器产品的架构咨询和设计。在容器平台的建设和运维,应用现代化,DevOps等领域有多年经验,致力于容器技术和现代化应用的推广。