我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Docker容器实现原理及容器隔离性踩坑介绍

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Docker容器实现原理及容器隔离性踩坑介绍


正如Docker官方的口号:“Build once,Run anywhere,Configure once,Run anything”,Docker被贴上了如下标签:轻巧、秒级启动、版本管理、可移植性等等,这些优点让它出现之初就收到极大的关注。现在,Docker已经不仅仅是开发测试阶段使用的工具,大家已经在生产环境中大量使用。今天我们给大家介绍关于容器隔离性的一个“坑”。在此之前,我们先来回顾一下Docker容器的底层实现原理。

容器底层实现

Docker容器实现原理及容器隔离性踩坑介绍我们都知道,虚拟机与容器的底层实现原理是不同的,正如下图对比:

Docker容器实现原理及容器隔离性踩坑介绍

虚拟机实现资源隔离的方法是利用一个独立的Guest OS,并利用Hypervisor虚拟化CPU、内存、IO设备等实现的。例如,为了虚拟化内存,Hypervisor会创建一个shadow page table,正常情况下,一个page table可以用来实现从虚拟内存到物理内存的翻译。相比虚拟机实现资源和环境隔离的方案,Docker就显得简练很多,它不像虚拟机一样重新加载一个操作系统内核,引导、加载操作系统内核是一个比较耗时而又消耗资源的过程,Docker是利用Linux内核特性实现的隔离,运行容器的速度几乎等同于直接启动进程。

关于Docker实现原理,简单总结如下:

  • 使用Namespaces实现了系统环境的隔离,Namespaces允许一个进程以及它的子进程从共享的宿主机内核资源(网络栈、进程列表、挂载点等)里获得一个仅自己可见的隔离区域,让同一个Namespace下的所有进程感知彼此变化,对外界进程一无所知,仿佛运行在一个独占的操作系统中;

  • 使用CGroups限制这个环境的资源使用情况,比如一台16核32GB的机器上只让容器使用2核4GB。使用CGroups还可以为资源设置权重,计算使用量,操控任务(进程或线程)启停等;

  • 使用镜像管理功能,利用Docker的镜像分层、写时复制、内容寻址、联合挂载技术实现了一套完整的容器文件系统及运行环境,再结合镜像仓库,镜像可以快速下载和共享,方便在多环境部署。

正因为Docker不像虚机虚拟化一个Guest OS,而是利用宿主机的资源,和宿主机共用一个内核,所以会存在下面问题:

注意:存在问题并不一定说就是安全隐患,Docker作为最重视安全的容器技术之一,在很多方面都提供了强安全性的默认配置,其中包括:容器root用户的 Capability 能力限制,Seccomp系统调用过滤,Apparmor的 MAC 访问控制,ulimit限制,镜像签名机制等。

Docker是利用CGroups实现资源限制的,只能限制资源消耗的最大值,而不能隔绝其他程序占用自己的资源;

Namespace的6项隔离看似完整,实际上依旧没有完全隔离Linux资源,比如/proc 、/sys 、/dev/sd*等目录未完全隔离,SELinux、time、syslog等所有现有Namespace之外的信息都未隔离。

容器隔离性踩过的坑

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,其他应用取到的可能也不正确,需要进行测试。

这些问题的本质都一样,在Linux环境,很多命令都是通过读取/proc 或者 /sys 目录下文件来计算资源使用情况,以free命令为例:

lynzabo@ubuntu:~$ strace freeexecve("/usr/bin/free", ["free"], []) = 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

对于JavaSE8(<8u131)版本,手动指定最大堆大小。

docker run的时候通过环境变量传参确切限制最大heap大小:

docker run -d -m 800M -e JAVA_OPTIONS='-Xmx300m' openjdk:8-jdk-alpine

对于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 

保证系统IO不会出现问题,这也是为什么在容器里面取到的是宿主机资源原因。

了解了这些,那么我们在容器里该如何获取实例资源使用情况呢,下面介绍两个方法。

从CGroups中读取

Docker容器实现原理及容器隔离性踩坑介绍

Docker 在 1.8 版本以后会将分配给容器的CGroups信息挂载进容器内部,容器里面的程序可以通过解析CGroups信息获取到容器资源信息。

在容器里面可以运行mount 命令查看这些挂载记录

...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。

Kubernetes 1.10以后支持为Pod指定固定CPU编号,我们在这里不详细介绍,就以常规的计算资源管理为主,简单讲一下以Kubernetes作为编排引擎,容器的CGroups资源限制情况:

1、读取容器CPU核数

# 这个值除以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。

4、获取容器磁盘I/O

~ # 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~ #

如果你对从容器中读取CGroups感兴趣,可以点击最下方“阅读原文”了解docker stats源码实现。

使用LXCFS

Docker容器实现原理及容器隔离性踩坑介绍

由于习惯性等原因,在容器中使用top、free等命令仍然是一个较为普遍存在的需求,但是容器中的/proc、/sys目录等还是挂载的宿主机目录,有一个开源项目:LXCFS。LXCFS是基于FUSE实现的一套用户态文件系统,使用LXCFS,让你在容器里面继续使用top、free等命令变成了可能。但需要注意,LXCFS可能会存在很多问题,建议在线上环境先不要使用。

总结

容器给大家带来了很多便利,很多公司已经或正在把业务往容器上迁移。在迁移过程中,需要清楚上面介绍的这个问题是不是会影响应用的正常运行,并采取相应的办法绕过这个坑。

这篇文章的分享就到这里,希望对大家有所帮助。

Docker容器实现原理及容器隔离性踩坑介绍

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Docker容器实现原理及容器隔离性踩坑介绍

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Docker容器实现原理及容器隔离性踩坑介绍

正如Docker官方的口号:“Build once,Run anywhere,Configure once,Run anything”,Docker被贴上了如下标签:轻巧、秒级启动、版本管理、可移植性等等,这些优点让它出现之初就收到极大的关
2023-06-04

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录