当前位置:首页 » 资源管理 » 容器如何做资源限制
扩展阅读
如何判断无形资源 2025-05-15 09:48:48
一条蛇成本多少 2025-05-15 09:47:08

容器如何做资源限制

发布时间: 2022-08-08 19:45:14

‘壹’ 生产环境,测试环境中,Docker 可以做什么

上次有人说不敢在生产环境中用 Docker,所以 SegmentFault 社区组织翻译 8 个你可能不知道的 Docker 知识 这篇文章,和大家介绍一下生产环境中的 Docker 用例。
自从上世纪 90 年代硬件虚拟化被主流的技术广泛普及之后,对数据中心而言,发生的最大的变革莫过于容器和容器管理工具,例如:Docker。在过去的一年内,Docker 技术已经逐渐走向成熟,并且推动了大型初创公司例如 Twitter 和 Airbnb 的发展,甚至在银行、连锁超市、甚至 NASA 的数据中心都赢得了一席之地。当我几年前第一次直到 Docker 的时候,我还对 Docker 的未来持怀疑的态度,我认为他们是把以前的 Linux 容器的概念拿出来包装了一番推向市场。但是使用 Docker 成功进行了几个项目 例如 Spantree 之后,我改变了我的看法:Docker 帮助我们节省了大量的时间和经历,并且已经成为我们技术团队中不可或缺的工具。
GitHub 上面每天都会催生出各式各样的工具、形态各异的语言和千奇百怪的概念。如果你和我一样,没有时间去把他们全部都测试一遍,甚至没有时间去亲自测试 Docker,那么你可以看一下我的这篇文章:我将会用我们在 Docker 中总结的经验来告诉你什么是 Docker、为什么 Docker 会这么火。
Docker 是容器管理工具
Docker 是一个轻量级、便携式、与外界隔离的容器,也是一个可以在容器中很方便地构建、传输、运行应用的引擎。和传统的虚拟化技术不同的是,Docker 引擎并不虚拟出一台虚拟机,而是直接使用宿主机的内核和硬件,直接在宿主机上运行容器内应用。也正是得益于此,Docker 容器内运行的应用和宿主机上运行的应用性能差距几乎可以忽略不计。
但是 Docker 本身并不是一个容器系统,而是一个基于原有的容器化工具 LXC 用来创建虚拟环境的工具。类似 LXC 的工具已经在生产环境中使用多年,Docker 则基于此提供了更加友好的镜像管理工具和部署工具。
Docker 不是虚拟化引擎
Docker 第一次发布的时候,很多人都拿 Docker 和虚拟机 VMware、KVM 和 VirtualBox 比较。尽管从功能上看,Docker 和虚拟化技术致力于解决的问题都差不多,但是 Docker 却是采取了另一种非常不同的方式。虚拟机是虚拟出一套硬件,虚拟机的系统进行的磁盘操作,其实都是在对虚拟出来的磁盘进行操作。当运行 CPU 密集型的任务时,是虚拟机把虚拟系统里的 CPU 指令“翻译”成宿主机的CPU指令并进行执行。两个磁盘层,两个处理器调度器,两个操作系统消耗的内存,所有虚拟出的这些都会带来相当多的性能损失,一台虚拟机所消耗的硬件资源和对应的硬件相当,一台主机上跑太多的虚拟机之后就会过载。而 Docker 就没有这种顾虑。Docker 运行应用采取的是“容器”的解决方案:使用 namespace 和 CGroup 进行资源限制,和宿主机共享内核,不虚拟磁盘,所有的容器磁盘操作其实都是对 /var/lib/docker/ 的操作。简言之,Docker 其实只是在宿主机中运行了一个受到限制的应用程序。
从上面不难看出,容器和虚拟机的概念并不相同,容器也并不能取代虚拟机。在容器力所不能及的地方,虚拟机可以大显身手。例如:宿主机是 Linux,只能通过虚拟机运行 Windows,Docker 便无法做到。再例如,宿主机是 Windows,Windows 并不能直接运行 Docker,Windows上的 Docker 其实是运行在 VirtualBox 虚拟机里的。
Docker 使用层级的文件系统
前面提到过,Docker 和现有容器技术 LXC 等相比,优势之一就是 Docker 提供了镜像管理。对于 Docker 而言,镜像是一个静态的、只读的容器文件系统的快照。然而不仅如此,Docker 中所有的磁盘操作都是对特定的Copy-On-Write文件系统进行的。下面通过一个例子解释一下这个问题。
例如我们要建立一个容器运行 JAVA Web 应用,那么我们应该使用一个已经安装了 JAVA 的镜像。在 Dockerfile(一个用于生成镜像的指令文件)中,应该指明“基于 JAVA 镜像”,这样 Docker 就会去 Docker Hub Registry 上下载提前构建好的 JAVA 镜像。然后再 Dockerfile 中指明下载并解压 Apache Tomcat 软件到 /opt/tomcat 文件夹中。这条命令并不会对原有的 JAVA 镜像产生任何影响,而仅仅是在原有镜像上面添加了一个改动层。当一个容器启动时,容器内的所有改动层都会启动,容器会从第一层中运行 /usr/bin/java 命令,并且调用另外一层中的 /opt/tomcat/bin 命令。实际上,Dockerfile 中每一条指令都会产生一个新的改动层,即便只有一个文件被改动。如果用过 Git 就能更清楚地认识这一点,每条指令就像是每次 commit,都会留下记录。但是对于 Docker 来说,这种文件系统提供了更大的灵活性,也可以更方便地管理应用程序。
我们Spantree的团队有一个自己维护的含有 Tomcat 的镜像。发布新版本也非常简单:使用 Dockerfile 将新版本拷贝进镜像从而创建一个新镜像,然后给新镜像贴上版本的标签。不同版本的镜像的不同之处仅仅是一个 90 MB 大小的 WAR 文件,他们所基于的主镜像都是相同的。如果使用虚拟机去维护这些不同的版本的话,还要消耗掉很多不同的磁盘去存储相同的系统,而使用 Docker 就只需要很小的磁盘空间。即便我们同时运行这个镜像的很多实例,我们也只需要一个基础的 JAVA / TOMCAT 镜像。
Docker 可以节约时间
很多年前我在为一个连锁餐厅开发软件时,仅仅是为了描述如何搭建环境都需要写一个 12 页的 Word 文档。例如本地 Oracle 数据库,特定版本的 JAVA,以及其他七七八八的系统工具和共享库、软件包。整个搭建过程浪费掉了我们团队每个人几乎一天的时间,如果用金钱衡量的话,花掉了我们上万美金的时间成本。虽然客户已经对这种事情习以为常,甚至认为这是引入新成员、让成员适应环境、让自己的员工适应我们的软件所必须的成本,但是相比较起来,我们宁愿把更多的时间花在为客户构建可以增进业务的功能上面。
如果当时有 Docker,那么构建环境就会像使用自动化搭建工具 Puppet / Chef / Salt / Ansible 一样简单,我们也可以把整个搭建时间周期从一天缩短为几分钟。但是和这些工具不同的地方在于,Docker 可以不仅仅可以搭建整个环境,还可以将整个环境保存成磁盘文件,然后复制到别的地方。需要从源码编译 Node.js 吗?Docker 做得到。Docker 不仅仅可以构建一个 Node.js 环境,还可以将整个环境做成镜像,然后保存到任何地方。当然,由于 Docker 是一个容器,所以不用担心容器内执行的东西会对宿主机产生任何的影响。
现在新加入我们团队的人只需要运行 docker-compose up 命令,便可以喝杯咖啡,然后开始工作了。
Docker 可以节省开销
当然,时间就是金钱。除了时间外,Docker 还可以节省在基础设施硬件上的开销。高德纳和麦肯锡的研究表明,数据中心的利用率在 6% - 12% 左右。不仅如此,如果采用虚拟机的话,你还需要被动地监控和设置每台虚拟机的 CPU 硬盘和内存的使用率,因为采用了静态分区(static partitioning)所以资源并不能完全被利用。。而容器可以解决这个问题:容器可以在实例之间进行内存和磁盘共享。你可以在同一台主机上运行多个服务、可以不用去限制容器所消耗的资源、可以去限制资源、可以在不需要的时候停止容器,也不用担心启动已经停止的程序时会带来过多的资源消耗。凌晨三点的时候只有很少的人会去访问你的网站,同时你需要比较多的资源执行夜间的批处理任务,那么可以很简单的便实现资源的交换。
虚拟机所消耗的内存、硬盘、CPU 都是固定的,一般动态调整都需要重启虚拟机。而用 Docker 的话,你可以进行资源限制,得益于 CGroup,可以很方便动态调整资源限制,让然也可以不进行资源限制。Docker 容器内的应用对宿主机而言只是两个隔离的应用程序,并不是两个虚拟机,所以宿主机也可以自行去分配资源。

