在容器中的Ruby OOM

最近我们遇到了一个Docker容器中的Ruby问题。 尽pipe负载很低,应用程序往往会消耗大量的内存,并在经过一段时间的下载负载OOMs。

经过一番调查,我们把问题缩小到一线

docker run -ti -m 209715200 ruby:2.1 ruby -e 'while true do array = []; 3000000.times do array << "hey" end; puts array.length; end;' 

在一些机器上,它在开始后不久就被OOM杀死了(因为超出限制而死亡),但是在一些机器上,虽然缓慢,但没有OOM。 似乎(似乎只是,也许不是这样)在一些configurationruby能够推断cgroup的限制,并调整它的气相色谱。

testingconfiguration:

  • CentOS 7,Docker 1.9 – OOM
  • CentOS 7,Docker 1.12 – OOM
  • Ubuntu 14.10,Docker 1.9 – OOM
  • Ubuntu 14.10,Docker 1.12 – OOM
  • MacOS X Docker 1.12 – 没有OOM
  • Fedora 23 Docker 1.12 – 没有OOM

如果你看看ruby进程的内存消耗情况,在所有情况下,它的performance都与这张图片类似,保持在稍低于极限的相同水平,或者崩溃到极限并被杀死。

内存消耗情节

我们希望不惜一切代价避免OOM,因为它会降低弹性并且会造成数据丢失的风险。 内存真正需要的应用程序是低于极限。

你有什么build议,如何做ruby,以避免OOMing,可能是因为失去了性能?

我们无法弄清楚testing装置之间的显着差异。

编辑:更改代码或增加内存限制不可用。 第一个是因为我们用我们无法控制的社区插件来运行,第二个是因为它不能保证我们将来不会再面对这个问题。

你可以尝试通过环境variables来调整 ruby垃圾回收(取决于你的ruby版本):

 RUBY_GC_MALLOC_LIMIT=4000100 RUBY_GC_MALLOC_LIMIT_MAX=16000100 RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR=1.1 

或通过GC.start调用垃圾回收GC.start

对于你的例子,尝试

docker run -ti -m 209715200 ruby:2.1 ruby -e 'while true do array = []; 3000000.times do array << "hey" end; puts array.length; array = nil; end;'

帮助垃圾收集器。

编辑:

我没有与你相似的环境。 在我的机器上(14.04.5 LTS,docker 1.12.3,RAM 4GB,Intel(R)Core TM i5-3337U CPU @ 1.80GHz),以下看起来很有希望。

 docker run -ti -m 500MB -e "RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR=1" \ -e "RUBY_GC_MALLOC_LIMIT=5242880" \ -e "RUBY_GC_MALLOC_LIMIT_MAX=16000100" \ -e "RUBY_GC_HEAP_INIT_SLOTS=500000" \ ruby:2.1 ruby -e 'while true do array = []; 3000000.times do array << "hey" end; puts array.length; puts `ps -o rss -p #{Process::pid}`.chomp.split("\n").last.strip.to_i / 1024.0 / 1024 ; puts GC.stat; end;' 

但每个ruby应用程序需要一个不同的设置微调,如果你遇到内存泄漏,你的丢失。

我不认为这是一个docker问题。 你过度使用容器的资源,一旦你达到内存阈值,Ruby往往performance不好。 它可以是GC,但是如果另一个进程试图占用一些内存,或者Ruby在最大输出时尝试重新分配,那么内核将(通常)以最大内存来终止进程。 如果您担心服务器上的内存使用率,请在80%的RAM上添加一些阈值警报,并为作业分配适当大小的资源。 当您开始达到阈值时,分配更多的RAM或查看特定的作业参数/分配,以查看是否需要重新devise以降低占用空间。

另一个可能的select,如果你真的想有一个很好的固定的内存带到GC反对是使用JRuby和设置JVM最大内存留在容器内存一个小摆动的空间。 JVM将更好地pipe理OOM,因为它不会与其他进程共享这些资源,也不会让内核认为服务器正在死亡。

我在一个Docker主机上运行的一些基于java的Docker容器也有类似的问题。 问题是每个容器看到了主机的全部可用内存,并假定它可以为自己使用所有的内存。 它不经常运行GC,最终导致内存exception。 我结束了手动限制每个容器可以使用的内存量,我不再有OOMs。 在合作者内部,我也限制了JVM的内存。

不知道这是否是你看到的相同的问题,但它可能是相关的。

https://docs.docker.com/engine/reference/run/#/runtime-constraints-on-resources