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

  • 时间:
  • 浏览:4
  • 来源:小贝博客 - 专注共享吾皇千睡博客资源

本文讲述了 关于容器隔离性的另1个“坑”

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

容器底层实现

有过后 人都知道,虚拟机与容器的底层实现原理是不同的,正如下图对比:

虚拟机实现资源隔离的法律依据 是利用另1个独立的 Guest OS,并利用 Hypervisor 虚拟化CPU、内存、IO 设备等实现的。类似于,为了虚拟化内存,Hypervisor 会创建另1个shadow page table,正常情况下,另1个 page table 后该 用来实现从虚拟内存到物理内存的翻译。相比虚拟机实现资源和环境隔离的方案,Docker 就显得简练什么都,它不像虚拟机一样重新加载另1个操作系统内核,引导、加载操作系统内核是另1个比较耗时而又消耗资源的过程,Docker 是利用 Linux 内核形态实现的隔离,运行容器的速率几乎等同于直接启动守护任务管理器。

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

  • 使用 Namespaces 实现了系统环境的隔离,Namespaces 允许另1个守护任务管理器以及它的子守护任务管理器从共享的宿主机内核资源(网络栈、守护任务管理器列表、挂载点等)里获得另1个仅被委托人可见的隔离区域,让同另1个 Namespace 下的所有守护任务管理器感知彼此变化,对外界守护任务管理器一无所知,仿佛运行在另1个独占的操作系统中;
  • 使用 CGroups 限制有过后 环境的资源使用情况,比如一台16核32GB的机器上只让容器使用2核4GB。使用 CGroups 还后该 为资源设置权重,计算使用量,操控任务(守护任务管理器或守护任务管理器)启停等;
  • 使用镜像管理功能,利用 Docker 的镜像分层、写时基因重组、内容寻址、联合挂载技术实现了一套完整篇 的容器文件系统及运行环境,再结合镜像仓库,镜像后该 快速下载和共享,方便在多环境部署。

正肯能 Docker 不像虚机虚拟化另1个 Guest OS,有过后利用宿主机的资源,和宿主机共用另1个内核,什么都会存在下面问题报告 :

注意:缺乏报告 固然一定说有过后安全隐患,Docker 作为最重视安全的容器技术之一,在什么都方面都提供了强安全性的默认配置,其中包括:容器 root 用户的 Capability 能力限制,Seccomp 系统调用过滤,Apparmor 的 MAC 访问控制,ulimit 限制,镜像签名机制等。

1、Docker 是利用 CGroups 实现资源限制的,不后该 限制资源消耗的最大值,而不后该 隔绝有过后 守护任务管理器占用被委托人的资源;

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

容器隔离性踩过的坑

在使用容器的前一天,有过后 人很肯能遇到过这有多少问题报告 :

1、在 Docker 容器中执行 top、free 等命令,会发现想看 的资源使用情况是是否是是宿主机的资源情况,而有过后 人后该 的是有过后 容器被限制了有多少 CPU,内存,当前容器内的守护任务管理器使用了有多少;

2、在容器里修改/etc/sysctl.conf,会收到提示”sysctl: error setting key ‘net.ipv4….’: Read-only file system”;

3、守护任务管理器运行在容器上端,调用API获取系统内存、CPU,取到的是宿主机的资源大小;

4、对于多守护任务管理器守护任务管理器,一般都后该 将 worker 数量设置成 auto,自适应系统CPU核数,但在容器上端没法设置,取到的CPU核数是不正确的,类似于 Nginx,有过后 应用取到的肯能有过后正确,后该 进行测试。

有有哪些问题报告 的本质都一样,在 Linux 环境,什么都命令是是否是是通过读取/proc 肯能 /sys 目录下文件来计算资源使用情况,以free命令为例:

lynzabo@ubuntu:~$ strace free

