Netflix 如何正确计算docker中containers的CPU分配

Docker如何做资源隔离,还是不做?怎样做才能最大化利用服务器资源,避免浪费?
我们看看Netflix是如何做到的 。
吵闹的邻居(Noisy Neighbors)我们都曾有过吵闹的邻居 。无论是在咖啡馆,还是穿过公寓的墙壁,它总是具有破坏性 。事实证明,在共享空间中保持良好的礼仪不仅对人很重要,对Docker容器也很重要 。
当你在云中运行时,你的containers在一个共享空间中;特别是它们共享主机实例的CPU内存层次结构 。
由于微处理器速度如此之快,计算机体系结构设计已经发展到在计算单元和主内存之间添加不同级别的缓存,以隐藏将bits带到CPU的延迟 。然而,这里的关键观点是,这些Cache在cpu之间部分共享,这意味着不可能对共同承载的containers进行完美的性能隔离 。如果在containers旁边的核心上运行的containers突然决定从RAM中获取大量数据,这将不可避免地导致更多的缓存丢失(从而导致潜在的性能下降) 。
linux拯救世界(Linux to the rescue)?传统上,减轻性能隔离问题一直是操作系统任务调度程序的职责 。在Linux中,当前的主流解决方案是CFS(Completely Fair Scheduler)( https://en.wikipedia.org/wiki/Completely_Fair_Scheduler) 。它的目标是以"公平"的方式将正在运行的进程分配给CPU的时间片 。
CFS得到了广泛的应用,因此经过了良好的测试,世界各地的Linux机器运行起来都具有合理的性能 。那么,为什么要搅乱它呢?事实证明,对于Netflix的大多数用例来说,它的性能远远不是最优的 。Titus(https://netflix.github.io/titus/)是Netflix的集装箱平台 。每个月,我们在Titus上的数千台机器上运行数百万个容器,为数百个内部应用程序和客户提供服务 。这些应用程序的范围从支持面向客户的视频流服务的关键低延迟服务,到用于编码或机器学习的批处理作业 。维护这些不同应用程序之间的性能隔离对于确保内部和外部客户的良好体验至关重要 。
通过将一些CPU隔离责任从操作系统转移到包含组合优化和机器学习的数据驱动解决方案,我们能够显著提高这些容器的可预测性和性能 。
这个想法(The idea)CFS非常频繁地(每隔几微秒)应用一组启发式操作,这些启发式操作封装了围绕CPU硬件使用的最佳实践的一般概念 。
相反,如果我们减少干预的频率(每隔几秒钟),但在分配计算资源的过程方面做出更好的数据驱动决策,以最小化配置噪音,结果会怎样?
减轻CFS性能问题的一种传统方法是让应用程序所有者通过使用核心固定或nice值手动协作 。然而,通过基于实际使用信息检测搭配机会,我们可以自动做出更好的全局决策 。例如,如果我们预测容器A将很快变得非常CPU密集型,那么也许我们应该在一个不同的NUMA(https://en.wikipedia.org/wiki/Non-uniform_memory_access)套接字上运行它,而容器B对延迟非常敏感 。这避免了过多的抖动缓存为B和平衡的压力,对L3(https://en.wikipedia.org/wiki/Memory_hierarchy)缓存的机器 。
通过组合优化优化布局(Optimizing placements through combinatorial optimization)OS任务调度程序所做的实际上是解决一个资源分配问题:我有X个线程要运行,但只有Y个cpu可用,我如何将线程分配给cpu,以产生并发的假象?
作为一个演示示例,让我们考虑一个包含16(https://en.wikipedia.org/wiki/Hyper-threading)个超线程的玩具实例 。它有8个物理超线程核心,分裂在2个NUMA套接字上 。每个超线程与其邻居共享其L1和L2缓存,并与套接字上的其他7个超线程共享其L3缓存:

Netflix 如何正确计算docker中containers的CPU分配

文章插图
 
如果我们想在4个线程上运行容器A,在这个实例上在2个线程上运行容器B,我们可以看看"坏"和"好"的布局决策是什么样子的:
Netflix 如何正确计算docker中containers的CPU分配

文章插图
 
第一个位置在直觉上是不好的,因为我们可能通过L1/L2缓存在前2个核心上创建A和B之间的并置噪声(collocation noise),而通过L3缓存在套接字上创建套接字,同时保留整个套接字为空 。第二个位置看起来更好,因为每个CPU都有自己的L1/L2缓存,我们更好地利用了两个可用的L3缓存 。
资源分配问题可以通过数学的一个分支组合优化有效地解决,例如用于航空公司调度或物流问题 。
我们将问题表示为一个混合整数程序(MIP) 。给定一组K个容器,每个容器在拥有d个线程的实例上请求特定数量的cpu,目标是找到一个大小为M (d, K)的二进制赋值矩阵,以便每个容器获得它请求的cpu数量 。损失函数和约束包含了表示先验的良好配置决策的各种术语,例如:


推荐阅读