网络问题排查

本节将介绍一些常用的工具和技能,用于解决流量管理方面的问题。

请求被 Envoy 拒绝

要了解拒绝请求的原因,最佳方式是检查 Envoy 的访问日志。默认情况下,访问日志输出到容器的标准输出。运行以下命令以查看日志:

  1. $ kubectl logs -it PODNAME -c istio-proxy -n NAMESPACE

在默认访问日志格式中,Envoy响应标志和 Mixer 策略状态位于响应代码之后,如果您使用的是自定义日志格式,请确保包含%RESPONSE_FLAGS%%DYNAMIC_METADATA(istio.mixer:status)%

详细的响应标志请参考 Envoy 响应标志

常见的响应标志是:

  • NR: 没有配置路由, 检查你的 DestinationRuleVirtualService
  • UO: 上游溢出,熔断, 在 DestinationRule 中检查你的熔断器配置。
  • UF: 无法连接到上游, 如果你正在使用Istio身份验证,请检查双向 TLS 配置冲突。如果响应标志为 UAEX 且 Mixer 策略状态不是 -,则 Mixer 拒绝请求。

通用 Mixer 策略状态为:

  • UNAVAILABLE: Envoy 无法连接到 Mixer,策略配置为关闭失败。
  • UNAUTHENTICATED: Mixer 身份验证拒绝该请求。
  • PERMISSION_DENIED: Mixer 授权拒绝该请求。
  • RESOURCE_EXHAUSTED: Mixer 配额拒绝该请求。
  • INTERNAL: 由于 Mixer 内部错误,请求被拒绝。

路由规则好像没有生效

在当前版本的 Envoy Sidecar 实现中,可能要 100 个请求才能观察到有权重版本的路由分发过程。

如果一组路由规则能够完美的和 Bookinfo 配合,但是类似的版本路由功能在其它应用上无法生效,一个可能的解决方法就是对 Kubernetes Service 进行一点改动。Kubernetes Service 的定义必须符合一定规范,才能享受到 Istio 的七层路由特性。应该根据 Istio 对 Pod 和服务的要求对服务进行调整。

还有个潜在问题就是路由的生效速度很慢。Istio 在 Kubernetes 上实现了一种最终一致性算法,这个算法用于保障所有 Envoy Sidecar 都能够获得正确的配置信息,其中就包含所有的路由规则。配置的变化需要一些时间来传递给所有的 Sidecar。在部署规模很大的情况下,这一传播过程会更长,可能会有数秒的延迟。

设置目标规则后出现 503 错误

在应用一个 DestinationRule 之后,如果对服务的请求突然开始生成 HTTP 503 错误,并且该错误会一直持续到删除或回滚 DestinationRule 为止,那么这个 DestinationRule 可能引起了服务的 TLS 冲突。

举个例子来说,如果在集群上启用了全局的双向 TLS,那么 DestinationRule 必须包含下面的 trafficPolicy 定义:

  1. trafficPolicy:
  2. tls:
  3. mode: ISTIO_MUTUAL

这一模式的缺省值是 DISABLE,会导致客户端 Sidecar 使用明文 HTTP 请求,而不是 TLS 加密请求;而服务端却又要求接收加密请求,因此就产生了冲突。

可以执行 istioctl authn tls-check 命令来检查这一问题,查看该命令的返回内容中的 STATUS 字段是否为 CONFLICT,例如:

  1. $ istioctl authn tls-check httpbin.default.svc.cluster.local
  2. HOST:PORT STATUS SERVER CLIENT AUTHN POLICY DESTINATION RULE
  3. httpbin.default.svc.cluster.local:8000 CONFLICT mTLS HTTP default/ httpbin/default

不论何时,在应用 DestinationRule 时都应该确认 trafficPolicy TLS 模式是否符合全局设置的要求。

路由规则在 Ingress Gateway 请求中无效

假设要使用 Ingress Gateway,结合 VirtualService 来访问一个内部服务。VirtualService 看起来大概是这样:

  1. apiVersion: networking.istio.io/v1alpha3
  2. kind: VirtualService
  3. metadata:
  4. name: myapp
  5. spec:
  6. hosts:
  7. - "myapp.com" # 如果在没有 DNS 的确情况下进行测试,使用 IP 来访问网关,也可以使用“*”
  8. gateways:
  9. - myapp-gateway
  10. http:
  11. - match:
  12. - uri:
  13. prefix: /hello
  14. route:
  15. - destination:
  16. host: helloworld.default.svc.cluster.local
  17. - match:
  18. ...