‘贰’ 常见的容器安全威胁有哪些

以Docker 容器的安全问题为例

(1) Docker 自身安全

Docker 作为一款容器引擎,本身也会存在一些安全漏洞,CVE 目前已经记录了多项与 Docker 相关的安全漏洞,主要有权限提升、信息泄露等几类安全问题。

(2) 镜像安全

由于Docker 容器是基于镜像创建并启动,因此镜像的安全直接影响到容器的安全。具体影响镜像安全的总结如下。

镜像软件存在安全漏洞:由于容器需要安装基础的软件包,如果软件包存在漏洞,则可能会被不法分子利用并且侵入容器,影响其他容器或主机安全。

仓库漏洞:无论是Docker 官方的镜像仓库还是我们私有的镜像仓库,都有可能被攻击,然后篡改镜像,当我们使用镜像时,就可能成为攻击者的目标对象。

用户程序漏洞:用户自己构建的软件包可能存在漏洞或者被植入恶意脚本,这样会导致运行时提权影响其他容器或主机安全。

(3) Linux 内核隔离性不够

尽管目前Namespace 已经提供了非常多的资源隔离类型,但是仍有部分关键内容没有被完全隔离,其中包括一些系统的关键性目录(如 /sys、/proc 等),这些关键性的目录可能会泄露主机上一些关键性的信息,让攻击者利用这些信息对整个主机甚至云计算中心发起攻击。

