Istio 上线可能会遇到的坑总结

我们从迁移服务到 Istio 到现在稳定运行已经有半年时间。虽然在使用 Istio 的过程中,整体过程还算顺利,但是我们还是碰到了一些大大小小的问题,我们把这些问题以及解决方法做一个总结,希望能帮助到有意愿使用 Istio 的团队和个人。

我们碰到的 Istio 使用问题:

  • k8s 中 service 的命名端口问题
  • gRPC 服务端使用 TLS 证书问题
  • 服务没有监听 127.0.0.1问题
  • HTTP 1.0 协议问题
  • 由 keepalive 导致的偶发 503
  • Proxy 与应用的启动顺序不固定
  • 路由域名冲突问题
  • Istio 导致 PV 挂载问题
  • Headless 服务问题

k8s 中 service 的命名端口问题

service 使用命名端口

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376

Istio 实现了 HTTP 和 HTTP2 流量的自动探测,当服务没有使用命名端口时候,并且开启了流量自动探测功时(默认开启),Istio 可以进行自动的流量探测来识别协议。但是如果你使用了命名端口,但是指定错了协议,那就会导致服务无法正常访问,通常会得到 connection reset 的错误。

具体支持协议的类型,以及自动探测相关文档可以参考Istio 协议选择

gRPC 服务端使用 TLS 问题

当你有服务使用 gRPC 协议并且使用 TLS 证书时,你需要把服务的协议类型命名为 https 或者 tls ,而不能直接使用 grpc ,具体问题可参考Istio issue

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: my-grpc-service-with-tls
spec:
selector:
app: MyApp
ports:
- name: https
protocol: TCP
port: 80
targetPort: 9376

服务没有监听 127.0.0.1 问题

如上图所示,当服务 A 访问服务 B 时,实际上流量是先达到服务 A 的 Proxy 上的,然后再到服务 B 的 Proxy 上的,然后服务 B 上的 Proxy 会访问本地 127.0.0.1 地址对应的端口。如果服务 B 没有监听 127.0.0.1 地址上的对应端口,就会导致服务 B 无法自动被访问,出现 connection refused 的情况。

迁移 zookeeper 或者 redis 到 Istio 中时,就会碰到类似问题,官方有专门的FAQ 说明

HTTP 1.0 协议问题

由于 Istio 不支持 HTTP 1.0 版本的协议,所以当客户端在 Istio 集群中使用 HTTP 1.0 协议时,就会出现无法正常访问的情况。比如当使用 Nginx 作了代理时,默认情况下会使用 HTTP 1.0 协议,访问 Nginx 时,就会出现 503 无法正常访问的情况。具体问题可见官方帮助文档

由 keepalive 导致的偶发 503

默认情况下 Istio 的 Proxy 会跟后端服务保持长连接,TCP 默认情况下保持跟系统的 tcp keepalive 一样,linux 系统一般默认为 7200s ,HTTP 协议默认设置为 1h,除非后端主动断开,否则连接将会一直被重复使用。

由于后端服务设置的 keepalive 时间过短,在高并发情况下,可能会出现当后端服务发送断开请求数据包 fin 时,数据包还没有达到 Proxy 时, Proxy 认为此连接依然正常可用,正好有请求到达 Proxy, 于是 Proxy 仍然会发请求给这个连接,由于后端服务已经发送了断开请求数据包 fin 包,当这条连接再有数据过来时,会直接返回 tcp reset ,导致 Proxy 返回 503 给调用方。

解法方法:

可以通过给服务的 destinationrule 设置 keepalive 时间,让 Proxy 的 keepalive 时间小于后端服务的 keepalive 时间,让 Proxy 主动断开连接。

HTTP 层可以设置idleTimeout参数

TCP 层可以设置tcpKeepalive参数

Proxy 与应用的启动顺序不固定

由于 k8s 的 pod 中的容器并不保证启动顺序,所以可能导致应用启动早于 Proxy,如果此时应用对外发起请求,就会出现请求失败的情况,应用可能会出现反复重启的情况,比如应用启动时会去先拿配置中心的配置,然后再启动应用。

路由域名冲突问题

某些版本的 Istio 当出现域名冲突时,会停止更新路由,导致路由不正常,集群无法正常提供服务。比如当配置了相同域名的 service 和 serviceentry 时

Istio 导致 PV 挂载问题

在 Istio 1.5 版本之后,加入了third party service account tokens 来增强 Proxy 到控制平面的认证。但是由于k8s 的 bug ,导致 Istio 使用了另一个方式绕过这个 bug ,但是这就导致了另外一个问题。由于使用 fsGroup ,会导致每次挂载 PV 时,都要修改整个 PV 中的文件权限,当文件比较多时,就可能会导致挂载超时,无法成功挂载 PV,问题可见Istio issue。不管使用的是third-party-jwt还是first-party-jwt,都会有该问题。

我之前的解决方案是使用first-party-jwt,并且注释了 Istio 代码中强制插入 fsGroup 的代码,重新编译了 pilot 镜像,替换了官方的镜像。

Headless 服务问题

Istio 默认情况下会开启服务间的兼容模式的 mTLS,即服务可以支持 mTLS 的方式访问,也可以使用 plain 流量访问。当两个服务服务都部署在 Istio 集群中时,会默认使用 mTLS 方式访问,当 Istio 集群中服务访问没有部署在 Istio 集群中的服务时,默认使用 plain 流量访问。但是由于 Istio 1.6 之前的版本对于 headless 服务支持不够完善,导致部署在 Istio 集群中的服务无法访问没有部署在 Istio 集群中的 headless 服务,访问时会出现 connection reset 的错误。排查后发现是由于 Istio 把集群中访问 headless 服务的路由规则设置成了只使用 mTLS 方式访问,所以导致了访问 Istio 集群外的 headless 服务出现问题。

具体分析可见Istio 运维实战系列(2):让人头大的『无头服务』-上

总结

以上这些问题都是我们在生产环境碰到的问题,有一些是我们自己的使用方式问题,还有一些是 Istio 自身的问题,甚至还有一些是 k8s 的问题,不过我们都找到了还不错的解决方法,希望以后 Istio 能越来越好用稳定。