-
使用Namespaces实现了系统环境的隔离,Namespaces允许一个进程以及它的子进程从共享的宿主机内核资源(网络栈、进程列表、挂载点等)里获得一个仅自己可见的隔离区域,让同一个Namespace下的所有进程感知彼此变化,对外界进程一无所知,仿佛运行在一个独占的操作系统中;
-
使用CGroups限制这个环境的资源使用情况,比如一台16核32GB的机器上只让容器使用2核4GB。使用CGroups还可以为资源设置权重,计算使用量,操控任务(进程或线程)启停等;
-
使用镜像管理功能,利用Docker的镜像分层、写时复制、内容寻址、联合挂载技术实现了一套完整的容器文件系统及运行环境,再结合镜像仓库,镜像可以快速下载和共享,方便在多环境部署。
-
在Docker容器中执行top、free等命令,会发现看到的资源使用情况都是宿主机的资源情况,而我们需要的是这个容器被限制了多少CPU,内存,当前容器内的进程使用了多少;
-
在容器里修改/etc/sysctl.conf,会收到提示”sysctl: error setting key ‘net.ipv4….’: Read-only file system”;
-
程序运行在容器里面,调用API获取系统内存、CPU,取到的是宿主机的资源大小;
-
对于多进程程序,一般都可以将worker数量设置成auto,自适应系统CPU核数,但在容器里面这么设置,取到的CPU核数是不正确的,例如Nginx,其他应用取到的可能也不正确,需要进行测试。
lynzabo@ubuntu:~$ strace freeexecve("/usr/bin/free", ["free"], [/* 66 vars */]) = 0...statfs("/sys/fs/selinux", 0x7ffec90733a0) = -1 ENOENT (No such file or directory)statfs("/selinux", 0x7ffec90733a0) = -1 ENOENT (No such file or directory)open("/proc/filesystems", O_RDONLY) = 3...open("/sys/devices/system/cpu/online", O_RDONLY|O_CLOEXEC) = 3...open("/proc/meminfo", O_RDONLY) = 3+++ exited with 0 +++lynzabo@ubuntu:~$
包括各个语言,比如Java,NodeJS,这里以NodeJS为例:
const os = require('os');const total = os.totalmem();const free = os.freemem();const usage = (free - total) / total * 100;
NodeJS的实现,也是通过读取 /proc/meminfo文件获取内存信息。Java也是类似。
我们都知道,JVM默认的最大Heap大小是系统内存的1/4,假若物理机内存为10G,如果你不手动指定Heap大小,则JVM默认Heap大小就为2.5G。JavaSE8(<8u131)版本前还没有针对在容器内执行高度受限的Linux进程进行优化,JDK1.9 以后开始正式支持容器环境中的CGroups内存限制,JDK1.10 这个功能已经默认开启,可以查看相关Issue(Issue地址:https://bugs.openjdk.java.net/browse/JDK-8146115)。熟悉JVM内存结构的人都清楚,JVM
Heap是一个只增不减的内存模型,Heap的内存只会往上涨,不会下降。在容器里面使用Java,如果为JVM未设置Heap大小,Heap取得的是宿主机的内存大小,当Heap的大小达到容器内存大小时候,就会触发系统对容器OOM,Java进程会异常退出。常见的系统日志打印如下:
memory: usage 2047696kB, limit 2047696kB, failcnt 23543memory+swap: usage 2047696kB, limit 9007199254740991kB, failcnt 0......Free swap = 0kBTotal swap = 0kB......Memory cgroup out of memory: Kill process 18286 (java) score 933 or sacrifice child
对于Java应用,下面提供两个办法来设置Heap。
1、对于 JavaSE8(<8u131)版本,手动指定最大堆大小。
docker run的时候通过环境变量传参确切限制最大heap大小:
docker run -d -m 800M -e JAVA_OPTIONS='-Xmx300m' openjdk:8-jdk-alpine
2、对于JavaSE8(>8u131)版本,可以使用上面手动指定最大堆大小,也可以使用下面办法,设置自适应容器内存限制。
docker run的时候通过环境变量传参确切限制最大heap大小:
docker run -d -m 800M -e JAVA_OPTIONS='-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1' openjdk:8-jdk-alpine
对比这两种方式,第一种方式缺乏灵活性,在确切知道内存限制大小的情况下可以使用,第二种方法必须在JavaSE8(>8u131)版本才能使用。
当你启动一个容器时候,Docker会调用libcontainer实现对容器的具体管理,包括创建UTS、IPS、Mount等Namespace实现容器之间的隔离和利用CGroups实现对容器的资源限制,在其中,Docker会将宿主机一些目录以只读方式挂载到容器中,其中包括/proc、/dev、/dev/shm、/sys目录,同时还会建立以下几个链接:
-
/proc/self/fd->/dev/fd
-
/proc/self/fd/0->/dev/stdin
-
/proc/self/fd/1->/dev/stdout
-
/proc/self/fd/2->/dev/stderr
...cgroup on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)cgroup on /sys/fs/cgroup/cpu type cgroup (ro,nosuid,nodev,noexec,relatime,cpu)cgroup on /sys/fs/cgroup/cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpuacct)cgroup on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)cgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)cgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)cgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)cgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)cgroup on /sys/fs/cgroup/hugetlb type cgroup (ro,nosuid,nodev,noexec,relatime,hugetlb)...
在这里我们不讲解CGroups对CPU和内存的限制都有哪些,只介绍基于Kubernetes编排引擎下的计算资源管理,对容器CGroups都做了哪些支持:
-
当为Pod指定了requests,其中 requests.cpu 会作为--cpu-shares 参数值传递给 docker run 命令,当一个宿主机上有多个容器发生CPU资源竞争时这个参数就会生效,参数值越大,越容易被分配到CPU,requests.memory 不会作为参数传递给Docker,这个参数在Kubernetes的资源QoS管理时使用;
-
当为Pod指定了limits,其中 limits.cpu 会作为 --cpu-quota 参数的值传递给 docker run 命令,docker run 命令中另外一个参数 --cpu-period 默认设置为100000,通过这两个参数限制容器最多能够使用的CPU核数,limits.memory 会作为 --memory 参数传递给docker run 命令,用来限制容器内存,目前Kubernetes不支持限制Swap大小,建议在部署Kubernetes时候禁用Swap。
# 这个值除以100000得到的就是容器核数~ # cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us 400000
2、获取容器内存使用情况(USAGE / LIMIT)
~ # cat /sys/fs/cgroup/memory/memory.usage_in_bytes 4289953792~ # cat /sys/fs/cgroup/memory/memory.limit_in_bytes 4294967296
将这两个值相除得到的就是内存使用百分比。
3、获取容器是否被设置了OOM,是否发生过OOM
~ # cat /sys/fs/cgroup/memory/memory.oom_control oom_kill_disable 0under_oom 0~ #
这里需要解释一下:
-
oom_kill_disable默认为0,表示打开了oom killer,就是当内存超时会触发kill进程。可以在使用docker run时候指定disable oom,将此值设置为1,关闭oom killer;
-
under_oom 这个值仅仅是用来看的,表示当前的CGroups的状态是不是已经oom了,如果是,这个值将显示为1。
~ # cat /sys/fs/cgroup/blkio/blkio.throttle.io_service_bytes253:16 Read 20015124480253:16 Write 24235769856253:16 Sync 0253:16 Async 44250894336253:16 Total 44250894336Total 44250894336
5、获取容器虚拟网卡入/出流量
~ # cat /sys/class/net/eth0/statistics/rx_bytes 10167967741~ # cat /sys/class/net/eth0/statistics/tx_bytes 15139291335
/proc/cpuinfo/proc/diskstats/proc/meminfo/proc/stat/proc/swaps/proc/uptime
如果命令是通过解析这些文件实现,那么在容器里面可以继续使用,否则只能通过读取CGroups获取资源情况。
总结
容器给大家带来了很多便利,很多公司已经或正在把业务往容器上迁移。在迁移过程中,需要清楚上面介绍的这个问题是不是会影响应用的正常运行,并采取相应的办法绕过这个坑。
如果您觉得不错,请别忘了转发、分享、点赞让更多的人去学习, 您的举手之劳,就是对小编最好的支持,非常感谢!
小编有话说
积极的人在每一次忧患中都看到一个机会,而消极的人则在每个机会都看到某种忧患。
目前10000+人已关注加入我们