而且仅仅依靠Namespace 的隔离是远远不够的,因为一旦内核的 Namespace 被突破,使用者就有可能直接提权获取到主机的超级权限,从而影响主机安全。

(4) 所有容器共享主机内核

由于同一宿主机上所有容器共享主机内核,所以攻击者可以利用一些特殊手段导致内核崩溃,进而导致主机宕机影响主机上其他服务。

既然容器有这么多安全上的问题,那么我们应该如何做才能够既享受到容器的便利性同时也可以保障容器安全呢?下面我带你来逐步了解下如何解决容器的安全问题。

如何解决容器的安全问题?

(1) Docker 自身安全性改进

事实上,Docker 从 2013 年诞生到现在,在安全性上面已经做了非常多的努力。目前 Docker 在默认配置和默认行为下是足够安全的。

Docker 自身是基于 Linux 的多种 Namespace 实现的,其中有一个很重要的 Namespace 叫作 User Namespace,User Namespace 主要是用来做容器内用户和主机的用户隔离的。在过去容器里的 root 用户就是主机上的 root 用户,如果容器受到攻击,或者容器本身含有恶意程序,在容器内就可以直接获取到主机 root 权限。Docker 从 1.10 版本开始,使用 User Namespace 做用户隔离,实现了容器中的 root 用户映射到主机上的非 root 用户,从而大大减轻了容器被突破的风险。

因此,我们尽可能地使用Docker 最新版本就可以得到更好的安全保障。

(2) 保障镜像安全

为保障镜像安全,我们可以在私有镜像仓库安装镜像安全扫描组件,对上传的镜像进行检查,通过与CVE 数据库对比,一旦发现有漏洞的镜像及时通知用户或阻止非安全镜像继续构建和分发。同时为了确保我们使用的镜像足够安全,在拉取镜像时,要确保只从受信任的镜像仓库拉取,并且与镜像仓库通信一定要使用 HTTPS 协议。

