Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Istio 运维实战系列(2):让人头大的『无头服务』-上

Istio 运维实战系列(2):让人头大的『无头服务』-上

作者头像
赵化冰
发布于 2022-08-01 02:42:38
发布于 2022-08-01 02:42:38
79600
代码可运行
举报
运行总次数:0
代码可运行

本系列文章将介绍用户从 Spring Cloud,Dubbo 等传统微服务框架迁移到 Istio 服务网格时的一些经验,以及在使用 Istio 过程中可能遇到的一些常见问题的解决方法。

什么是『无头服务』?

『无头服务』即 Kubernetes 中的 Headless Service。Service 是 Kubernetes 对后端一组提供相同服务的 Pod 的逻辑抽象和访问入口。Kubernetes 会根据调度算法为 Pod 分配一个运行节点,并随机分配一个 IP 地址;在很多情况下,我们还会对 Pod 进行水平伸缩,启动多个 Pod 来提供相同的服务。在有多个 Pod 并且 Pod IP 地址不固定的情况下,客户端很难通过 Pod 的 IP 地址来直接进行访问。为了解决这个问题,Kubernetes 采用 Service 资源来表示提供相同服务的一组 Pod。

在缺省情况下,Kubernetes 会为 Service 分配一个 Cluster IP,不管后端的 Pod IP 如何变化,Service 的 Cluster IP 始终是固定的。因此客户端可以通过这个 Cluster IP 来访问这一组 Pod 提供的服务,而无需再关注后端的各个真实的 Pod IP。我们可以将 Service 看做放在一组 Pod 前的一个负载均衡器,而 Cluster IP 就是该负载均衡器的地址,这个负载均衡器会关注后端这组 Pod 的变化,并把发向 Cluster IP 的请求转发到后端的 Pod 上。(备注:这只是对 Service 的一个简化描述,如果对 Service 的内部实现感兴趣,可以参考这篇文章如何为服务网格选择入口网关?

对于无状态的应用来说,客户端并不在意其连接的是哪一个 Pod,采用 Service 是没有问题的。但在某些特殊情况下,并不能这样做。例如,如果后端的这一组 Pod 是有状态的,需要由客户端根据某种应用相关的算法来选择哪一个 Pod 提供服务;或者客户端需要连接所有的后端 Pod,这时我们就不能在这一组 Pod 前放一个负载均衡器了。这种情况下,我们需要采用 Headless Service,即无头服务(该命名把多个 Pod 前面的负载均衡器比作服务的头,很形象是不是?)。在定义 Headless Service,我们需要把 Service 的 Cluster IP 显示设置为 None,这样 Kubernetes DNS 在解析该 Service 时会直接返回其后端的多个 Pod IP,而不是 Service 的 Cluster IP。

假设从客户端访问一个 Redis 集群,采用带 Cluster IP 的普通 Service 和 Headless Service 的过程分别如下图所示:

Istio 中『无头服务』的 mTLS 故障

由于 Headless Service 的特殊性,Istio 中对 Headless Service 的处理和普通 Service 有所不同,在应用迁移到 Isito 的过程中也常常遇到由于 Headless Service 导致的一些问题。下面我们就一个由于 Headless Service 的 mTLS 故障导致的典型案例进行说明。

故障现象:运维同学反馈从带 Envoy Sidecar 的 Pod 中不能访问 Redis 服务器,但在没有安装 Sidecar 的 Pod 中可以正常访问该 Redis 服务器。

遇到无法进行出向访问的问题,我们可以首先通过 Envoy 的管理接口来查看 Envoy 的访问日志。在客户端 Pod 中运行下面的命令查看 Envoy 日志:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
kubectl logs -f redis-client-6d4c6c975f-bm5w6 -c istio-proxy

日志中对 Redis 的访问记录如下,其中 UR,URX 是 Response Flag,表示 upstream connection failure,即连接上游失败。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[2020-09-12T13:38:23.077Z] "- - -" 0 UF,URX "-" "-" 0 0 1001 - "-" "-" "-" "-" "10.1.1.24:6379" outbound|6379||redis.default.svc.cluster.local - 10.1.1.24:6379 10.1.1.25:45940 - -

我们可以通过 Envoy 管理接口导出其 xDS 配置,以进一步分析其失败原因。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
kubectl exec redis-client-6d4c6c975f-bm5w6 -c istio-proxy curl http://127.0.0.1:15000/config_dump

由于是出向访问错误,因此我们主要关注客户端中该出向访问的 Cluster 的配置。在导出的 xDS 配置中,可以看到 Redis Cluster 的配置如下面的 yaml 片段所示(为了方便读者查看,去掉了该 yaml 中一些无关的内容):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
     "version_info": "2020-09-13T00:33:43Z/5",
     "cluster": {
      "@type": "type.googleapis.com/envoy.api.v2.Cluster",
      "name": "outbound|6379||redis.default.svc.cluster.local",
      "type": "ORIGINAL_DST",
      "connect_timeout": "1s",
      "lb_policy": "CLUSTER_PROVIDED",
      "circuit_breakers": {
        ...
      },

      # mTLS 相关设置
      "transport_socket": {
       "name": "envoy.transport_sockets.tls",
       "typed_config": {
        "@type": "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext",
        "common_tls_context": {
         "alpn_protocols": [
          "istio-peer-exchange",
          "istio"
         ],

         # 访问 Redis 使用的客户端证书
         "tls_certificate_sds_secret_configs": [
          {
           "name": "default",
           "sds_config": {
            "api_config_source": {
             "api_type": "GRPC",
             "grpc_services": [
              {
                "envoy_grpc": {
                "cluster_name": "sds-grpc"
               }
              }
             ]
            }
           }
          }
         ],

         "combined_validation_context": {
          "default_validation_context": {
           # 用于验证 Redis 服务器身份的 spiffe indentity
           "verify_subject_alt_name": [
            "spiffe://cluster.local/ns/default/sa/default"
           ]
          },
          # 用于验证 Redis 服务器的根证书
          "validation_context_sds_secret_config": {
           "name": "ROOTCA",
           "sds_config": {
            "api_config_source": {
             "api_type": "GRPC",
             "grpc_services": [
              {
               "envoy_grpc": {
                "cluster_name": "sds-grpc"
               }
              }
             ]
            }
           }
          }
         }
        },
        "sni": "outbound_.6379_._.redis.default.svc.cluster.local"
       }
      },
      "filters": [
       {
         ...
       }
      ]
     },
     "last_updated": "2020-09-13T00:33:43.862Z"
    }

