- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Kubernetes Service 502,IPVS 的坑由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
目前部署在 Kubernetes 中的服务,通过 Calico BGP 将 Service 与集群外网络打通,并在外部的 nginx 中配置 Service 地址对外进行服务暴露。经过一段时间的观察,发现在 Deployment 滚动更新中及之后一段时间,偶现服务访问 502 的问题.
当前 Kuberntes 集群使用 Calico 作为 CNI 组件,并使用 BGP 模式将 Pod IP 和 Service IP 与集群外网络打通,通过集群外的 Nginx 作反向代理对外提供服务,应用都是以 Deployment 形式部署。通过一段时间的观察,部分应用反馈,在应用发布后一段时间内,服务有一定几率出现 502 报错.
最直接的猜测,是否问题只发生在滚动更新过程中,即应用没有做好检查检测的配置,导致服务没有真正可用,Pod 却已经处于 ready 状态.
简单的测试后很快排除这个可能,对配置了有效健康检查探针的 Deployment 进行滚动更新,并使用 ab 通过 Nginx 配置的域名进行持续请求(此时无并发),发现在应用滚动更新结束后,并通过 pod IP 人工确认了服务没有问题,仍有概率出现 502 错误,且出现错误的现象会持续几分钟甚至十几分钟的时间,显然远远超过了滚动更新所需时间.
上面的初步测试的现象,排除了应用本身的问题。下一个怀疑的目标指向了 Nginx。既然现象是通过 Nginx 代理访问产生的,那么直接请求 Service 有没有问题呢,由于当前集群 Service 地址和外部网络做了打通,测试起来很方便,我准备了如下的测试:
经过测试,案例 1 出现了 502 错误,案例 2 未出现。所以,问题是在 Nginx 嘛?
找到负责 Nginx 的同事进行分析,结论是 Nginx 似乎不会造成类似的问题。那为什么上面测试中只有案例1 复现了问题呢?于是我决定重新进行测试,这次在 ab 请求的时候加上了并发(-c 10),结果,两个案例都出现了 502 的错误。这样,问题似乎又回到了 Kubernetes 集群本身,而且似乎在请求量较大的情况下才会出现.
这时,我开始怀疑是否可能是因为某种原因,滚动发布后的一段时间里,一些请求会错误的被分发到已经被杀掉的老得 podIP 上。为了验证这一猜测,我进行了如下实验:
第三步 patch pod 的 label,是为了保留原来的 pod 实例,以便观察请求是否会分发到老的 Pod。(patch Pod 的 label 不会使 Pod 重启或退出,但是改变了 label,会使 Pod 脱离原 Deployment 的控制,因此触发 Deployment 新建一个 Pod).
结果和预期一致,当新的 Pod 已经 ready,Endpoint 已经出现了新的 Pod 的 IP,请求仍然会进到原来的 Pod 中.
基于以上的结果,又通过多次实验,观察 Kubernetes 节点上的 IPVS 规则,发现在滚动更新及之后一段时间,老的 podIP 还会出现在 IPVS 规则中,不过 weight 为 0,手动删除后 weight 为 0 的 rs 后,问题就不再出现。到此,找到问题所在是 IPVS,但是为什么会这样呢,在搜索了相关的文章后,大概找到了原因.
诡异的 No route to host,讲到了 IPVS 的一个特性:
即:五元组(源IP地址、目的IP地址、协议号、源端口、目的端口)一致的情况下,IPVS 有可能不经过权重判断,直接将新的连接当成存量连接,转发到原来的 real server(即 PodIP)上。理论上这种情况在单一客户端大量请求的场景下,才有可能触发,这也是诡异的 No route to host一文中模拟出的场景,即:
不同请求的源 IP 始终是相同的,关键点在于源端口是否可能相同。由于 ServiceA 向 ServiceB 发起大量短连接,ServiceA 所在节点就会有大量 TIME_WAIT 状态的连接,需要等 2 分钟(2*MSL)才会清理,而由于连接量太大,每次发起的连接都会占用一个源端口,当源端口不够用了,就会重用 TIME_WAIT 状态连接的源端口,这个时候当报文进入 IPVS 模块,检测到它的五元组跟本地连接转发表中的某个连接一致(TIME_WAIT 状态),就以为它是一个存量连接,然后直接将报文转发给这个连接之前对应的 rs 上,然而这个 rs 对应的 Pod 早已销毁,所以抓包看到的现象是将 SYN 发给了旧 Pod,并且无法收到 ACK,伴随着返回 ICMP 告知这个 IP 不可达,也被应用解释为 “No route to host”.
这里分析一下之前的测试中为何会出现两种不同的结果。我一共进行了两次对比实验.
第一次,未加并发,通过 Nginx 和 通过 Service IP 进行访问并对比。这组实验中,通过 Nginx 访问复现了问题,而通过 Service IP 没有,这个结果也险些将排查引入歧途。而现在分析一下,原因是因为目前的 Kubernetes 服务访问入口的设计,是集群外 Nginx 为整个 Kubernetes 集群共用,所以 Nginx 的访问量很高,这也导致 Nginx 向后端的 upstream(即 Service IP)发起连接时,理论上源端口重用的概率较高(事实上经过抓包观察,确实几分钟内就会观察到多次端口重用的现象),因而更容易出现五元组重复的情况.
第二次,同样的对比,这次加了并发,两边的案例都复现了问题。这样,和上面文章中的场景类似,由于加了并发,发布 ab 请求的机器,也出现了源端口不足而重用的情况,因此也复现了问题.
而正式环境出现的问题反馈,和我第一次实验通过 Nginx 访问得到复现,是同一个原因,虽然单个应用的请求量远没有达到能够触发五元组重复的量级,但是集群中的所有应用请求量加起来,就会触发此问题.
几种解决方案,上面引用的文章中也都提到,另外可参考isuue 81775,对这一问题及相关的解决方式有很多的讨论.
鉴于目前我们的技术能力和集群规模,暂时无法也无需进行 linux 内核级别的功能修改和验证,并且调研了业务应用,绝大部分以短连接为主,我们采用了一种简单直接的方式,在一定程度上避免该问题。开发一个自定义的进程,并以 Daemonset 的方式部署在每个 Kubernetes 的每个节点上。该进程通过 informer 机制监听集群 Endpoint 的变化,一旦监听到事件,便获取 Endpoint 及其对应 Service 的信息,并由此找到其在本节点上对应产生的 IPVS 规则,如果发现在 Virtual Service 下有 weight 为 0 的 Real Service,则立即删除此 Real Service。但是这一解决方式,不可避免的牺牲了部分优雅退出的特性。但是在综合了业务应用的特点权衡之后,这确实是目前可接受的一种解决方式(虽然极其不优雅).
是否应该如此使用 Service?
总结问题的原因,在我们单一业务的请求量远未达到会触发五元组重复这种小概率事件的瓶颈时,过早的遭遇这一问题,和我们对 Kubernetes 服务的入口网关的设计有很大关系,打通 Service 和虚拟机的网络,使用外部 Nginx 作为入口网关,这种用法,在 Kubernetes 的实践中应该算是非常特殊(甚至可称为奇葩),但是这一设计,也是由于目前业务的实例,存在大量虚拟机和容器混布的场景。教训是,在推广和建设 Kubernetes 这种复杂系统时,尽量紧靠社区及大厂公开的生产最佳实践,减少仅凭经验的或机械延用的方式进行架构设计,否则很容易踩坑,事倍功半.
原文地址:http://dockone.io/article/2434505 。
最后此篇关于Kubernetes Service 502,IPVS 的坑的文章就讲到这里了,如果你想了解更多关于Kubernetes Service 502,IPVS 的坑的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
前言 每日站会(Daily Standup)是团队统一节奏的、在固定时间发生的、帮助团队内部快速同步进展的敏捷实践活动: 站会的目的是让团队能更好地对齐 Sprint 目标;
jdbcTemplate 中的queryForList,你真的懂吗? 你想象中的queryForList是不是应该长成下面这种模样? String sql = "select *
python是一门清晰简洁的语言,如果你对一些细节不了解的话,就会掉入到那些深不见底的“坑”里,下面,我就来总结一些python里常见的坑。 列表创建和引用 嵌套列表的创建 使用*号来创建一个
如今,在DevOps当中建立安全体系显得比以往任何时候都更加重要。《2021年企业DevOps技能提升报告》指出,56%的受访者表示DevSecOps已经成为自动化工具中的一大必备要素。然而,D
前言 相信看到这个题目,可能大家都觉得是一个老生常谈的月经topic了。一直以来其实把握一个“值传递”基本上就能理解各种情况了,不过最近遇到了更深一点的“小坑”,与大家分享一下。 首先还是从最简
前言 Go 中的for range组合可以和方便的实现对一个数组或切片进行遍历,但是在某些情况下使用for range时很可能就会被"坑",下面用一段代码来模拟下:
大家好,我是明哥。 在开始之前,先考你一个非常 Go 味的经典问题:如何判断一个 interface{} 的值是否为 nil ? 这也是面试有可能会被问到的一个问题,这个问题很 “迷”,平时
ava并发包有很大一部分内容都是关于并发容器的,因此学习和搞懂这部分的内容很有必要。 Java 1.5 之前提供的同步容器虽然也能保证线程安全,但是性能很差,而 Java 1.5 版本之后提供的并发
大家好,我是煎鱼。 前几天在读者交流群里看到一位小伙伴,针对 interface 的使用有了比较大的疑惑。 无独有偶,我也在网上看到有小伙伴在 Go 面试的时候被问到了:
我是一名优秀的程序员,十分优秀!