(3) 加强内核安全和管理

由于仅仅依赖内核的隔离可能会引发安全问题,因此我们对于内核的安全应该更加重视。可以从以下几个方面进行加强。

宿主机及时升级内核漏洞

宿主机内核应该尽量安装最新补丁,因为更新的内核补丁往往有着更好的安全性和稳定性。

使用Capabilities 划分权限

Capabilities 是 Linux 内核的概念,Linux 将系统权限分为了多个 Capabilities,它们都可以单独地开启或关闭,Capabilities 实现了系统更细粒度的访问控制。

容器和虚拟机在权限控制上还是有一些区别的,在虚拟机内我们可以赋予用户所有的权限,例如设置cron 定时任务、操作内核模块、配置网络等权限。而容器则需要针对每一项 Capabilities 更细粒度的去控制权限,例如:

cron 定时任务可以在容器内运行,设置定时任务的权限也仅限于容器内部;

由于容器是共享主机内核的,因此在容器内部一般不允许直接操作主机内核;

容器的网络管理在容器外部,这就意味着一般情况下,我们在容器内部是不需要执行ifconfig、route等命令的 。

由于容器可以按照需求逐项添加Capabilities 权限,因此在大多数情况下,容器并不需要主机的 root 权限,Docker 默认情况下也是不开启额外特权的。

最后,在执行docker run命令启动容器时,如非特殊可控情况,–privileged 参数不允许设置为 true,其他特殊权限可以使用 --cap-add 参数,根据使用场景适当添加相应的权限。

使用安全加固组件

Linux 的 SELinux、AppArmor、GRSecurity 组件都是 Docker 官方推荐的安全加固组件。下面我对这三个组件做简单介绍。

SELinux (Secure Enhanced Linux): 是 Linux 的一个内核安全模块,提供了安全访问的策略机制,通过设置 SELinux 策略可以实现某些进程允许访问某些文件。

AppArmor: 类似于 SELinux,也是一个 Linux 的内核安全模块,普通的访问控制仅能控制到用户的访问权限,而 AppArmor 可以控制到用户程序的访问权限。

GRSecurity: 是一个对内核的安全扩展,可通过智能访问控制,提供内存破坏防御,文件系统增强等多种防御形式。

这三个组件可以限制一个容器对主机的内核或其他资源的访问控制。目前,容器报告的一些安全漏洞中,很多都是通过对内核进行加强访问和隔离来实现的。

资源限制

在生产环境中,建议每个容器都添加相应的资源限制。下面给出一些执行docker run命令启动容器时可以传递的资源限制参数:

1--cpus 限制 CPU 配额

2-m, --memory 限制内存配额

3--pids-limit 限制容器的 PID 个数

例如我想要启动一个1 核 2G 的容器,并且限制在容器内最多只能创建 1000 个 PID,启动命令如下:

1 $ docker run -it --cpus=1 -m=2048m --pids-limit=1000 busybox sh

推荐在生产环境中限制CPU、内存、PID 等资源,这样即便应用程序有漏洞,也不会导致主机的资源完全耗尽,最大限度降低安全风险。

(4) 使用安全容器

容器有着轻便快速启动的优点,虚拟机有着安全隔离的优点,有没有一种技术可以兼顾两者的优点,做到既轻量又安全呢?

答案是有,那就是安全容器。安全容器是相较于普通容器的,安全容器与普通容器的主要区别在于,安全容器中的每个容器都运行在一个单独的微型虚拟机中,拥有独立的操作系统和内核,并且有虚拟化层的安全隔离。

安全容器目前推荐的技术方案是Kata Containers,Kata Container 并不包含一个完整的操作系统,只有一个精简版的 Guest Kernel 运行着容器本身的应用,并且通过减少不必要的内存,尽量共享可以共享的内存来进一步减少内存的开销。另外,Kata Container 实现了 OCI 规范,可以直接使用 Docker 的镜像启动 Kata 容器,具有开销更小、秒级启动、安全隔离等许多优点。

