Kubernetes-利用-OkHttp-客户端实现网络负载均衡 (kubernetes)
背景
在一次内部服务审计中,我们发现一些请求没有在 Kubernetes (K8s) 网络上正确地实现负载均衡。这导致我们深入研究的问题包括 HTTP 5xx 错误率急剧上升、高 CPU 使用率、废品回收事件数量增加以及超时,但这些问题仅发生在一些特定的 Pod 中。问题
这种情况并不在所有情况下都可见,因为它影响到多 Pod 服务,源 Pod 和目标 Pod 的数量各不相同。在我们的部署中,请求在 Pod 之间是如何均衡的?两个源 Pod 向六个目标 Pod 发送请求。可以清楚地看到,请求分布在目标 Pod 之间存在不均衡。根本原因
K8s 负载均衡器(IPVS 代理模式)的默认负载均衡调度程序设置为轮询(roundrobin)。IPVS 提供了更多选项来均衡流量到 Pod 后端。在测试这些选项时,我们发现无论配置如何,当涉及到我们的服务时,行为都相同,这些服务之间使用内部路由进行通信。 问题在于 K8s 中的 IPVS 根据连接来平衡流量。我们的服务使用 OkHttp 作为相互通信的 HTTP 客户端。OkHttp 的默认行为是创建到服务器的连接,并且在不想在代码中显式关闭连接的情况下,会保持并重新建立到先前合作伙伴的连接。这意味着客户端尝试保持与目标的连接,并通过该特定连接发送请求。通常情况下,它会创建 1:1 的连接,这在 K8s 方面没有均衡。解决方案
如果您需要扩展或希望使您的服务得到适当的负载均衡,您需要在客户端端更新配置。OkHttp 提供了 ConnectionPool 功能。当使用 ConnectionPool 选项时,连接将在有限的时间段内建立,然后重复设置一个新的连接,因此 IPVS 可以进行负载均衡,因为它有大量的新连接,应该根据 IPVS 调度程序路由到目标。效果
使用更新的 HTTP 客户端和默认 IPVS 调度程序在多 Pod 服务之间实现了负载均衡的连接。代码变更
主要代码变更如下: java // Option to set scheduler to allow for more concurrent requests to be sent. // In our case, this ended up building up a bunch of close to death connections that then proceeded to only use a single connection. // Additionally, we wanted to try to prevent opening up new connections too often, as that is much more expensive than making a request. client.connectionPool().evictionPolicy(EvictionPolicy.DEFAULT); client.connectionPool().idleConnectionTimeout(1, TimeUnit.SECONDS);结果
网络和资源的使用现在比以前更加平衡——没有巨大或持续很长时间的峰值,也没有出现只影响部署中某些 Pod 的嘈杂邻居效应。现在几乎所有的 Pod 都以几乎相同的方式被利用,因此我们能够减少我们的部署中的 Pod 数量。结论
定期进行彻底的服务审计是有益的,因为它可以揭示出未来对所有服务有益的优化点,并在解决那些本应该立即运行的功能的奇怪症状时为您节省时间。花些时间查看文档、测试、讨论并了解在使用客户端库时关于连接设置和处理的默认设置的影响,以确保它们将按照您的预期行事。理解Kubernetes的NodePort、LoadBalancer和Ingress
这三个东西都可以实现将集群内的服务暴露到集群外,那么它们到底有什么不同,要如何正确地使用这些组件,希望通过这篇文章,可以给大家一些启示。首先明确一点,NodePort和LoadBalancer指Kubernetes Service组件的两种类型。 在正式开始之前,有必要对Service做简单介绍。Service是一组Pod的抽象,虽然在集群中Pod可以通过IP直达,但是Pod不稳定,它可能会经常死掉,这时集群会重新启动一个Pod,这是一个全新的Pod,它的IP地址会发生变化,这样不利于客户端访问。为了解决这个问题,Kubernetes引入了Service组件,从Pod创建之初到人为删除这段时间,为其创建的Service访问方式都是稳定的,即它的访问IP不变,再配合集群内置的DNS服务,客户端可以利用不变的Service名称或Service IP访问到目标Pods,而且Service还实现了简单的负载均衡功能。 到这里,还有一个很重要的问题没有解决,在Kubernetes集群外如何访问集群内的服务,这就引出了标题中列举的三个对象,下面逐一介绍。 NodePort Nodeport是Service的三种类型之一(ExternalName不常用故除外),其他两种是ClusterIP和LoadBalancer。当Service工作在NodePort类型时,默认每个Node会在全部网络接口开启一个端口来转发对集群内服务的请求。当请求到来时,Node会转发请求到集群中的服务。这样就带来一个问题,请求的目标Node可能会Down掉或其他别的原因导致网络不能访问,NodePort还有一个问题就是对外暴露的端口有限制,默认端口范围是30,000-32,767。这就限制了可以对外暴露服务的个数,使用这些不易记录的端口访问服务也是让人头疼的问题,这就引入了下一个Service类型:LoadBalancer。 LoadBalancer 大多数公有云平台都支持创建这种类型的服务,每个服务可以支持多种协议和多个端口,使用单个IP来访问。因为需要在集群外访问内部服务,所以这个IP地址是公有的,这会产生额外的费用。如果暴露的服务很多,使用时需要慎重。在私有云环境中,不能创建此类的服务,可以创建NodePort类型的服务然后使用HAproxy来充当Load Balancer,这样和公有云平台的LoadBalancer差别不大。Service的三种类型:ClusterIP、NodePort、LoadBalancer,后一种是前一种的增强,NodePort类型Service会创建ClusterIP类型Service,LoadBalancer类型Service会创建NodePort和ClusterIP类型Service。Service组件依赖操作系统中的iptables或ipvs,这是Service的灵魂。有一点需要注意,当使用NodePort类型的服务时,请求会直接转发给实际的Pod而不用转发给Service的Cluster IP(kube-proxy工作在iptables模式),具体实现方法可以在搜索引擎上搜“NodePort类型Service的工作原理”。 Ingress 前面介绍的内容都围绕Service,主要解决网络层的问题,Ingress的出现主要是解决应用层的问题。Ingress实际上充当一个反向代理的角色,和Nginx的功能很类似。Kubernetes中广泛使用的Nginx Ingress其本质就是一个Nginx服务。Ingress依赖LoadBalancer类型的Service,因为它自己没有暴露集群内服务到外部的能力。这里以Nginx Ingress为例,介绍它的工作原理,其他类型的Ingress可以参考其官方文档介绍。
集群部署Ingress资源后,进入集群流量的第一站就是Nginx Ingress中的Nginx服务,由它做HTTP层的负载均衡,还有TLS终结等工作。 总结 文章对NodePort和LoadBalancer类型的Service做了简单介绍,在测试环境中如果需要快速将服务从集群内暴露出来,可以使用NodePort类型的Service,生产环境,如果使用了阿里云或腾讯云可以使用LoadBalancer类型的Service并配合Ingress,如果没有也可以使用NodePort类型的Service并配置Haproxy等来实现负载均衡。
kubernetes 集群中 cilium 的实践及其网络通信解析
Cilium是一个基于 eBPF 和 XDP 的高性能容器网络方案的开源项目,目标是为微服务环境提供网络、负载均衡、安全功能,主要定位是容器平台。
Why Cilium ?
现在应用程序服务的发展已从单体结构转变为微服务架构,微服务间的的通信通常使用轻量级的 http 协议。微服务应用往往是经常性更新变化的,在持续交付体系中为了应对负载的变化通常会横向扩缩容,应用容器实例也会随着应用的更新而被创建或销毁。
这种高频率的更新对微服务间的网络可靠连接带来了挑战:
Cilium 通过利用 BPF 具有能够透明的注入网络安全策略并实施的功能,区别于传统的 IP 地址标识的方式,Cilium 是基于 service / pod /container 标识来实现的,并且可以在应用层实现 L7 Policy 网络过滤。总之,Cilium 通过解藕 IP 地址,不仅可以在高频变化的微服务环境中应用简单的网络安全策略,还能在支持 L3/L4 基础上通过对 http 层进行操作来提供更强大的网络安全隔离。BPF 的使用使 Cilium 甚至可以在大规模环境中以高度可扩展的方式解决这些挑战问题。
Cilium 的主要功能特性 :
此处使用外部的 etcd 的部署方式,外部 etcd 安装 cilium 在较大的运行环境中能够提供更好的性能。
Requirements
安装 helm 3
挂载 BPF 文件系统
在 kubernetes 集群所有 node 上挂载 bpf 文件系统
kubernetes 配置
cilium 安装
当使用外部 etcd 作为 cilium 的 k-v 存储,etcd 的 IP 地址需要在 cilium 的 configmap 中配置。
使用 helm 安装 cilium
安装 cilium 连接测试用例
此用例将会部署一系列的 deployment,它们会使用多种路径来相互访问,连接路径包括带或者不带服务负载均衡和各种网络策略的组合。
部署的 podName 表示连接方式,readiness/liveness 探针则可指示连接是否成功。
安装 hubble
hubble 是一个用于云原生工作负载的完全分布式网络和安全可视化平台。它建立在 Cilium 和 eBPF 的基础上,以完全透明的方式深入了解服务以及网络基础结构的通信和行为。
hubble 对前面安装的测试用例监控信息
cilium 在 kubernetes 集群中安装好后,此处我们来探究一下在不同 node 上 pod 间的 vxlan 通信方式。
cilium 安装完后,cilium agent 会在 node 上创建cilium_net与cilium_host一对 veth pair 及用于跨宿主机通信的cilium_vxlan ,然后在cilium_host上配置其管理的 CIDR IP 作为网关。
如上图示中,通过抓包分析 Container A 与 Container B 之前的通信路径。
Container A ping Container B(以下称 Container A -> CA , Container B -> CB)
进入 Node01 上 CA 内 (10.244.1.154),ping CB ip 地址 10.224.6.11
进入 Node01 上 cilium agent 容器内,查看 CA 容器作为 cilium endpoint 的信息
CA 容器 ping 出的 icmp 包将会经过网卡 lxc8b528e748ff4 (lxcxxA) 路由到cilium_host网关,路由方式与传统的通过 Linux bridge 这样的二层设备转发不一样,cilium 在每个容器相关联的虚拟网卡上都附加了 bpf 程序,通过连接到 TC ( traffic control ) 入口钩子的 bpf 程序将所有网络流量路由到主机端虚拟设备上,由此 cilium 便可以监视和执行有关进出节点的所有流量的策略,例如 pod 内的 networkPolicy 、L7 policy、加密等规则。
CA 容器内的流量要跨宿主机节点路由到 CB 容器,则需要cilium_vxlanVTEP 设备对流量包进行封装转发到 Node02 上。bpf 程序会查询 tunnel 规则并将流量发送给cilium_vxlan ,在 cilium agent 容器内可查看到 bpf 的 tunnel 规则
在 Node01 上对cilium_vxlan抓包,可看到 CA 容器对 icmp 包经过了cilium_vxlan
再对 Node01 上的 eth0 抓包,可看到cilium_vxlan已将 CA 的流量进行 vxlan 封包,src ip 改为本机 node ip 192.168.66.226, dst ip 改为 192.168.66.221
到 Node02 上对 eth0 抓包,可看到 CA 容器的流量包已到 Node02 上
对cilium_vxlan抓包即可看到 CA 容器过来对流量包已被解封
至此,CA 容器对流量已到达 cilium 创建的虚拟网卡。
我们知道 Linux 内核本质上是事件驱动的(the Linux Kernel is fundamentally event-driven), cilium 创建的虚拟网卡接收到流量包将会触发连接到 TC ( traffic control ) ingress 钩子的 bpf 程序,对流量包进行相关策略对处理。
查看 cilium 官方给出的 ingress/egress datapath,可大致验证上述 cilium 的网络通信路径。
首先是egress datapath ,图中橙黄色标签为 cilium component,有 cilium 在宿主机上创建的 bpf 程序(对应着红色标签的 kernel bpf 钩子),若使用 L7 Policy 则还有 cilium 创建的 iptables 规则。流量从某个容器 endpoint 通过容器上的 veth pair 网卡 lxcxxx 出发,即会触发 bpf_sockops.c / bpf_redir.c bpf 程序,若使用了 L7 Policy 则进入用户空间进行 L7 层的数据处理,若没有使能 L7 Policy 则将触发 TC egress 钩子,bpf_lxc 对数据进行处理(若使能 L3 加密,则触发其他 bpf 钩子),数据最终被路由到cilium_hos t 网关处,再根据 overlay 模式(vxlan 等)将数据发送出去。
在 ciliumingress datapath中,数据流量进入主机网络设备上,cilium 可根据相关配置,对数据流量进行预处理(prefilter/L3 加解密/ 负载均衡/ L7 Policy 处理)或直接路由到cilium_host触发相应到 bpf 程序,再到最终的 endpoint 处。
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。