小聪明 2016-05-20
开发者在使用JVM、内存与Docker时是否遭遇到难题?今天我们将一同利用Jelastic将其解决。最近Matt Willaims提起的Docker内Java及其内存限制的议题得到了广泛关注,而这些在容器使用当中无法避免的状况也在Twitter上引发一系列讨论。
那么在今天的文章中,我们就来共同分析这一问题并尝试找出解决办法。
问题
Matt讲述了他在Docker容器内使用JVM heap时的探索之旅。他指出,容器对于内存的限制未能正确显示,这导致任意Java或其它应用都可分配给该主机设备的全部内存资源,而JVM却无法指示其中有多少资源应该专门用于其运行所在的父容器。如此一来,OutOfMemoryError开始出现。
事实上,大多数Linux工具所提供的系统资源指标在创建时间上要早于cgroups(例如free与top,二者皆源自procps)。其通常会从proc文件系统:/proc/meminfo,/proc/vmstat, /proc/PID/smaps或者其它位置处读取内存指标。
遗憾的是,/proc/meminfo, /proc/vmstat及其它位置无法实现容器化,也就是其无法为cgroup所识别。其只能显示主机系统(物理或虚拟机)的整体内存容量,而现代Linux容器技术无法对其加以利用。容器中的流程无法依靠free、top及其它指标确定所能使用的内存容量;其必须遵循cgroups提供的约束条件,且无法使用主机系统中的全部内存。
事实上,实际内存限制条件的可见性非常重要,我们需要利用其实现应用优化及故障排查,例如内存泄露、交换空间使用、性能下降等等。另外,某些用例还需要利用垂直伸缩以实现容器内的资源使用优化,包括自动变更工作程序、进程或线程数量。垂直伸缩往往取决于特定容器的实际可用内存量,因此必须配合容器中的限制可见性方能起效。
解决方案
开放容器倡议配合runC能够利用文件系统用户空间覆盖/proc文件。LXC创建的lxcfs文件系统允许容器拥有虚拟cgroup文件系统及虚拟/proc文件查看方式,这样上述问题的实质就成了如何对容器进行维护。
我们也遇到过类似的问题,但在Jelastic的帮助下,我们顺利为客户解决了这一障碍。下面来看具体办法。
首先打开Jelastic向导,为测试账户选定服务供应商并利用预定义内存限制创建一套Java Docker容器——例如8 cloudlets,相当于1 GB内存。
接下来前往Jelastic SSH gate(1),选定之前创建的测试环境(2),而后选定该容器(3)。现在我们已经可以利用free工具检查可用内存了(4)。
As we can see, the memory limit equals 1GB defined before. Let’s check the top tool.
一切运行正常。为了进一步检查,我们重复Matt之前提到的Java启发式行为问题。
现在的MaxHeapSize = 268435546 (~256 MiB),意味着当前容器的内存容量为默认JVM heap的四分之一。
其中的秘诀是什么?当然,这是各种“成分”的完美结合。在本示例中,我们将OpenVZ与Docker技术结合以提供更出色的安全与隔离控制效果,同时保证其不会影响到容器实时迁移与容器休眠等必要功能。下面,大家可以看到Docker容器在Jelastic当中的高级应用思路。
在OpenVZ中,每套容器都拥有一套/proc pseudo-filesystem虚拟化视图。需要强调的是,容器中的/proc/meminfo为“定制”版本,能够显示每容器信息而非直接提取自主机。如此一来,top与free等工具在运行于容器内时,就能显示特定容器中遵循限制条件时的内存与swap使用情况。
值得注意的是,容器中的swap并非真正的swap,而只是一种虚拟机制(因此其整体技术被称为VSwap)。其核心思路在于,一旦启用了VSwap的某容器超出配置内存限制,则其部分内存即会转换为所谓swap cache。期间并不会发生真正的空间交换,意味着不存在I/O——除非整体环境下出现了全局(而非每容器)内存短缺。另外,使用VSwap的容器会通过降低运行速度的方式解决内存超标,这种方式从容器内部角度来看类似于执行真正的空间交换。这项技术能够对每容器内存与swap使用情况加以有效控制。