docker背后的内核知识

Posted diagnose

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了docker背后的内核知识相关的知识,希望对你有一定的参考价值。

在上一篇提到安装docker需要至少需要linux内核版本3.10以上,且需要支持cgroups和namespace功能。这是因为docker的容器实现本质还是 host上的进程。

Docker通过namespace实施了资源隔离,且通过了cgroups实施了资源约束,通过写时复制(copy-on-write)机制实现了高效的文件操作。

下面将详细介绍一下这三者。

 

一、写时复制(copy-on-write,cow)机制:

参考wiki:https://en.wikipedia.org/wiki/Copy-on-write以及http://www.jianshu.com/p/b5b95a710fec

总结起来就是,通过浅拷贝(shallow copy)复制引用而避免复制值,当需要进行写入操作时,首先进行值拷贝,再对拷贝后的值执行写入操作,这样减少了无谓的复制耗时。

特点有

  • 读取安全(但是不保证缓存一致性),写入安全(代价是加了锁,而且需要全量复制)
  • 不建议用于频繁读写场景下,全量复制很容易造成GC停顿,因此建议使用平时的ConcurrentXX包来实现。
  • 适用于对象空间占用大,修改次数少,而且对数据实效性要求不高的场景。

它适用于

并发读取场景,即多个线程/进程可以通过对一份相同快照,去处理实效性要求不是很高但是仍然要做的业务(比如实现FS\DB备份、日志、分布式路由)

 

二、namespace实施资源隔离

首先了解一下namespace能提供哪些资源隔离:

Namespace

系统调用参数

隔离内容

UTS

CLONE_NEWUTS

主机名与域名

IPC

CLONE_NEWIPC

信号量、消息队列和共享内存

PID

CLONE_NEWPID

进程编号

Network

CLONE_NEWNET

网络设备、网络栈、端口等

Mount

CLONE_NEWNS

挂载点(文件系统)

User

CLONE_NEWUSER

用户和用户组

 

 

 

 

 

 

 

 

 

 

 

 

 

 

来分别理解一下这些资源隔离主要产生什么作用,然后怎样实施。概括地讲,容器作为一个独立的存在,它需要一些基本属性来标识自己,所以一个主机名、域名是必不可少的,

除此之外容器不能作为一个孤儿独立存在,需要提供一个连接点,那么自然而然的网络相关如IP,端口,路由等也需要添加进来,容器实施隔离后容器可以作为一个独立的os在其上跑各种进程,线程,这个时候用于标识PID的隔离就出现了,进程线程之间的通信要用到的信号量、消息队列、管道、共享内存等等也需要隔离出来,除此之外像文件系统也可以独立出来。那么这个容器就可以视作一个独立的os。

我说了一大堆隔离措施和效果其实并没有什么鸟用,它应该由需求出发来进行驱动理解。首先就是为什么要实施隔离?个人感觉还是和开篇讲的一样,隔离技术的诞生不是由于资源的富余,而是资源的受限导致了开发者想尽办法用来规划各个进程的使用空间,但是只规划宿主机上的资源实际上还是不够安全,举个例子,端口冲突或者僵尸进程等等,都会导致宿主机上的其他进程受到影响,所以在这个基础上,开发者考虑搞一个完全小型的os,它不依赖于操作系统发行版,还能统一调度资源,安全性能也有足够保障,大大的提高了资源的使用效率和灵活性。现在这六项隔离措施事实上和一个os所需的几个基本要素比较贴近,随着技术的发展,这些隔离措施也越来越成熟安全,需要注意的是用户和群组这个隔离目前实施起来还有点问题,此处先mark一下。

 

那么接下来就介绍一下怎样通过namespace的API进行资源隔离,namespace的API包括clone()、setns()以及unshare(),还有/proc下的部分文件:

1)clone()

它主要用来创建一个新的独立的namespace进程,也是最常见的做法。它实际上是Linux调用fork()的一种实现方式,通过表格里的系统调用参数来控制实现哪些隔离功能。

2)/proc/<pid>/ns文件

可以在ns文件目录下看到指定pid使用的哪些隔离功能,这些文件在内核3.8版本之后以软连接的方式存在,如果pid下使用的隔离功能的namespace号相同,则意味着他们具有相同的命名空间。那么可以通过这个特性观察到docker进程下的若干容器使用的ns。此外,/proc/<pid>/ns软连接文件的另一个作用就是,一旦这个link文件被打开,只要打开的文件描述符fd存在,即使该ns下的所有进程以及全部结束,该ns也会一直存在。该特性常被docker用于绑定已有的网络和卷操作,即通过文件描述符加入一个已有的ns。

除了文件描述符,将ns目录下的文件挂载起来也能达到同样的效果。

3)sentns()

这个方法主要用于加入一个已有的ns。

上面提到,将一个已经结束全部结束进程的ns挂载起来,也可以在后续将其它进程加入进来。它最常用在docker exec命令,这个命令会进入容器内部,并可在容器内部执行新的命令。当然了,它需要和bash或sh结合起来,然后在新加入的ns中运行shell并执行命令。

4)unshare()和fork()(fork属于系统调用函数)

它的作用是在原先的进程上隔离出一个ns出来,而不需要额外起一个新的进程,由于docker没有直接使用这个玩意,这边不再介绍,深入了解可以参考链接:

http://www.man7.org/linux/man-pages/man2/unshare.2.html

https://en.wikipedia.org/wiki/Fork_(system_call)

 

接下来将分别介绍六大ns以及它们在docker常用的使用模式,由于网上已经有一些代码描述了实施这样隔离功能的效果,这边不再贴代码描述结果了。我这里主要提及一些它的使用经验和注意事项,还有表征效果。

 

1)UTS

Unix Time-sharing System为容器提供了主机名和域名的隔离,一般说来,容器的主机名等同于容器ID的短号(据说是全球唯一),此外它的域名解析服务文件resolv.conf一般等同于宿主机的域名解析,如果域名解析服务在容器启动时未特别指定,则按照默认策略实施域名解析。

但需要注意的是由于容器本身的一些特性,容器在进行域名解析的时候常常遇到一些困难,以glibc主导的linux则可以按照nsswitch.conf里面hosts字段配置的域名解析优先级进行解析,它在解析resolv.conf的nameserver时是按照从上往下依次解析的,如果解析失败则会耗费非常多的时间,而非glibc主导的linux,如musl-libc主导的alpine linux则无法使用nsswitch.conf文件进行配置域名解析的优先级,且在解析resolv.conf时是并行解析的,只返回最先解析好的结果。

另外,如果是python,并使用了eventlet某些特殊版本,它会在绿化后优先使用resolv.conf进行域名解析继而使用/etc/hosts文件进行域名解析,在socket连接时的进行的域名解析则会被eventlet的greendns接管,如果域名解析失败,则会导致eventlet的hub sleep 60s继而采用 hosts解析,对整个消息处理流程是极为不利的,如果要搞定这个问题,需要eventlet某些版本,在编写此文档时,最好使用0.21.0版本中EVENTLET_NO_GREENDNS选项配置为yes,就能避免域名解析时间过长的bug。

 

 


以上是关于docker背后的内核知识的主要内容,如果未能解决你的问题,请参考以下文章

docker背后的内核知识

《Linux内核 核心知识全解析(完)》

Docker Cgroups——Docker 资源限制背后的技术原理

宋宝华- Docker 背后的故事之名称空间

《Linux内核 核心知识全解析(完)》

Docker基础知识