容器中可预测CPU隔离技术在网飞的应用

吵闹的邻居

我们一生中总会有吵闹的邻居,无论是在咖啡厅还是一墙之隔,总是会打断我们。如何避免被打扰不仅对人们很重要,对容器来说也很重要。在云端,容器是运行在共享空间中,总需要共享宿主机的CPU内存等组件。因为微处理器运行很快,为了减少延迟,计算架构一般都会在计算单元和和主内存之间加缓存。然而,缓存是在CPU之间共享的,也就意味着同一宿主机的容器之间性能隔离是不可能的。如果运行自己容器旁的内核突然从RAM读取大量数据,必然造成自己容器缓存命中率大大降低(因此会引起性能下降)。

Linux能解决问题吗?

传统上操作系统任务调度器负责性能隔离。Linux中,主流方案是CFS(Completely Fair Scheduler),目的是公平地在CPU时间片之间分配进程。

CFS在Linux中被广泛使用因此经历过大规模测试。那为什么还会发生上面说的那种情况呢?事实证明,大部分Netflix大部分使用场景的性能都没有被优化。Titus是Netflix的容器平台,每个月,都会在平台上的上千台设备中运行近百万个容器,为成百的内部应用和客户服务。这些应用既有对延迟很敏感的关键流媒体服务,也有解码和机器学习作业。在这些不同应用之间保证性能隔离无疑对提高内外用户体验至关重要。

解决办法

CFS以毫秒级频率申请CPU资源以最佳利用这些资源。我们能否降低申请频率根据数据申请情况最佳地应用计算资源,最小化干扰呢?

一个传统方法就是应用拥有者手动调整内核pinning或者nice值。然而,我们可以根据实际使用情况进行更佳全局性判断。例如,如果我们可以预测容器A将会占用大量CPU资源,就可以将其运行在不同的NUMA内核上,避免和延迟敏感的容器B放在一起,这可以避免容器B缓存吃紧进而造成L3缓存压力。

通过组合优化位置

操作系统任务调度器主要目标是解决资源分配问题:例如有X个线程,但是只有Y个CPU可用,如何并发地调度这些线程呢?如图例,有16个超线程的实例,只有8个物理超线程核,分布在两个NUMA槽中。每个超线程与其邻居共享L1和L2缓存,与其它7个超线程共享L3缓存。

如果计划将容器A运行在4个线程上,容器B运行在2个线程上,可以看看不同调度的区别:

第一个方式显然最差,因为A和B运行在头两个核心上,L1/L2缓存互相影响,而且第二个槽完全空闲。第二个方式最佳,因为每个CPU都有自己的L1/L2缓存,而且最佳利用了L3缓存。资源调度问题可以通过一种叫做组合优化的方法有效解决,这种方法也会被用在航班调度和有效物流中。这种问题被统称为Mixed Integer Program(MIP)问题。假如一个实例中d个线程运行在K个容器中,每个都需要一定数量的CPU,目标是发现一个(d,K)阶的矩阵,每个容器都能得到请求的CPU。损失函数和限制包含一些好的调度算法,例如:

– 避免容器跨越多个NUMA槽(以免引起跨槽间内存访问和页面调度)

– 尽量避免使用超线程(减少L1/L2冲突)

– 平衡使用L3缓存(基于对容器硬件占用的潜在评估)

考虑到系统低延迟和低计算的请求(不希望太多CPU周期计算容器如何使用CPU),是否能够实现以上的需求?

实现

因为CFS也支持linux的cgroups,因此我们决定也使用这一策略。根据容器对应超线程的期望,修改每个容器的cpuset cgroup,这样用户空间进程定义了CFS运行容器的篱笆墙(隔离墙)。这样通过移除了性能隔离对CFS启发的影响,同时保留了关键的内核调度能力。

用户空间进程是Titus子系统,叫做titus-isolate,其工作原理如下。每个实例,定义三个可以出发位置优化的事件:

– 添加:Titus调度器为实例重新分配的可运行容器

– 移除:运行结束的容器

– 重新平衡:容器中CPU使用率改变,需要重新调整位置策略

我们会定期发起平衡事件触发定位策略。每当事件发生,titus-isolate会请求远程优化服务(也是Titus服务,因此也隔离自身),以此解决容器线程位置问题。

服务发起本地GBRT模式(保有整个Titus平台几个星期以小时为单位的抽样数据),预判十分钟内每个容器对P95 CPU的使用情况。此模式包括上下文功能(与容器相关的元数据:发起者,图像,内存和网络配置,应用名等等。。。)以及从核心CPU搜集到最近几个小时时间序列功能。

预判信息进入MIP系统,我们用cvxpy作为前端nice符号,用以代表可以被各种后端开源MIP算法解决的问题。由于MIP是很难的NP问题,因此需要多加注意。我们给算法充裕的时间以使解决问题延迟最小,以便控制各种解决方案的数量。

通过改变容器 CPUsets ,服务向宿主机返回位置决策。例如,任意时刻,一个具有64个逻辑CPUs r4.16xlarge开起来如下(颜色变化代表CPU占用率):

结论

第一版系统产生了令人惊讶的结果。如图所示,我们通过成比例减少批作业执行时间,同时更重要的是减少作业运行参数(一种合理隔离代理)。我们可以看到一个调整隔离策略前后对比的批处理作业:

注意我们如何大幅度减少运行时间,右侧互相干扰问题消失了。

对于服务,收获印象深刻。在同样要求P99延迟服务质量的负载情况下,因为采用一个特殊Titus中间件服务,使得容量减少了13%(相当于减少了1000多个容器)。我们也注意到因为减少了无效核心缓存情况,设备CPU使用率大幅下降。现在容器使用状况更加可控,更快,资源利用率更加有效。鱼和熊掌兼得的情况可不多见。

下一步

对目前取得的效果我们感到很兴奋,我们正在将收获运用到其他领域。

我们想支持CPU过载。大多数客户并不知道其应用需要用多少CPU资源。实际上,在容器运行各个阶段,这个需求也不同。因为可以预测容器运行阶段CPU的需求,因此可以将资源利用自动化。例如,如果可以监测下图坐标轴上各种敏感信息,我们就可以自动将特定容器部署到共享的CPU上。

我们也计划通过核心PMC事件直接优化最小缓存噪声。一个可能是采用Intel裸金属实例,这一Amazon带来的技术可以对设备进行深度分析。我们可以把信息输入优化引擎,从而得到更加惊讶的学习方法。这一想法要求更佳的、连续的、随机的地点来搜集无偏估计,以便建立某种干涉模型(如果将容器A的某个线程转到容器B上,假设知道容器C也运行在同一个槽上,那么下一分钟容器A的性能是什么样的?)

结论

如果有任何问题,联系我们。