支持 Hugging Face 生产基础设施的三大警报
Hugging Face 的基础设施团队很高兴能与大家分享 Hugging Face 生产基础设施的幕后工作,我们有幸参与了其构建和维护。我们的团队致力于设计和实施一个强大的监控和警报系统,这对确保我们平台的稳定性和可扩展性至关重要。我们时刻铭记,警报对于我们识别和响应潜在问题,防止其演变成重大事件的能力有着巨大的影响。
在这篇博文中,我们将深入探讨在支持我们的生产基础设施方面发挥独特作用的三个强大警报,并探讨它们如何帮助我们维持社区所依赖的高水平性能和正常运行时间。
高 NAT 网关吞吐量
在云计算架构中,数据在私有网络和公共网络之间流动,实施 NAT(网络地址转换)网关通常是一项坚定的最佳实践。这个网关充当一个战略性的守门员,监控并促进所有流向公共互联网的出站流量。通过集中管理出口流量,NAT 网关为全面的可见性提供了一个战略制高点。我们的团队可以轻松查询和分析这些流量,使其成为处理安全、成本优化或各种其他调查场景时的宝贵资产。
成本优化是云基础设施管理的一个关键方面,而理解定价动态是关键。在数据中心,定价结构通常区分东西向流量(通常是同一机架或建筑物内的通信)和南北向流量(较远的私有网络之间或与互联网的通信)。通过监控网络流量,Hugging Face 可以获得有关这些流量模式的宝贵洞察。这种认知使我们能够就基础设施配置和架构做出明智的决策,确保我们限制不必要的成本产生。
我们的一个关键警报旨在当我们的网络流量超过预定义阈值时通知我们。这个警报有多种用途。首先,它作为一个预警系统,提醒我们任何可能指示潜在问题或意外行为的异常流量激增。其次,它促使我们定期审查我们的流量趋势,确保我们跟上基础设施的增长和不断变化的需求。这个警报设置了一个静态阈值,我们随着时间的推移对其进行了微调,以确保其保持相关和有效。当它被触发时,通常与我们重构基础设施的时期相吻合。
例如,在集成第三方安全和自动扩展工具时,我们观察到从我们的节点流出的遥测数据增加,这触发了警报并促使我们优化配置。
在另一次调整基础设施时,我们错误地避开了一条连接特定产品基础设施(例如,从 Space 到 Hub 用于与仓库数据交互的流量)的私有、低成本路径。更详细地说,我们发现对成本节约影响最大的工作负载是那些访问对象存储的工作负载。直接获取对象比通过 CDN 托管的资产为我们的 LFS 仓库存储获取更便宜,并且与到达我们前端的公共请求相比,它不需要我们 WAF 提供的同样的安全措施。利用 DNS 覆盖来切换私有网络路径和公共网络路径之间的流量已成为我们的一项宝贵技术,这得益于 CDKTF AWS 提供商。
new Route53ResolverFirewallRule(
stack,
`dns-override-rule-${key}-${j}`,
{
provider: group?.provider!,
name: `dns-override-${dnsOverride.name}-${rule[0]}`,
action: 'BLOCK',
blockOverrideDnsType: 'CNAME',
blockOverrideDomain: `${rule[1]}.`,
blockOverrideTtl: dnsOverride.ttl,
blockResponse: 'OVERRIDE',
firewallDomainListId: list.id,
firewallRuleGroupId: group!.id,
priority: 100 + j,
},
);
最后需要说明的是,虽然我们有“配置即代码”来确保期望的状态始终有效,但在此基础上增加一个警报层有助于在通过代码表达期望状态时出错的情况下提供保障。
Hub 请求日志归档成功率
Hugging Face 的日志基础设施是一个复杂的系统,旨在收集、处理和存储由我们的应用程序和服务生成的大量日志数据。该系统的核心是 Hub 应用程序日志管道,这是一个架构精良的解决方案,确保 Hub 模型使用数据被高效地捕获、丰富和存储,以用于报告和归档。该管道始于 Filebeat,这是一个轻量级的日志收集器,在每个 Kubernetes 集群中作为守护进程集与我们的应用程序 Pod 一起运行。Filebeat 的作用是从各种来源(包括应用程序容器)收集日志,并将它们转发到管道的下一阶段。
一旦日志被 Filebeat 收集,它们就会被发送到 Logstash,一个强大的日志处理工具。Logstash 充当数据处理的主力,对传入的日志应用一系列的修改和转换。这包括用 GeoIP 数据丰富日志以获得地理位置洞察,根据预定义的标准将日志路由到特定的 Elasticsearch 索引,以及通过添加、删除或重新格式化日志字段来操纵它们,以确保一致性和分析的便利性。在 Logstash 处理完日志后,它们会被转发到一个 Elasticsearch 集群。
Elasticsearch 是一个分布式搜索和分析引擎,构成了我们日志存储和分析平台的核心。它从 Logstash 接收日志,并通过 Elasticsearch 管道应用自己的一套处理规则。这些管道执行最少的处理任务,例如添加时间戳字段以指示处理时间,这对于日志分析和关联至关重要。Elasticsearch 提供了一个可扩展且灵活的存储解决方案,允许我们为运营使用和实时分析缓冲日志。
为了管理 Elasticsearch 内的日志生命周期,我们采用了一个强大的存储和生命周期管理策略。这确保日志在 Elasticsearch 中保留一段定义的时间,为运营和故障排除提供快速访问。在这个保留期之后,日志被卸载到长期归档存储。归档过程涉及一个自动化工具,该工具从 Elasticsearch 索引中读取日志,将它们格式化为 Parquet 文件(一种高效的列式存储格式),并将它们写入我们的对象存储系统。
我们日志管道的最后阶段利用了 AWS 数据仓库服务。在这里,我们使用 AWS Glue 爬虫来发现和分类我们对象存储中的数据,自动生成一个 Glue 数据目录,它提供了一个统一的元数据存储库。Glue 表的模式会定期刷新,以确保它与我们日志数据不断变化的结构保持同步。与 AWS Glue 的这种集成使我们能够使用 Amazon Athena(一个无服务器的交互式查询服务)来查询归档的日志。Athena 允许我们直接对对象存储中的数据运行 SQL 查询,为日志分析和历史数据探索提供了一个成本效益高且可扩展的解决方案。
日志管道虽然设计得非常精细,但并非没有挑战和潜在的故障点。其中最关键的漏洞之一在于系统的弹性,特别是在 Elasticsearch 集群中。作为一个分布式系统,Elasticsearch 在各种情况下都可能遇到背压,例如高入口流量、密集的查询或像分片重定位这样的内部操作。当发生背压时,它可能导致整个管道出现一系列问题。例如,如果 Elasticsearch 集群不堪重负,它可能会开始拒绝或延迟日志摄入,导致 Logstash 甚至 Filebeat 出现积压,这可能导致日志丢失或处理延迟。
另一个脆弱点是 Elasticsearch 中的自动模式检测机制。虽然它旨在适应变化的日志结构,但当应用程序日志的字段类型发生重大变化时,它可能会失败。如果模式检测未能识别新的字段类型,可能会导致从 Logstash 到 Elasticsearch 的写入失败,从而引起日志处理瓶颈和潜在的数据不一致。这个问题凸显了主动日志模式管理以及需要强大的监控来及时检测和解决此类问题的重要性。
内存管理也是管道稳定性的一个关键方面。日志处理层,包括 Logstash 和 Filebeat,为了控制成本,在有限的内存资源下运行。当发生背压时,这些组件可能会遇到内存不足(OOM)问题,尤其是在系统减速期间。随着日志积压和背压增加,这些进程的内存占用会增长,使它们更接近其极限。如果不能及时处理,这可能导致进程崩溃或进一步加剧背压问题。
负责将日志从 Elasticsearch 传输到对象存储的归档作业也遇到了挑战。有时,这些作业可能资源密集,其性能对节点大小和内存可用性变得敏感。在垃圾数据或异常大的日志条目通过管道的情况下,它们可能会给归档过程带来压力,导致因内存耗尽或节点容量限制而失败。这强调了在管道早期进行数据验证和过滤的重要性,以防止此类问题到达归档阶段。
为了减轻这些潜在的故障,我们实施了一个强大的警报系统,其动机独特:验证端到端的日志流。该警报旨在 **比较我们的应用程序负载均衡器(ALB)收到的请求数量与成功归档的日志数量**,从而全面了解整个管道中的日志数据流。这种方法使我们能够快速识别任何可能指示潜在日志丢失或处理问题的差异。
警报机制基于一个简单而有效的比较:我们 ALB 收到的请求数(代表进入系统的总日志量)与我们长期存储中成功归档的日志数。通过监控这个比率,我们可以确保输入和输出一致,从而为我们的日志基础设施的健康状况提供一个有力的验证。当警报被触发时,它表示可能存在不匹配,促使立即进行调查和修复。
在实践中,这个警报被证明是一个有价值的工具,尤其是在基础设施重构期间。例如,当我们将 ALB 迁移到一个 VPC 源时,该警报在识别和解决由此产生的日志流差异方面起到了关键作用。然而,它也在一些不太明显的情况下拯救了我们。例如,当归档作业因不可预见的问题未能运行时,该警报标记了缺失的归档日志,使我们能够迅速调查和解决问题,以免影响我们的日志分析和保留过程。
虽然这个警报是一个强大的工具,但它只是我们全面监控策略的一部分。我们不断完善和调整我们的日志基础设施,以处理不断增长的日志数据量和复杂性。通过结合主动监控、高效的资源管理以及对我们系统行为的深刻理解,Hugging Face 确保我们的日志管道保持弹性、可靠,并能够支持我们平台的增长和不断变化的需求。这个警报证明了我们致力于维护一个强大而透明的日志系统,为我们的团队提供他们所需洞察,以保持 Hugging Face 的平稳运行。
Kubernetes API 请求错误和速率限制
在运营云原生应用和基于 Kubernetes 的基础设施时,即使是看似微小的问题,如果不加检查,也可能升级为严重的停机。对于 Kubernetes API 尤其如此,它作为 Kubernetes 集群的中枢神经系统,协调容器的创建、管理和网络。在 Hugging Face,我们通过经验认识到,监控 Kubernetes API 错误率和速率限制指标是一项至关重要的实践,可以防止潜在的灾难。
Hugging Face 的基础设施与 Kubernetes 深度集成,而 kube-rs
库在高效构建和管理这个生态系统中起到了关键作用。kube-rs
提供了一种以 Rust 为中心的 Kubernetes 应用开发方法,为开发者提供了一个熟悉而强大的工具包。其核心是,kube-rs
引入了三个关键概念:反射器 (reflector)、控制器 (controller) 和自定义资源接口 (custom resource interface)。反射器确保 Kubernetes 资源的实时同步,使应用程序能够对变化做出迅速反应。控制器,作为决策者,持续协调资源的期望状态和实际状态,使 Kubernetes 具有自愈能力。自定义资源接口扩展了 Kubernetes,允许开发者定义特定于应用的资源,以实现更好的抽象。
此外,kube-rs
还引入了监视器 (watcher) 和终结器 (finalizer)。监视器监控特定资源的变化,以响应事件触发动作。另一方面,终结器通过定义自定义逻辑来确保资源的正确清理和终止。通过为这些 Kubernetes 概念提供基于 Rust 的抽象,kube-rs
使开发者能够构建健壮、高效的应用程序,利用 Kubernetes 平台的能力和灵活性,同时保持以 Rust 为中心的开发方法。这种集成简化了构建和管理复杂 Kubernetes 应用程序的过程,使其成为 Hugging Face 基础设施中的一个宝贵工具。
Hugging Face 与 Kubernetes 的集成是我们基础设施的基石,而 kube-rs
库在管理这个生态系统中扮演着关键角色。kube::api::
模块在自动化各种任务中起着重要作用,例如为支持我们 Spaces 产品的自定义域名管理 HTTPS 证书。通过编程方式处理证书生命周期,我们确保了服务的安全性和可访问性,为用户提供了无缝的体验。此外,我们在日常维护中也在面向用户的特性之外使用了这个模块,以促进节点排空和终止,从而在基础设施更新期间保证集群的稳定性。
kube::runtime::
模块对我们同样至关重要,它使我们能够开发和部署自定义控制器,从而增强我们基础设施的自动化和弹性。例如,我们在我们的托管服务中为账单管理实现了控制器,其中客户 Pod 上的监视器和终结器确保了准确的资源跟踪和计费。这种级别的定制化使我们能够根据我们的具体需求调整 Kubernetes。
通过 kube-rs
,Hugging Face 在我们的云原生应用上实现了高水平的效率、可靠性和控制。该库以 Rust 为中心的设计与我们的工程理念相符,使我们能够利用 Rust 在管理 Kubernetes 资源方面的优势。通过自动化关键任务和构建自定义控制器,我们创建了一个可扩展、自愈的基础设施,以满足我们用户和企业客户多样化且不断变化的需求。这种集成展示了我们致力于充分利用 Kubernetes 的潜力,同时保持一种为我们独特需求量身定制的开发方法。
虽然我们的基础设施很少遇到与 Kubernetes API 相关的问题,但我们仍然保持警惕,尤其是在部署期间和部署之后。Kubernetes API 在我们使用 kube::runtime::
管理客户 Pod 和云网络资源方面是一个关键组件。API 通信中的任何中断或低效都可能对我们的服务产生连锁效应,可能导致停机或性能下降。
监控这些 API 指标的重要性,可以从其他 Kubernetes 用户的经验中得到印证。例如,OpenAI 分享了一份 状态更新,详细说明了 DNS 可用性问题如何导致了重大停机。虽然这与 Kubernetes API 不直接相关,但他们的经历凸显了各种基础设施组件之间的相互关联性以及连锁故障的可能性。正如 DNS 可用性对应用程序的可访问性至关重要一样,一个健康且响应迅速的 Kubernetes API 对于管理和协调我们的容器化工作负载也至关重要。
作为一项最佳实践,我们已将这些 API 指标整合到我们的监控和警报系统中,确保任何异常或趋势都能及时引起我们的注意。这使我们能够采取主动的方法,在问题影响客户之前进行调查和处理。例如,有一次一个集群开始对 Kubernetes API 的请求进行速率限制。我们追溯到这是由于我们的一个第三方工具遇到了一个 bug,它反复请求排空一个已经排空了的节点。作为回应,我们能够在系统出现任何明显的用户影响之前,将这个故障作业从系统中清除。这是一个很好的例子,说明警报场景不仅仅是在部署我们自定义控制器的新版本后立即发生——bug 可能需要一些时间才能表现为生产问题。
总之,虽然我们的基础设施是稳健且架构良好的,但我们认识到,警惕和主动监控对于维持其健康和稳定至关重要。通过密切关注 Kubernetes API 的错误率和速率限制指标,我们保护我们的托管服务,确保顺畅的客户体验,并坚守我们对可靠性和性能的承诺。这证明了我们的信念:在云原生技术的世界里,每个组件,无论多小,都在我们平台的整体弹性和成功中扮演着重要角色。
附加警报:新集群发送零指标
感谢您阅读到这里,这是一个附加警报!
在 Hugging Face,我们的实验不断变化,随着我们迭代功能和产品,通常会有特定用途的集群启动和关闭。为了增加这种混乱度,我们的增长也是一个重要因素,集群会扩展到其极限,然后触发类似减数分裂的拆分以保持平衡。为了在这种动态环境中导航,而无需硬编码或引入额外的集群发现层,我们设计了一个巧妙的警报来适应这些变化。
(
(sum by (cluster) (rate(container_network_transmit_packets_total{pod="prometheus"}[1h] ) ) > 0)
or
(-1 * (sum by (cluster) (rate(container_network_transmit_packets_total{pod="prometheus"}[1h] offset 48h) ) > 0))
) < 0
此查询中使用的指标是 **`container_network_transmit_packets_total`**,它表示容器传输的总数据包数。该查询正在过滤来自集群本地 Prometheus 实例的指标,该实例负责指标收集以及向我们的中央指标存储——Grafana Mimir 进行远程写入。数据包的传输近似于健康的远程写入,这是我们希望在所有活动集群中确保的。
查询的第一部分执行当前速率检查。查询的第二部分通过使用与当前速率检查相同的计算加上一个 **`offset 48h`** 子句来执行历史速率检查。**`-1 *`** 乘法用于反转结果,因此如果历史速率大于 0,结果将小于 0。
**`or`** 运算符将查询的两个部分结合起来。如果满足以下任一条件,查询将返回 **`true`**:
- 集群的当前数据包传输速率大于 0。
- 集群的历史数据包传输速率(48 小时前)大于 0,但当前速率不是。
外部的 **`< 0`** 条件检查 **`or`** 操作的结果是否小于 0。这意味着查询只有在两个条件都不满足时才会触发,即当一个集群从未发送任何指标时(当前和历史速率都为 0)。
在两种情况下查询会触发:
- 没有指标的新集群:添加了一个新集群,但它还没有发送任何指标。在这种情况下,当前和历史速率都将为 0,查询将触发。
- 从未发送过指标的集群:一个集群已经存在超过 48 小时,但它从未发送过任何指标。在这种情况下,历史速率将为 0,当前速率也将为 0,从而触发查询。
在这两种情况下,查询都会检测到集群没有发送任何指标并触发警报。
这个简单而有效的解决方案在我们指标基础设施崩溃、集群设置期间以及它们被拆除时都会触发,为我们提供了对基础设施健康的及时洞察。虽然它可能不是我们武器库中最关键的警报,但它有一个特殊的位置,因为它诞生于协作。这是通过 Hugging Face 基础设施团队同事的专业知识和乐于助人精神,经过严格的代码审查,实现团队合作力量的证明 🤗
总结
在这篇文章中,我们分享了一些我们最喜欢的、支持 Hugging Face 基础设施的警报。我们也想听听你们团队最喜欢的警报!
您是如何监控您的 ML 基础设施的?哪些警报让您的团队不断回来进行修复?您的基础设施中经常出问题的是什么?或者反过来,您从未监控过但一直运行良好的又是什么?
请在下面的评论中与我们分享!