专栏名称: 字节跳动技术团队
字节跳动的技术实践分享
目录
相关文章推荐
高可用架构  ·  这家公司对网关性能的优化历程,在 ... ·  6 小时前  
美团技术团队  ·  北斗计划 | 美团核心本地商业大模型全年招聘 ·  3 天前  
美团技术团队  ·  无需代码!美团 NoCode ... ·  3 天前  
美团技术团队  ·  可信实验白皮书系列05:准实验 ·  3 天前  
51好读  ›  专栏  ›  字节跳动技术团队

Kubernetes 跨集群 Pod 可用性保护

字节跳动技术团队  · 公众号  · 架构  · 2025-02-28 11:00

正文

请到「今天看啥」查看全文



list-watch 低于实际值,这意味着突然大量删除事件会被错误地允许爆发,所以这并非一个安全的选择。但是使用来自webhook的事件计数也不可行,因为它会无限制地偏离实际状态。


相反,我们结合了两个数据源:webhook 存储已批准的 pod 删除的历史记录,aggregator将其视图时间之前的历史记录压缩为最终正确的状态。PodProtector 对象同时包含来自aggregator最后观察到的状态以及其后来自 webhook 的增量历史,后者作为临时缓冲等待aggregator进行权威聚合。当删除突发过大,无法在单个 PodProtector 对象内存储(因为可能包含上万副本Deployment的每个副本的条目)时,最早的Pod删除事件将被压缩到某个时间范围内,避免超大PodProtector对象影响存储集群性能。


示例时间线:


图片


因此,分歧的 webhook 线会定期校准到实际线。考虑之前的示例,其中aggregator每秒报告一秒前的状态,校准后的线与实际线更接近:

图片


除了压缩删除历史之外,aggregator还会将扩缩容、数据面等非删除事件导致的可用性变化同步到基线值。由于 Podseidon 仅旨在防止控制平面导致的不可用,这些状态变化的延迟相对可以接受。使用此双数据源方案,可以平衡正确性和及时性的要求。





Aggregator 快照时间推断

为准确截断准入历史并保留当前快照后的增量删除事件,需从aggregator的pod list-watch事件中获取事件时间戳,然而Kubernetes原生未提供该功能。


最理想的方案本应使webhook与aggregator共用同一时钟,但由于删除请求无法通过mutating webhook修改对象字段,这个想法并不可行。故此,我们需通过其他不可靠渠道推断快照时间戳,包括:


  • clock:使用aggregator系统时间

  • status:使用Pod字段(creationTimestamp、deletionTimestamp、conditions等)


这些方法均存在偏差:


图片

  • 受webhook响应延迟和watch延迟影响,aggregator系统时间会晚于准入webhook录入PodProtector准入历史的时间

  • Pod字段时间数据源不一致,部分早于、部分晚于webhook响应,但无watch延迟问题。尤其当status.conditions推断的快照时间早于webhook准入时间时,可能导致快照无法清除引发自身触发删除事件的准入记录。此外,部署于不同机器的组件间可能存在系统时钟偏差。


在字节跳动的实践中,我们部署的定制版kube-apiserver会在etcd存储的GuaranteedUpdate调用中添加annotation。


其值为当前apiserver系统时间戳 (功能上就是个lastUpdateTimestamp)。这使得准入历史时间戳与推断的快照时间之间的延迟更可预测,在生产环境中验证该方案,余下的竞态问题机率较小可忽略。对于标准Kubernetes,推荐采用aggregator系统时间方案 (clock),至少避免了快照无法清除自身触发事件的问题。


不过理论上,即使忽略时钟偏差,当watch延迟过高时,clock仍可能导致假阴性(错误允许pod删除):


图片


在00:01之后,pod X和pod Y被允许删除,此时PodProtector状态中包含两个准入历史记录条目{X: 00:00}及{Y: 00:01}。在00:02时,aggregator收到pod X 删除的watch事件(延迟了2秒)。通过其informer中的新缓存状态,它观察到00:02时的快照包含pod Y而不包含pod X,因此PodProtector状态中的两个准入历史条目都被清除了。实际上这是预期的行为,因为这符合我们的假设:如果在00:02发生事件而前面没有收到pod Y的删除事件,则意味着pod Y实际上并未被删除;但事实上,该请求仍在处理中,只是时间参照系不一致导致误判。虽然aggregator最终会在00:04更新,正确地排除X和Y影响的pod数,但如果webhook在00:03收到另一个其他pod的删除请求(下称pod Z),则会被错误允许准入,因为系统假定只有pod X被删除而pod Y未被删除,从而导致最后一个不可用配额同时被 pod Y 和 pod Z 重复使用。


在灾难性事件中,大量 pod 在短时间内被删除时,这个问题尤其显著。假设有个控制器在同一个Deployment下,在 10 毫秒内并发100个删除不同pod的请求:由于webhook推送了100次准入历史,临时不可用配额下降了 100;但如果aggregator在观察到首个删除事件后、观察到后续事件之前过快进行对账,其中99个会立即被撤销。如果前面的控制器再对其他pod激增99个删除请求,不可用约束便会突破近双倍了。


为缓解此问题,Podseidon 提供了两个配置项,可全局配置或针对单个PodProtector进行覆盖:


  • maxConcurrentLag限制限制单个PodProtector中准入历史的最大条目数。当该值小于maxUnavailable阈值时,可以确保aggregator单次最多清除maxConcurrentLag个条目。然而,将其设置得过小会导致PodProtector缓冲挤拥,引致更多假阳性(误拒绝)返回值,从而可能损害发布、缩容等操作的效率,尤其是当频繁失败触发控制器退避逻辑的更长间隔时尤其显著。

  • aggregationRateMillis为aggregator添加从接收pod事件到执行聚合的延迟,限制同一PodProtector的聚合频率。如果一个PodProtector最近没有新的 pod 事件,收到第一个事件后会先等待aggregationRateMillis,以容许更多激增性事件进入informer,然后再执行聚合;每次聚合后至少aggregationRateMillis内不会对同一PodProtector进行重聚合。虽然聚合触发有所延迟,但聚合过程使用的是最新的快照,而非事件接收时的快照。这有助于缓解由上述相同对象突然大量删除引起的竞态条件问题(除非我们不幸接收到在第一个事件后正好过了aggregationRateMillis毫秒才开始发生删除激增)。然而,将该值设置得过高可能会导致更多由于控制器响应变慢帶來的误拒绝,例如在正常的滚动更新过程中,当新版本的pod X状态切成可用,可以把旧版本的pod Y下线时:







请到「今天看啥」查看全文