在 transport_socket 部分的配置中,我们可以看到 Envoy 中配置了访问 Redis Cluster 的 tls 证书信息,包括 Envoy Sidecar 用于访问 Redis 使用的客户端证书,用于验证 Redis 服务器证书的根证书,以及采用 spiffe 格式表示的,需验证的服务器端身份信息。 这里的证书相关内容是使用 xDS 协议中的 SDS(Secret discovery service) 获取的,由于篇幅原因在本文中不对此展开进行介绍。如果需要了解 Istio 的证书和 SDS 相关机制,可以参考这篇文章一文带你彻底厘清 Isito 中的证书工作机制。从上述配置可以得知,当收到 Redis 客户端发起的请求后,客户端 Pod 中的 Envoy Sidecar 会使用 mTLS 向 Redis 服务器发起请求。

Redis 客户端中 Envoy Sidecar 的 mTLS 配置本身看来并没有什么问题。但我们之前已经得知该 Redis 服务并未安装 Envoy Sidecar,因此实际上 Redis 服务器端只能接收 plain TCP 请求。这就导致了客户端 Envoy Sidecar 在向 Redis 服务器创建链接时失败了。

Redis 客户端以为是这样的:

但实际上是这样的:

在服务器端没有安装 Envoy Sidecar,不支持 mTLS 的情况下,按理客户端的 Envoy 不应该采用 mTLS 向服务器端发起连接。这是怎么回事呢?我们对比一下客户端 Envoy 中的其他 Cluster 中的相关配置。

一个访问正常的 Cluster 的 mTLS 相关配置如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   {
     "version_info": "2020-09-13T00:32:39Z/4",
     "cluster": {
      "@type": "type.googleapis.com/envoy.api.v2.Cluster",
      "name": "outbound|8080||awesome-app.default.svc.cluster.local",
      "type": "EDS",
      "eds_cluster_config": {
       "eds_config": {
        "ads": {}
       },
       "service_name": "outbound|8080||awesome-app.default.svc.cluster.local"
      },
      "connect_timeout": "1s",
      "circuit_breakers": {
       ...
      },
      ...

      # mTLS 相关的配置
      "transport_socket_matches": [
       {
        "name": "tlsMode-istio",
        "match": {
         "tlsMode": "istio"  #对带有 "tlsMode": "istio" lable 的 endpoint,启用 mTLS
        },
        "transport_socket": {
         "name": "envoy.transport_sockets.tls",
         "typed_config": {
          "@type": "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext",
          "common_tls_context": {
           "alpn_protocols": [
            "istio-peer-exchange",
            "istio",
            "h2"
           ],
           "tls_certificate_sds_secret_configs": [
            {
             "name": "default",
             "sds_config": {
              "api_config_source": {
               "api_type": "GRPC",
               "grpc_services": [
                {
                 "envoy_grpc": {
                  "cluster_name": "sds-grpc"
                 }
                }
               ]
              }
             }
            }
           ],
           "combined_validation_context": {
            "default_validation_context": {},
            "validation_context_sds_secret_config": {
             "name": "ROOTCA",
             "sds_config": {
              "api_config_source": {
               "api_type": "GRPC",
               "grpc_services": [
                {
                 "envoy_grpc": {
                  "cluster_name": "sds-grpc"
                 }
                }
               ]
              }
             }
            }
           }
          },
          "sni": "outbound_.6379_._.redis1.dubbo.svc.cluster.local"
         }
        }
       },
       {
        "name": "tlsMode-disabled",
        "match": {},   # 对所有其他的 enpoint,不启用 mTLS,使用 plain TCP 进行连接
        "transport_socket": {
         "name": "envoy.transport_sockets.raw_buffer"
        }
       }
      ]
     },
     "last_updated": "2020-09-13T00:32:39.535Z"
    }

