代码改变世界

java中的Docker

2019-11-14 09:10  胡世达  阅读(1167)  评论(0编辑  收藏  举报

Docker和虚拟机的区别

看起来Docker和虚拟机有很多相似的地方。他有自己的shell,能独立安装软件包,运行起来和其他容器互不干扰。不过Docker并不是完全的虚拟化技术,而是一种轻量级的隔离技术。

Docker仅仅在Linux内核上面,进行了有限的隔离和虚拟化,没有像传统虚拟化软件一样,独立运行一个新的操作系统。

Docker基于namespace,给每个容器提供了单独的命名空间,对网络,PID,用户,IPC通信,文件系统挂载点实现了隔离。

Docker通过CGroup,管理CPU,内存,磁盘IO等计算资源。

Docker给Java平台带来的问题

由于Docker没有完全隐藏底层信息,会带来一些问题。主要体现在几个方面:

一:容器采用CGroup方式管理计算资源的方式是全新的 。历史版本的Java并不能很好地理解容器对应的资源限制

二:namespace给容器内部的应用增加了一些差异。比如jcmd,jstack这些工具会依赖“/proc//”下面提供的部分信息,但是Docker的设计,对原有的结构做了改变,需要自己对原有的工具做一些修改。

对JVM的影响

JVM会根据系统资源(内存,CPU等)情况,在启动的时候设置默认参数。

比如,JVM根据检测到的内存大小,把最初启动时候的堆大小设置为系统内存的1/64,并且把堆最大值,设置为系统内存的1/4.

JVM检测到的CPU核数,会直接影响Parallel GC的并行线程数和JIT Complier线程数。

这些默认参数,都是根据通用场景原则的初始值,但是由于容器环境有差异,Java 的判断基于的信息可能是错误的。

有些JVM的原有的备用机制也会受到影响。
比如为了保证服务的可用性,一个常见的选择是依赖-XX:OnOutOfMemoryError功能,通过调用处理脚本来做一些补救的措施,比如自动重启服务,但是这个机制是基于fork实现的。如果Java进程已经过度提交内存,那么fork的新进程往往已经不可能正常运行了。

如何解决这些问题

在实践中,通常升级到比较新的版本,这个问题就能解决了。
比如在JVM9当中,有一些实验性的参数,用于Docker和Java沟通。
针对内存限制,可以用下面的参数设置

-XX:+UnlockExperimentalVMOptions
-XX:_UserCGroupMemoryLimitForHeap

这两个参数值支持LInux环境。

如果是JDK10版本,问题就更简单了。Java对Docker的支持已经做的比较完善了。默认就支持各种资源限制。
增加了参数,用来明确指定CPU核心的数量。 -XX:ActiveProcessorCount-N

如果只能使用老版本,如何解决

如果暂时只能使用老版本,可以采用多种方式解决
一、明确设置堆等内存区域的大小,保证Java进程的总大小是可控的。
比如在运行环境中,限制内存
docker rum -it --rm --name yourcontainer -p 8080:8080 -m 800M repo/your-java-container:openjdk
额外配置环境变量,直接指定JVM堆大小。
-e JAVA_OPTIONS='-Xmx300m'
配置GC和JIT并行的线程数量,避免他们占用过多的计算资源。

-XX:ParallelGCThreads
-XX:CICompilerCount

由于很多场景下,Java在DOcker环境中,会意外地使用Swap。可以配置参数,明确告诉JVM系统内存限额。

-XX:MaxRAM=`cat /sys/fs/cgroup/memeoy/memory.limit_in_bytes`

也可以指定Docker运行参数
--memory--swapiness-0

参考资料

《极客时间:java核心36讲》