‘叁’ 怎么限制cpu

现在很多高端笔记本游戏本都有一个通病,那就是在玩大型游戏和渲染输出视频的时候,CPU会满载运行以至于温度飙升,下面就教大家如何限制cpu频率吧。

1、首先找到控制面板,打开控制面板,打开这个电源选项。

2、这里有三种模式,我这里以高性能模式为例(其他两种模式也步骤一样),点击更改计划设置。

3、然后点击更改高级电源设置。

4、这里选择处理器,依次展开,打开最大处理器状态,限制我们限制的就是这个数值。

5、我们分别将这两个数据都设置为85%,以我的本子为例,处理器最高睿频为3.4Ghz,限制最高性能在85%后最高睿频为2Ghz左右,此时玩游戏是感觉不到任何区别的,但是温度却降下来了,对于那些散热不给力的本本,这个方法可以有效降低CPU高温问题。

拓展资料:

中央处理器(CPU,central processing unit)作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元。CPU 自产生以来,在逻辑结构、运行效率以及功能外延上取得了巨大发展。

中央处理器主要包括两个部分,即控制器、运算器,其中还包括高速缓冲存储器及实现它们之间联系的数据、控制的总线。电子计算机三大核心部件就是CPU、内部存储器、输入/输出设备。中央处理器的功效主要为处理指令、执行操作、控制时间、处理数据。

在计算机体系结构中,CPU 是对计算机的所有硬件资源(如存储器、输入输出单元) 进行控制调配、执行通用运算的核心硬件单元。CPU 是计算机的运算和控制核心。计算机系统中所有软件层的操作,最终都将通过指令集映射为CPU的操作。

‘肆’ 如何实现自己的linux container

这几个flag 可以在调用clone时候作为参数传入,从而实现namespace的隔离,
从这个角度来说,container跟主要是进程角度的隔离,而不是传统的虚拟机,
因为它底层用用的同一个内核来调度。
cgroup 是linux 内核的另外一个控制和隔离进程的特性,他分为cpu ,memory,net,io等几个子系统,从而实现对进程cpu,内存,磁盘,网络等资源使用的控制。

制作自己容器,需要一个image ,可以从网上下一个,也可以自己制作,制作很简单,新装一个操作系统,安装一些需要用到的软件包,然后用tar 制作 / 目录下的压缩包,去掉一些虚拟文件系统的文件,本文用的是自己制作的centos 6.5 的image。

容器实现过程可以归纳为
1, 用clone系统调用 创建子进程,传入namespace的那几个参数,实现namespace的隔离
2, 父进程中创建veth pair ,一个veth在自己的namespace,将另一个设置为子进程的namespace,实现container和宿主机的网络通信
3, 父进程创建cgroup memory和cpuset子系统,将子进程attach到cgroup子系统上,实现container 的资源限制和隔离
4, 子进程在自己的namespace里,设置主机名,mount proc虚拟文件系统,设置veth ip,chroot到centos 6镜像的位置, 最终将进程镜像替换成/bin/bash
5, 父进程调用waitpid 等待子进程退出

‘伍’ 如何限制容器内存大小

container中的free命令,看的是Host上的内存。 要查看container的内存限制用这个: cat /cgroup/memory/lxc/{full_container_id}/memory.limit_in_bytes /cgroup/下还可以查看其它资源的限制: ll /cgroup/

‘陆’ 如何分配docker容器的系统资源