execve("/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 * 3000;

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 是另1个只增不减的内存模型,Heap 的内存只会往上涨,不多下降。在容器上端使用Java,肯能为 JVM 未设置 Heap 大小,Heap 取得的是宿主机的内存大小,当 Heap 的大小达到容器内存大小前一天,就会触发系统对容器OOM,Java 守护任务管理器会异常退出。常见的系统日志打印如下:

memory: usage 2047696kB, limit 2047696kB, failcnt 23543

memory+swap: usage 2047696kB, limit 9007199254740991kB, failcnt 0

......

Free swap = 0kB

Total swap = 0kB

......

Memory cgroup out of memory: Kill process 18286 (java) score 933 or sacrifice child

对于 Java 应用,下面提供另1个法律依据 来设置 Heap

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

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

docker run -d -m 30000M -e JAVA_OPTIONS='-Xmx3000m' openjdk:8-jdk-alpine

2、对于 JavaSE8(>8u131)版本,后该 使用上端手动指定最大堆大小,也后该 使用下面法律依据 ,设置自适应容器内存限制。

docker run 的前一天通过环境变量传参确切限制最大 heap 大小

docker run -d -m 30000M -e JAVA_OPTIONS='-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1' openjdk:8-jdk-alpine

对比这人种法律依据 ,第一种法律依据 缺乏灵活性,在确切知道内存限制大小的情况下后该 使用,第二种法律依据 后该 在 JavaSE8(>8u131) 版本也能使用。

当你启动另1个容器前一天,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 不多出显问题报告 ,这也是为有哪些在容器上端取到的是宿主机资源原因。

了解了有有哪些,没法有过后 人在容器里该怎样才能获取实例资源使用情况呢,下面介绍另1个法律依据 。

从CGroups中读取

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 命令,当另1个宿主机上有多个容器存在 CPU 资源竞争时有过后 参数就会生效,参数值越大,越容易被分配到 CPU,requests.memory 不多作为参数传递给 Docker,有过后 参数在 Kubernetes 的资源 QoS 管理时使用;
  • 当为Pod指定了 limits,其中limits.cpu会作为 –cpu-quota 参数的值传递给docker run 命令,docker run 命令中另外另1个参数–cpu-period 默认设置为3000000,通过这另1个参数限制容器最多也能使用的CPU核数,limits.memory 会作为–memory 参数传递给docker run 命令,用来限制容器内存,目前Kubernetes 不支持限制 Swap 大小,建议在部署 Kubernetes 前一天禁用 Swap.

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

1、读取容器 CPU 核数

# 有过后

值除以3000000得到的有过后容器核数

~ # cat  /sys/fs/cgroup/cpu/cpu.cfs_quota_us

300000

2、获取容器内存使用情况(USAGE / LIMIT)

~ # cat /sys/fs/cgroup/memory/memory.usage_in_bytes

4289953792

~ # cat /sys/fs/cgroup/memory/memory.limit_in_bytes

4294967296

将这另1个值相除得到的有过后内存使用百分比。

3、获取容器是是是否是是被设置了 OOM,是是是否是是存在过 OOM

~ # cat /sys/fs/cgroup/memory/memory.oom_control

oom_kill_disable 0

under_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_bytes

253:16 Read 30001512443000

253:16 Write 24235769856

253:16 Sync 0

253:16 Async 4423000894336

253:16 Total 4423000894336

Total 4423000894336

5、获取容器虚拟网卡入/出流量

~ # cat /sys/class/net/eth0/statistics/rx_bytes

10167967741

~ # cat /sys/class/net/eth0/statistics/tx_bytes

15139291335

~ #

使用LXCFS

肯能习惯性等原因,在容器中使用 top、free 等命令仍然是另1个较为普遍存在的需求,有过后容器中的/proc、/sys目录等还是挂载的宿主机目录,有另1个开源项目:LXCFS.LXCFS 是基于 FUSE 实现的一套用户态文件系统,使用 LXCFS,要我在容器上端继续使用 top、free 等命令变成了肯能。但后该 注意,LXCFS 肯能会存在什么都问题报告 ,建议在线上环境先固然使用。

总结

容器给有过后 人带来了什么都便利,什么都公司肯能或正在把业务往容器上迁移。在迁移过程中,后该 清楚上端介绍的有过后 问题报告 是是是否是是会影响应用的正常运行,并采取相应的法律依据 绕过有过后 坑。

这篇文章的分享就到这里,希望对有过后 人有所帮助。

本文由

小米云技术

发布在

ITPUB

,转载此文请保持文章完整篇 性,并请附上文章来源(ITPUB)及本页链接。

原文链接:http://www.itpub.net/2019/07/18/2444/

小米云平台部,主要分享云存储、云计算、系统、网络、运维、私有云、安全、数据库、内核等内容,欢迎感兴趣的有过后 有过后 人关注!