从配置中可以看到,一个正常的 Cluster 中有两部分 mTLS 相关的配置:tlsMode-istio 和 tlsMode-disabled。tlsMode-istio 部分和 Redis Cluster 的配置类似,但包含一个匹配条件(match部分),该条件表示只对带有 “tlsMode” : “istio” lable 的 endpoint 启用 mTLS;对于不带有该标签的 endpoint 则会采用 tlsMode-disabled 部分的配置,使用 raw_buffer,即 plain TCP 进行连接。

查看 Istio 的相关源代码,可以得知,当 Istio webhook 向 Pod 中注入 Envoy Sidecar 时,会同时为 Pod 添加一系列 label,其中就包括 “tlsMode” : “istio” 这个 label,如下面的代码片段所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  patchLabels := map[string]string{
		label.TLSMode:                                model.IstioMutualTLSModeLabel,
		model.IstioCanonicalServiceLabelName:         canonicalSvc,
		label.IstioRev:                               revision,
		model.IstioCanonicalServiceRevisionLabelName: canonicalRev,
	}

由于 Pod 在被注入 Envoy Sidecar 的同时被加上了该标签,客户端 Enovy Sidecar 在向该 Pod 发起连接时,根据 endpoint 中的标签匹配到 tlsMode-istio 中的配置,就会采用 mTLS;而如果一个 Pod 没有被注入 Envoy Sidecar,自然不会有该 Label,因此不能满足前面配置所示的匹配条件,客户端的 Envoy Sidecar 会根据 tlsMode-disabled 中的配置,采用 plain TCP 连接该 endpoint。这样同时兼容了服务器端支持和不支持 mTLS 两种情况。

下图展示了 Istio 中是如何通过 endpoint 的标签来兼容 mTLS 和 plain TCP 两种情况的。

通过和正常 Cluster 的对比,我们可以看到 Redis Cluster 的配置是有问题的,按理 Redis Cluster 的配置也应该通过 endpoint 的 tlsMode 标签进行判断,以决定客户端的 Envoy Sidecar 是通过 mTLS 还是 plain TCP 发起和 Redis 服务器的连接。但实际情况是 Redis Cluster 中只有 mTLS 的配置,导致了前面我们看到的连接失败故障。

Redis 是一个 Headless Service,通过在社区查找相关资料,发现 Istio 1.6 版本前对 Headless Service 的处理有问题,导致了该故障。参见这个 Issue Istio 1.5 prevents all connection attempts to Redis (headless) service #21964

解决方案

找到了故障原因后,要解决这个问题就很简单了。我们可以通过一个 Destination Rule 禁用 Redis Service 的 mTLS。如下面的 yaml 片段所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
kind: DestinationRule
metadata:
  name: redis-disable-mtls
spec:
  host: redis.default.svc.cluster.local
  trafficPolicy:
    tls:
      mode: DISABLE 

再查看客户端 Envoy 中的 Redis Cluster 配置,可以看到 mTLS 已经被禁用,Cluster 中不再有 mTLS 相关的证书配置。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    {
     "version_info": "2020-09-13T09:02:28Z/7",
     "cluster": {
      "@type": "type.googleapis.com/envoy.api.v2.Cluster",
      "name": "outbound|6379||redis.dubbo.svc.cluster.local",
      "type": "ORIGINAL_DST",
      "connect_timeout": "1s",
      "lb_policy": "CLUSTER_PROVIDED",
      "circuit_breakers": {
        ...
      },
      "metadata": {
       "filter_metadata": {
        "istio": {
         "config": "/apis/networking.istio.io/v1alpha3/namespaces/dubbo/destination-rule/redis-disable-mtls"
        }
       }
      },
      "filters": [
       {
        "name": "envoy.filters.network.upstream.metadata_exchange",
        "typed_config": {
         "@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
         "type_url": "type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange",
         "value": {
          "protocol": "istio-peer-exchange"
         }
        }
       }
      ]
     },
     "last_updated": "2020-09-13T09:02:28.514Z"
    }

