**Amazon App Mesh** 是亚马逊云科技的托管服务网格解决方案。通过使用 Sidecar 部署方式,App Mesh 可以在不对应用程序进行更改的前提下,提供网络流量控制、流量加密和可观测性,帮助您轻松运行服务,而无需修改应用源代码。
Amazon App Mesh 使用开源的 **Envoy** 代理来接管进出容器的网络流量,以达到进行流量控制的目的。但由于流量需要通过 Envoy 进行转发,在相对复杂的网络环境下,Envoy 和应用自身产生的非正常返回值(如503、504等)会混杂在一起,导致很难判断具体故障。
Envoy 有着完善的可观测性功能,会以通用的 **Prometheus** 格式暴露指标。管理员可以通过指标直观地查看 Envoy 的运行状况并定位故障。Envoy 也同时提供访问日志功能,可以更详细地查找每条请求的来源、目标、返回值等基本信息。对于非正常返回的请求,也会通过自定义 Header 或在报错中包含的方式,提示问题根因。
本文会介绍如何在 **[Amazon Elastic Kubernetes Service](https://aws.amazon.com/cn/eks/?trk=cndc-detail)** (以下简称 [Amazon EKS](https://aws.amazon.com/cn/eks/?trk=cndc-detail)) 环境下收集 App Mesh 暴露的指标和日志,并通过这些资源分析问题。其他运行环境([Amazon Elastic Container Service](https://aws.amazon.com/cn/ecs/?trk=cndc-detail),Amazon EC2)下仅有采集方式不同,分析方式相对一致,故在此不再赘述。
Amazon App Mesh:
https://aws.amazon.com/cn/app-mesh/
Envoy:
https://www.envoyproxy.io/
Prometheus:
https://prometheus.io/
[Amazon Elastic Kubernetes Service](https://aws.amazon.com/cn/eks/?trk=cndc-detail):
https://aws.amazon.com/cn/eks/
##### **先决条件**
1.拥有管理员权限的亚马逊云科技账号;
2.正在运行的 [Amazon EKS](https://aws.amazon.com/cn/eks/?trk=cndc-detail) 集群,版本为1.21以上;
3.已安装 `kubectl` 和 `helm` 客户端;
4.使用者对 [Amazon EKS](https://aws.amazon.com/cn/eks/?trk=cndc-detail) 和 Amazon App Mesh 有基本了解。
## **部署示例应用**
### **部署 Amazon App Mesh Controller**
在 [Amazon EKS](https://aws.amazon.com/cn/eks/?trk=cndc-detail) 环境,可以使用 Amazon App Mesh Controller 创建并管理 Amazon App Mesh 资源,并自动为启动的 Pod 注入 Envoy。Amazon App Mesh Controller 可以通过 Helm Chart 部署。请参考**此文档**以部署 Amazon App Mesh Controller。
此文档:
https://github.com/awseks-charts/tree/master/stable/appmesh-controller
### **部署示例应用**
亚马逊云科技提供了示例应用以演示 Amazon App Mesh 的功能。运行以下命令以部署 `howto-k8s-connection-pools` 应用,该应用会演示连接池功能。请将`123456789012`替换为您的12位亚马逊云科技账户 ID, `us-west-2` 替换为您 [Amazon EKS](https://aws.amazon.com/cn/eks/?trk=cndc-detail) 集群所在的区域。
```
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](https://aws.amazon.com/cn/eks/?trk=cndc-detail)。Amazon App Mesh Controller 会自动在 Pod 中插入 Envoy。
可以通过 `kubectl get all -n howto-k8s-connection-pools` 检查是否部署完成:
![image.png](https://dev-media.amazoncloud.cn/c3ccddd335c343a5823839386217e9e6_image.png "image.png")
部署完成后,将创建的 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
```
Amazon ECR:
https://aws.amazon.com/cn/ecr/
## **利用 Prometheus 抓取 Amazon App Mesh 数据平面指标**
Amazon App Mesh 采用 Envoy 作为数据平面。Envoy通过 /stats 接口提供数百个指标以展现 Envoy 自身和处理的连接状况。连接会根据其状况(返回值、是否触发连接池、是否重试、中断原因等)被添加到某项指标中。Envoy 支持通过 statsd 和 Prometheus 格式暴露指标。在 [Amazon EKS](https://aws.amazon.com/cn/eks/?trk=cndc-detail) 环境下,可以使用 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 运行状况:
![image.png](https://dev-media.amazoncloud.cn/65086192db4b4da2b01f38a37c98b66d_image.png "image.png")
可以通过 `kubectl get svc prometheus-k8s -n monitoring` 获取访问 Prometheus 的地址,并通过 http://:9090/ 访问:
![image.png](https://dev-media.amazoncloud.cn/8073c330ec2345a0a1613cff5e56b7bc_image.png "image.png")
kube-prometheus:
https://github.com/prometheus-operator/kube-prometheus
文档:
https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/user-guides/storage.md
### **利用 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 界面中的 `Status – Targets` 应当可以看到运行的 Pod。
![image.png](https://dev-media.amazoncloud.cn/e7b2fa116a9a4bc395db74d8532c4003_image.png "image.png")
返回 Graph,运行查询 `envoy_server_live{}`,结果数量应与目前部署 Pod 数量一致。
![image.png](https://dev-media.amazoncloud.cn/3cd88c9d4d784284b4972d54808ceac1_image.png "image.png")
返回 Graph,运行查询 `envoy_server_live{}` ,结果数量应与目前部署 Pod 数量一致。
![image.png](https://dev-media.amazoncloud.cn/7be7d82283604622bf15f68c1e9c664e_image.png "image.png")
此时,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_CODE` 和 `RESPONSE_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 命令查看。
![image.png](https://dev-media.amazoncloud.cn/1e32fe7cab7f4f7b9da933a9f4e78a2b_image.png "image.png")
可以通过 Fluent Bit 等日志收集器将日志收集到其他存储,或使用 Log Hub 等日志解决方案进行收集。
注:目前通过 Amazon App Mesh Controller 创建的 Virtual Node 无法自定义日志格式。
Envoy 文档:
https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators
Fluent Bit:
https://fluentbit.io/
Log Hub:
https://awslabs.github.io/log-hub/zh/
## **通过指标和日志进行分析**
下面会以连接池场景为例,测试如何根据指标和日志分析错误原因。样例应用已经为 `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-.*"}`
![image.png](https://dev-media.amazoncloud.cn/11062703f4d04d53b3b1dae74414eadb_image.png "image.png")
此时可以看到,标注为 `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` 配置,问题解决。
Fortio:
https://github.com/fortio/fortio
Envoy 文档:
https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/circuit_breaking
Envoy 访问日志文档:
https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators
Cluster 的指标列表:
https://www.envoyproxy.io/docs/envoy/latest/configuration/upstream/cluster_manager/cluster_stats#circuit-breakers-statistics
App Mesh 的命名规则:
https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy-metrics.html
### **常见的故障分析流程**
从刚才的示例中您可以看到,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
```
3.进入 Amazon ECR 控制台,删除创建的镜像仓库。
## **总结**
Envoy 提供了指标,错误消息和访问日志三种方式,来帮助管理员分析异常返回的具体原因。本文简单介绍了如何使用 Prometheus 监控 Envoy 指标,如何开启访问日志,以及在故障发生时如何利用这三种手段快速定位问题。限于篇幅,本文并未详细介绍 Envoy 的其他指标和日志含义,各位可以从 Envoy 官方文档和社区资源中自行探索。
## **扩展阅读**
App Mesh 访问日志文档:
https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy-logs.html
App Mesh 指标文档:
https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy-metrics.html
Envoy 指标总览:
https://www.envoyproxy.io/docs/envoy/latest/operations/stats_overview
Envoy 错误消息列表:
https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/response_code_details
**本篇作者**
![image.png](https://dev-media.amazoncloud.cn/a11b65fab3fe4ace96015798060d022e_image.png "image.png")
**于昺蛟**
*亚马逊云科技现代化应用解决方案架构师,负责亚马逊云科技容器和[无服务器](https://aws.amazon.com/cn/serverless/?trk=cndc-detail)产品的架构咨询和设计。在容器平台的建设和运维,应用现代化,DevOps 等领域有多年经验,致力于容器技术和现代化应用的推广。*