鉴于现在多个机构开始使用K8S和容器,性能问题成为了管理员的关注焦点,尤其是海量用户使用的互联网公司,比如Airbnb。在2019年北美KubeCon和云原生会议上,来自Airbnb的工程师分享了一些K8S性能方面的实践经验。
Airbnb计算基础设施小组的软件工程师stephen陈和服务编排小组的张jian的分享的主题:K8S让我们的P95变的更糟糕了吗。 他们讨论了他们在使用K8S容器编排引擎中遇到的性能问题。
自从2018年以来,airbnb的在线住房系统开始了一项运动:把直接部署在AWS EC2实例上的服务迁移到公司自有的Kubernetes 容器管理平台上,总量大概1000个服务。Airbnb运行Amazon Linux2作为有限实例,使用ubuntu image,容器网络部分则选择Flannel/Calico集成的Canal,使用K8S的NodePort方式来和公司的服务发现机制做交互。这项工程导致了一个结果:Airbnb的开发者会非常迅速地询问服务编排小组:为什么我们的POD如此之慢?
在会议上,工程师分享了他们遇到的性能问题和相应的解决方案。总体的思路清晰如下:在处理复杂的基于K8S的基础设施的性能问题的时候,调优必须要涉及所有的技术栈,包括宿主机,集群,容器,网络,甚至是更深层的应用本身。
”吵闹的邻居“ 问题再现
K8S集群中一部分POD的响应速度要比其他的POD慢一些。首先要排查的可能是邻居pod,一些高负载的POD攫取了大部分的CPU和网络资源。这种异常可能是偶然的,比如一些理应处于测试阶段的服务异常的开放为生产阶段。为了应对这些异常情况,必须要建立好控制机制。Airbnb在早期的时候并没有对服务设定CPU配额限制,CPU配额可以限制服务能从宿主机CPU中获得的资源上限,他们为此付出了代价,此后公司便在Kubernetes平台中针对服务做好了资源配额限制。
Kubernetes并不是第一个遭遇”吵闹的邻居“问题的平台。虚拟化平台首先遭遇了这种问题,当多个虚拟机在服务器中共存时,需要大量CPU资源的应用所在的VM可能贪婪的占用掉server中所有的资源,从而损害其他的VM的性能。
面对这个问题,Kubernetes提供解决的工具。然而这些工具使用起来比较困难,而且可能会导向难以精确排查地细粒度热点。Kubernetes使用Linux内核的CFS Bandwidth Control特性,可以以微秒级别的CPU资源分配给提前定义好的组。这可能会导致资源限流问题:某节点可能会很慢,即使CPU使用率并不高。某应用需要10CPU,如果你分配好了CFS配额为100微秒的CPU时间,可能在前20微秒的资源使用完之后,剩下的80微秒资源会被限流。(此处为BUG,Linux kernel已经提供解决问题的补丁)
Airbnb的张工说道:多租户共存的系统需要多关注一下性能问题上。之前的应用运行在独享的资源池中,现在在Kubernetes平台上,他们需要和其他的应用共享资源。
Kubernetes社区开发了一些工具应对性能问题,包括可以配置的CFS配额阶段。POD请求配额允许内服务需要的资源的时候,有的pull的请求会禁用CPU配额(你认为不会被限额,但是实际上资源会被限制)。
Kubernetes中性能问题的另一个罪魁祸首可能是弹性伸缩。在Airbnb中,一个高负载服务可能包括600-1000个POD。对于集群调度器来说,CPU使用率并不紧张,总体来看50%。但是定位到单独的节点来看,可能某节点上运行了18个相同的服务POD。换句话说,总体来看,集群中的CPU使用率并不高,只有50%,但是对于单独的某节点来看,CPU资源使用已经枯竭。
针对放置镜像,K8S调度器有一套规则,比如workload分发到尽可能多的节点上,或者基于特殊关系把工作负载分发到特定节点。如果image已经下载到某节点,那么特定pod更有可能分发到此节点。比如说,某容量超大的image已经下载到特定节点,那么工作pod会全部部署在此节点上。
Airbnb的Stephen陈说道:在一些出问题的案例中,K8S的调度器是罪魁祸首。他们将来会限制在单个节点上能运行的POD数量。
应用和应用之下的依赖关系也可能导致性能问题。以JAVA为例,某Airbnb开发小组发现某JAVA应用响应时间从30毫秒变成了超过100毫秒,案例发生在95%的服务器上。症状仅仅发生在和数据库交互的时候。在应用迁移到K8S之前,性能良好。问题最终定位到JAVA Virtual Machine(JVM)处理多CPU节点上面。36节点的K8S集群中,POD之上的单个JVM可见36CPU。但是单个节点上,如果有两个POD,每个POD存在一个JVM,那么每个JVM都能识别到36 CPU。性能瓶颈自然接着发生。
Airbnb的张Jian指出:这种症状存在于早期版本的JAVA,不能读取容器。JAVA会基于读取到的CPU来自我调优,在容器化的环境中,这会搞坏JAVA中线程池的处理。
这种BUG已经在JAVA8U191修复,但是经验教训必须要牢记:语言和应用对于底层的系统有很深的依赖关系。另一个经验是:在应用迁移到K8S之前,设置一个基线,比对在K8S平台上的应用和其他平台上应用的性能表现。
来自Airbnb的Setphen陈和张Jian还讨论了其他影响性能的潜在因素:比如来自IPtable的负载均衡问题,还有源自DNS配置错误的问题。
总体来说:Kubernetes只是复杂云原生栈的一个组件,所以用户遇到的性能问题可能各不相同。
遇到的性能问题都是比较典型,可以通过一定的管理策略去规避。
比如应用上云必须配置限额、单个节点运行不超过110个POD、JVM启动参数从resourceRef中获取容器的配额启动等