此时再尝试从客户端访问 Redis 服务器,一切正常!

小结

Headless Service 是 Kubernetes 中一种没有 Cluster IP 的特殊 Service,Istio 中对 Headless Service 的处理流程和普通 Service 有所不同。由于 Headless Service 的特殊性,我们在将应用迁移到 Istio 的过程中常常会遇到与此相关的问题。这次我们遇到的问题是由于 Istio 1.6 版本前对 Headless Service 处理的一个 Bug 导致无法连接到 Headless Service。该问题是一个高频故障,我们已经遇到过多次。可以通过创建Destination Rule 禁用 Headless Service 的 mTLS 来规避该问题。该故障在1.6版本中已经修复,建议尽快升级到 1.6 版本,以彻底解决本问题。除了这一个故障以外,我们还在迁移过程中遇到了其他一些关于 Headless Service 的有意思的问题,在下一篇文章中再继续和大家分享。

参考文档


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Istio 运维实战系列(2):让人头大的『无头服务』-上
作者赵化冰,腾讯云高级工程师,Istio contributor,ServiceMesher管理委员,热衷于开源、网络和云计算。目前主要从事服务网格的开源和研发工作。 本系列文章将介绍用户从 Spring Cloud,Dubbo 等传统微服务框架迁移到 Istio 服务网格时的一些经验,以及在使用 Istio 过程中可能遇到的一些常见问题的解决方法。 什么是『无头服务』? 『无头服务』即 Kubernetes 中的 Headless Service。Service 是 Kubernetes 对后端一组提供
腾讯云原生
2020/09/21
3.6K0
Istio 运维实战系列(3):让人头大的『无头服务』-下
本系列文章将介绍用户从 Spring Cloud,Dubbo 等传统微服务框架迁移到 Istio 服务网格时的一些经验,以及在使用 Istio 过程中可能遇到的一些常见问题的解决方法。
赵化冰
2022/08/01
5700
Istio 运维实战系列(3):让人头大的『无头服务』-下
Istio 运维实战系列(3):让人头大的『无头服务』-下
作者赵化冰,腾讯云高级工程师,Istio contributor,ServiceMesher管理委员,热衷于开源、网络和云计算。目前主要从事服务网格的开源和研发工作。 本系列文章将介绍用户从 Spring Cloud,Dubbo 等传统微服务框架迁移到 Istio 服务网格时的一些经验,以及在使用 Istio 过程中可能遇到的一些常见问题的解决方法。 失败的 Eureka 心跳通知 在上一篇文章中,我们介绍了 Headless Service 和普通 Service 的区别。由于 Headless Ser
腾讯云原生
2020/09/29
1.5K0
Istio中的流量配置
Istio的数据面会在pod中注入两个容器:istio-init和istio-proxy。
charlieroro
2020/09/22
2.4K0
Istio中的流量配置
Istio的运维-诊断工具(istio 系列五)
在参考官方文档的时候发现环境偶尔会出现问题,因此插入一章与调试有关的内容,便于简单问题的定位。涵盖官方文档的诊断工具章节
charlieroro
2020/06/11
2.9K0
一文带你彻底厘清 Isito 中的证书工作机制
作者赵化冰,腾讯云高级工程师,Istio Member,ServiceMesher管理委员,热衷于开源、网络和云计算。目前主要从事服务网格的开源和研发工作。 目录 Istio 安全架构 控制面证书签发流程 为什么要通过 Pilot-agent 中转? 控制面身份认证 SDS 工作原理 网格边车证书配置 Gateway 证书配置 数据面使用的所有证书 小结 参考文档 在这篇文章中,我们将探讨 Istio 是如何使用证书来实现网格中服务的身份认证和安全通信的。 本文是对 Istio 认证工作机制的深度分
腾讯云原生
2020/08/17
2.2K0
Istio Envoy 配置解读
前面我们创建了一个 Gateway 和 VirtualService 对象,用来对外暴露应用,然后我们就可以通过 ingressgateway 来访问 Bookinfo 应用了。那么这两个资源对象是如何实现的呢?
我是阳明
2023/11/06
7800
Istio Envoy 配置解读
istio请求路由分析
•httpbin访问sleep:80端口•iptables拦截转发到15001 的15001端口
有点技术
2020/07/22
1.6K0
Istio流量管理实现机制深度解析-基于1.4.0更新
基于1.4.0更新,和最新版本1.6.0的架构基本相同,具有较大参考价值。 目录 前言 Pilot高层架构 统一的服务模型 标准数据面 API 业务DSL语言 Istio流量管理相关组件 控制面组件 数据面组件 命令行工具 数据面标准API 基本概念和术语 XDS服务接口 XDS服务接口的最终一致性考虑 ADS聚合发现服务 Bookinfo 示例程序分析 Bookinfo程序结构 xDS接口调试方法 Envoy启动过程分析 Envoy配置分析 Bookinfo端到端调用分析 小结 参考资料 前言 Is
腾讯云原生
2020/09/10
1.3K1
Istio Ambient 模式流量管理实现机制详解(三)
本文将继续介绍 ambient 模式下四层流量处理的实现机制。本文将以 bookinfo 应用中 productpage 访问 reviews 的请求路径为例来分析一个请求从 client 端发出到 server 端处理的四层流量处理流程。
赵化冰
2023/02/25
3410
Istio Ambient 模式流量管理实现机制详解(三)
Aeraki 教程系列:如何设置路由规则?
Aeraki 可以帮助你在服务网格中管理任何七层协议。目前 Aeraki 已经支持了 Dubbo、Thrit、Redis 等开源协议。你还可以使用 Aeraki 的 MetaProtocol 协议扩展框架来管理私有协议的七层流量。
赵化冰
2022/08/01
5920
Istio服务网格细节剖析
因为nginx是代理层,可以转发请求,istio也实现了流量转发的效果,肯定也有代理层,并且识别了前面创建的虚拟服务中定义的规则。
王先森sec
2023/04/24
8250
Istio服务网格细节剖析
Istio Ambient 模式流量管理实现机制详解(三)
本文将继续介绍 ambient 模式下四层流量处理的实现机制。本文将以 bookinfo 应用中 productpage 访问 reviews 的请求路径为例进行分析,以理清一个请求从 client 端发出到 server 端处理的完整流程。
赵化冰
2022/10/31
5020
Istio: 服务网格领域的新王者
Istio 是当前 Service Mesh 领域最完善的解决方案,同源自 kubernetes 项目团队。
钟华
2019/02/12
4.4K0
给你的istio sidecar写一个自定义扩展程序
我们知道 istio 支持 lua 和 wasm 两种扩展能力,lua 作为脚本语言,相信写过游戏或 nginx 插件的都了解他,这里以 Lua 为例子,介绍下 istio 的 sidecar 如何编写一个插件。
机械视角
2022/06/05
6990
给你的istio sidecar写一个自定义扩展程序
Istio流量管理之请求路由分析
前面我们了解了 Gateway 和 VirtualService 资源对象的作用,以及它们是如何影响 Envoy 的配置的,那么这些资源对象又是如何影响流量的呢?通过 Istio 如何实现流量管理的呢?
我是阳明
2023/11/09
4690
Istio流量管理之请求路由分析
打造企业级自动化运维平台系列(十六):服务网格 Istio 详解
官方解释:An open platform to connect, secure, control and observe services.
民工哥
2024/01/18
5470
打造企业级自动化运维平台系列(十六):服务网格 Istio 详解
大规模场景下对Istio的性能优化
当前istio下发xDS使用的是全量下发策略,也就是网格里的所有sidecar(envoy),内存里都会有整个网格内所有的服务发现数据。这样的结果是,每个sidecar内存都会随着网格规模增长而增长。
tunsuy
2023/09/09
4130
istio 数据面调试指南
这是使用 istio 最常见的困境:在微服务中引入 envoy 作为代理后,当流量访问和预期行为不符时,用户很难快速确定问题是出在哪个环节。客户端收到的异常响应,诸如 403、404、503 或者连接中断等,可能是链路中任一 sidecar 执行流量管控的结果, 但也有可能是来自某个服务的合理逻辑响应。
钟华
2020/02/12
2.7K0
Aeraki 教程系列:如何设置全局限流规则?
Aeraki 可以帮助你在服务网格中管理任何七层协议。目前 Aeraki 已经支持了 Dubbo、Thrit、Redis 等开源协议。你还可以使用 Aeraki 的 MetaProtocol 协议扩展框架来管理私有协议的七层流量。
赵化冰
2022/08/01
4130
Aeraki 教程系列:如何设置全局限流规则?
推荐阅读
相关推荐
Istio 运维实战系列(2):让人头大的『无头服务』-上
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验