另外还有一个 VirtualService ,会把发往 helloworld 服务的请求路由到某个 subset

  1. apiVersion: networking.istio.io/v1alpha3
  2. kind: VirtualService
  3. metadata:
  4. name: helloworld
  5. spec:
  6. hosts:
  7. - helloworld.default.svc.cluster.local
  8. http:
  9. - route:
  10. - destination:
  11. host: helloworld.default.svc.cluster.local
  12. subset: v1

这种情况下会注意到,通过 Ingress 网关发出的对 helloworld 服务的请求并没有被重定向到 v1,而是继续使用缺省的轮询路由。

Ingress 请求使用的是网关定义的主机(例如 myapp.com)进行访问的,因此会使用 myapp 这个 VirtualService,结果就是路由到 helloworld 服务的任意端点;只有在网格内部发往主机 helloworld.default.svc.cluster.local 的请求才会使用 helloworld 这个 VirtualService,这个配置的目的就是将流量分配到 v1

要控制来自网关的流量,就需要把引流到 subset 的配置写入到 myapp 中:

  1. apiVersion: networking.istio.io/v1alpha3
  2. kind: VirtualService
  3. metadata:
  4. name: myapp
  5. spec:
  6. hosts:
  7. - "myapp.com" # 如果在没有 DNS 的确情况下进行测试,使用 IP 来访问网关,也可以使用“*” (例如 http://1.2.3.4/hello)
  8. gateways:
  9. - myapp-gateway
  10. http:
  11. - match:
  12. - uri:
  13. prefix: /hello
  14. route:
  15. - destination:
  16. host: helloworld.default.svc.cluster.local
  17. subset: v1
  18. - match:
  19. ...

还有一个方式就是把两个 VirtualService 合而为一:

  1. apiVersion: networking.istio.io/v1alpha3
  2. kind: VirtualService
  3. metadata:
  4. name: myapp
  5. spec:
  6. hosts:
  7. - myapp.com # 因为有了网格内的服务主机的定义,这里就不能用 * 了
  8. - helloworld.default.svc.cluster.local
  9. gateways:
  10. - mesh # 对内部网关也生效
  11. - myapp-gateway
  12. http:
  13. - match:
  14. - uri:
  15. prefix: /hello
  16. gateways:
  17. - myapp-gateway # 限制只对 Ingress 网关的流量有效
  18. route:
  19. - destination:
  20. host: helloworld.default.svc.cluster.local
  21. subset: v1
  22. - match:
  23. - gateways:
  24. - mesh # 对所有网格内服务有效
  25. route:
  26. - destination:
  27. host: helloworld.default.svc.cluster.local
  28. subset: v1

Headless TCP 服务连接丢失

如果部署了 istio-citadel,Envoy 会每隔 15 分钟重启一次,来完成证书的刷新任务。这就造成了服务间的长连接以及 TCP 流的中断。

建议提高应用的适应能力,来应对这种断流情况。如果要阻止这种情况的发生,就要禁止双向 TLS 和 istio-citadel 的部署。

首先编辑 istio 配置来禁止双向 TLS:

  1. $ kubectl edit configmap -n istio-system istio
  2. $ kubectl delete pods -n istio-system -l istio=pilot

接下来对 istio-citadel 进行缩容,来阻止 Envoy 的重启:

  1. $ kubectl scale --replicas=0 deploy/istio-citadel -n istio-system

这样应该就能阻止 Istio 重启 Envoy,从而防止 TCP 连接的中断。

Envoy 在高压情况下会崩溃

检查一下 ulimit -a,很多系统缺省设置打开文件描述符数量上限为 1024,会导致 Envoy 断言失败引发崩溃:

  1. [2017-05-17 03:00:52.735][14236][critical][assert] assert failure: fd_ != -1: external/envoy/source/common/network/connection_impl.cc:58

确认提高 ulimit 上限,例如 ulimit -n 16384

Envoy 无法连接 HTTP/1.0 服务

Envoy 要求上游服务提供 HTTP/1.1 或者 HTTP/2。例如当使用 NGINX 在 Envoy 之后提供服务时,就需要在 NGINX 配置文件中设置 proxy_http_version1.1,否则就会使用缺省值 1.0

配置样例:

  1. upstream http_backend {
  2. server 127.0.0.1:8080;
  3. keepalive 16;
  4. }
  5. server {
  6. ...
  7. location /http/ {
  8. proxy_pass http://http_backend;
  9. proxy_http_version 1.1;
  10. proxy_set_header Connection "";
  11. ...
  12. }
  13. }