最近在和阿里的一些同事谈起使用Docker部署Java应用的场景,其中一个大家普遍关心的问题就是如何设置容器中JVM的内存限制。 如果使用官方的Java镜像,或者基于Java镜像构建的Docker镜像,都可以通过传递 JAVA_OPTS 环境变量来轻松地设置JVM的内存参数。比如,对于官方Tomcat 镜像,我们可以执行下面命令来启动一个最大内存为512M的tomcat实例 docker run --rm -e JAVA_OPTS='-Xmx512m' tomcat:8 在日志中,我们可以清楚地发现设置已经生效 “Command line argument: -Xmx512m” 02-Apr-2016 12:46:26/denverdino/tomcat:8-autoheap ,其源代码可以从Github 获得。它基于Docker官方Tomcat镜像创建,它的启动脚本会检查CGroup中内存限置,并计算JVM最大Heap size来传递给Tomcat。其代码如下 #!/bin/bash limit_in_bytes=$(cat /sys/fs/cgroup/memory/memory/denverdino/tomcat:8-autoheap 通过下列命令,从日志中我们可以检测到相应的JVM参数已经被设置成 448MB (512-64) docker logs test ... 02-Apr-2016 14:18:09.870 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Xmx448m ... 我们也可以方便的调整Java应用的内存. Docker 1.10提供了对容器资源限制的动态修改能力。但是由于JVM无法感知容器资源修改,我们依然需要重启tomcat来变更JVM的内存设置,例如,我们可以通过下面命令把容器内存限制调整到1GB docker update -m 1024m test docker restart test 再次检查日志,相应的JVM Heap Size最大值已被设置为960MB docker logs test ... 02-Apr-2016 14:21:07.644 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Xmx960m ...

‘柒’ docker可以控制很多资源,目前还不能对如下哪些资源进行隔离

不能对硬盘I/O读写进行隔离。

硬盘I/O是指硬盘的输入和输出(Input和Output的缩写)。就是发指令,从磁盘读取某段扇区的内容。指令一般是通知磁盘开始扇区位置,然后给出需要从这个初始扇区往后读取的连续扇区个数,同时给出动作是读,还是写。

对于磁盘I/O资源来说,考量的参数是容量和读写速度,因此对容器的磁盘限制也应该从这两个维度出发。目前Docker 支持对磁盘的读写速度进行限制,但是并没有方法能限制容器能使用的磁盘容量(一旦磁盘 mount 到容器里,容器就能够使用磁盘的所有容量)。

(7)容器如何做资源限制扩展阅读

引擎局限性:

1、Docker是基于Linux 64bit的,无法在32bit的linux/Windows/unix环境下使用。

2、LXC是基于cgroup等linux kernel功能的,因此container的guest系统只能是linux base的。

3、隔离性相比KVM之类的虚拟化方案还是有些欠缺,所有container公用一部分的运行库。

4、网络管理相对简单,主要是基于namespace隔离。

5、cgroup的cpu和cpuset提供的cpu功能相比KVM的等虚拟化方案相比难以度量(所以dotcloud主要是按内存收费)。

6、Docker对disk的管理比较有限。

7、container随着用户进程的停止而销毁,container中的log等用户数据不便收集。

‘捌’ 如何使用 Docker 来限制 CPU,内存和 IO等资源

Docker 作为容器的管理者,自然提供了控制容器资源的功能。正如使用内核的 namespace 来做容器之间的隔离,Docker 也是通过内核的 cgroups 来做容器的资源限制。

‘玖’ 如何实时查看Docker容器占用的CPU,内存状态

Docker
作为容器的管理者,自然提供了控制容器资源的功能。
正如使用内核的
namespace
来做容器之间的隔离,Docker
也是通过内核的
cgroups
来做容器的资源限制。

‘拾’ 如何设置Docker容器中Java应用的内存限制

最近在和阿里的一些同事谈起使用Docker部署Java应用的场景,其中一个大家普遍关心的问题就是如何设置容器中JVM的内存限制。

如果使用官方的Java镜像,或者基于Java镜像构建的Docker镜像,都可以通过传递 JAVA_OPTS 环境变量来轻松地设置JVM的内存参数。比如,对于官方Tomcat 镜像,我们可以执行下面命令来启动一个最大内存为512M的tomcat实例
docker run --rm -e JAVA_OPTS='-Xmx512m' tomcat:8

