实战:Docker容器可能变慢的一个“神秘”原因

近期我们谈论Kubernetes以及ThoughtSpot如何将它用于开发基础设施的需求。今天我想用最近发生的一个相当简短但有趣的调试故事来跟进。它重申了容器化化=虚拟化的事实,并展示了即使所有cgroup限制设置为合理值,并且主机上有足够的计算能力下,容器化过程将如何竞争资源?

实战:Docker容器可能变慢的一个“神秘”原因

因此,我们使用内部的Kubernetes集群来运行一系列CI/CD和开发相关的工作流程,除了一件事情之外,一切都变得很好:当启动我们产品的Docker副本时,我们看到了比我们预期的、更糟糕的性能。每个容器都具有宽裕的CPU和内存限制,通过Pod配置设置了5个CPU/30 Gb RAM。在一台虚拟机上,我们的小型(10Kb)测试数据集上的所有查询将超过四个。在Docker&Kubernetes上,我们只能在72 CPU/512 Gb RAM机器上启动3-4个产品副本,之后情况变得太慢。过去几毫秒内完成的查询现在需要一两秒钟,这导致我们的CI管道中出现各种故障。所以,我们努力进行调试。

当然,通常的原因是我们在将产品打包到Docker时可能发生的配置错误。但是,与VM或裸机安装相比,我们找不到任何可能导致缓慢的原因。一切看起来没毛病。下一步,我们运行了Sysbench软件包中的各种测试。我们测试了CPU,磁盘和RAM的性能,没有任何与裸机不同。我们产品中的一些服务可以保存所有活动的详细信息,以后可用于性能分析。通常,如果我们在某个资源(CPU,RAM,磁盘,网络)上不足,某些调用的时间会有明显的偏差,这就是我们如何确定缓慢来自哪里的原因。然而,在这种情况下,没有什么看起来不对。所有时间比例与健康配置相同,只是每次调用都比裸机要慢得多。没有任何东西指向我们实际问题的方向,我们准备放弃,但后来我们发现了一篇博客文章:

https://sysdig.com/blog/container-isolation-gone-wrong/

在文中,作者分析了一个类似的神秘案例,即在资源限制设置为非常保守的值的情况下,在同一台计算机上运行Docker时,两个据称轻量级的进程正在相互侵蚀。对我们来说两个关键的要点是:

  • 他的问题的根本原因最终在Linux内核中。由于内核dentry缓存设计,一个进程的行为使__d_lookup_loop内核调用速度明显变慢,这直接影响了另一个进程的性能。

  • 作者使用perf来追踪内核错误,一个不错的调试工具,这是我们以前从未使用过的,真是太遗憾了!。

perf(有时称为perf_events或perf工具,最初是Linux的性能计数器,PCL)是Linux中的性能分析工具,可从Linux内核版本2.6.31中获得。从命令行访问名为perf的用户空间控制实用程序,并提供许多子命令;它能够对整个系统进行统计分析(内核和用户代码)。

它支持硬件性能计数器,跟踪点,软件性能计数器(例如hrtimer)和动态探测器(例如kprobes或uprobes)。在2012年,两位IBM工程师认识到perf(连同OProfile)是Linux上最常用的两种性能计数器分析工具。

所以,我们认识到,可能我们的情况难道也是这样?我们在容器中运行数百个不同的进程,它们都共享相同的内核。会有有一些瓶颈!所以掌握perf,重新开始调试,并获得了一些有趣的发现。

以下是在健康机器(左侧)和容器(右侧)内运行几十秒的ThoughtSpot的全记录。

实战:Docker容器可能变慢的一个“神秘”原因

我们可以立即注意到,右侧的前5个调用与内核相关,时间主要用于内核空间,而左侧的大部分时间都是由我们自己的进程在用户空间中运行所花费的。更有趣的是,一直在调用的是posix_fadvise。

程序可以使用posix_fadvise()来宣布将以特定模式访问文件数据的意图,从而允许内核执行适当的优化。

它可以用于各种情况,所以它不直接表明问题可能来自哪里。然而,在搜索我们的代码库之后,我发现只有一个地方可能会被系统中的每个进程击中:

实战:Docker容器可能变慢的一个“神秘”原因

它位于名为glog的第三方日志记录库中。我们在整个项目中都使用它,这个特定的行在LogFileObject :: Write中 - 也许是整个库中最关键的路径。每次“登录到文件”事件都会调用它,并且我们的产品的多个实例可能会非常密集地记录。快速查看源代码表明,可以通过设置--drop_log_memory = false标志禁用fadvise部分:

if (FLAGS_drop_log_memory) {

if (file_length_ >= logging::kPageSize) {

// don’t evict the most recent page

uint32 len = file_length_ & ~(logging::kPageSize — 1);

posix_fadvise(fileno(file_), 0, len, POSIX_FADV_DONTNEED);

}

}

我们立即尝试了...... bingo!

实战:Docker容器可能变慢的一个“神秘”原因

以前需要花费几秒钟的时间现在只需要8毫秒。搜索后的相关结果https://issues.apache.org/jira/browse/MESOS-920和https://github.com/google/glog/pull/145,进一步证实这确实是慢的根本原因。最有可能的是,它甚至在虚拟机或裸机上影响我们,但是因为我们每个机器/内核每个进程只有一个副本,所以他们调用fadvise的速度要慢几倍,因此不会增加大量的开销。将日志记录过程的数量增加3-4倍,同时让它们共享相同的内核,这就导致时间成为真正的瓶颈的原因。

结论

虽然这绝对不是一个新发现,但大多数人仍然没有记住,在容器的情况下,“孤立”的进程不仅竞争CPU,RAM,磁盘和网络,而且还为各种内核资源竞争。而且,由于内核难以置信地复杂,在大多数意想不到的地方可能会出现效率低下的情况(如Sysdig文章中的__d_lookup_loop)。

但这并不意味着容器比传统虚拟化更糟或更好。它是实现目的的绝佳工具。我们应该始终都知道内核是共享资源,并准备在内核空间中调试奇怪的冲突。最后,perf是一个很棒的工具,它可以告诉你系统中发生了什么,并帮助你调试各种性能问题。如果您计划在Docker上运行高负载应用程序,那么你肯定应该投入时间学习性能的相关内容。

本文编译自:https://hackernoon.com/another-reason-why-your-docker-containers-may-be-slow-d37207dec27f

相关推荐