在日志中,我们可以清楚地发现设置已经生效 “Command line argument: -Xmx512m”
02-Apr-2016 12:46:26.970 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version: Apache Tomcat/8.0.32
02-Apr-2016 12:46:26.974 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Feb 2 2016 19:34:53 UTC
02-Apr-2016 12:46:26.975 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server number: 8.0.32.0
02-Apr-2016 12:46:26.975 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
02-Apr-2016 12:46:26.975 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 4.1.19-boot2docker
02-Apr-2016 12:46:26.975 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
02-Apr-2016 12:46:26.975 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /usr/lib/jvm/java-7-openjdk-amd64/jre
02-Apr-2016 12:46:26.976 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 1.7.0_95-b00
02-Apr-2016 12:46:26.976 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Oracle Corporation
02-Apr-2016 12:46:26.977 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
02-Apr-2016 12:46:26.977 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
02-Apr-2016 12:46:26.978 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
02-Apr-2016 12:46:26.978 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
02-Apr-2016 12:46:26.978 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Xmx512m
...

然而在Docker集群上部署运行Java容器应用的时候,仅仅对JVM的heap参数设置是不够的,我们还需要对Docker容器的内存资源进行限制:

1. 限制容器使用的内存的最大量,防止对系统或其他应用造成伤害

2. 能够将Docker容器调度到拥有足够空余的内存的节点,从而保证应用的所需运行资源

关于容器的资源分配约束,Docker提供了相应的启动参数

对内存而言,最基本的就是通过 -m参数来约束容器使用内存的大小
-m, --memory=""
Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M.

那么问题就来了,为了正确设置Docker容器内存的大小,难道我们需要同时传递容器的内存限制和JAVA_OPTS环境变量吗? 如下所示:
docker run --rm -m 512m -e JAVA_OPTS='-Xmx512m' tomcat:8

这个方法有两个问题
1. 需要管理员保证容器内存和JVM内存设置匹配,否则可能引发错误
2. 当对容器内存限制调整时,环境变量也需要重新设定,这就需要重建一个新的容器

是否有一个方法,可以让容器内部的JVM自动适配容器的内存限制?这样可以采用更加统一的方法来进行资源管理,简化配置工作。

大家知道Docker是通过CGroup来实现资源约束的,自从1.7版本之后,Docker把容器的local cgroups以只读方式挂载到容器内部的文件系统上,这样我们就可以在容器内部,通过cgroups信息来获取系统对当前容器的资源限制了。

我创建了一个示例镜像 registry.aliyuncs.com/denverdino/tomcat:8-autoheap
,其源代码可以从Github 获得。它基于Docker官方Tomcat镜像创建,它的启动脚本会检查CGroup中内存限置,并计算JVM最大Heap size来传递给Tomcat。其代码如下
#!/bin/bash
limit_in_bytes=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)

# If not default limit_in_bytes in cgroup
if [ "$limit_in_bytes" -ne "9223372036854771712" ]
then
limit_in_megabytes=$(expr $limit_in_bytes \/ 1048576)
heap_size=$(expr $limit_in_megabytes - $RESERVED_MEGABYTES)
export JAVA_OPTS="-Xmx${heap_size}m $JAVA_OPTS"
echo JAVA_OPTS=$JAVA_OPTS
fi

exec catalina.sh run

说明:

为了监控,故障排查等场景,我们预留了部分内存(缺省64M),其余容器内存我们都分配给JVM的堆。
这里没有对边界情况做进一步处理。在生产系统中需要根据情况做相应的设定,比如最大的堆大小等等。

现在我们启动一个tomcat运行在512兆的容器中
docker run -d --name test -m 512m registry.aliyuncs.com/denverdino/tomcat:8-autoheap

通过下列命令,从日志中我们可以检测到相应的JVM参数已经被设置成 448MB (512-64)
docker logs test

...
02-Apr-2016 14:18:09.870 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Xmx448m
...

我们也可以方便的调整Java应用的内存.

Docker 1.10提供了对容器资源限制的动态修改能力。但是由于JVM无法感知容器资源修改,我们依然需要重启tomcat来变更JVM的内存设置,例如,我们可以通过下面命令把容器内存限制调整到1GB
docker update -m 1024m test
docker restart test

再次检查日志,相应的JVM Heap Size最大值已被设置为960MB
docker logs test

...
02-Apr-2016 14:21:07.644 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Xmx960m
...