浅谈 Docker 底层原理

容器技术是操作系统层面的虚拟化技术,可以概括为使用 Linux 内核的 cgroup,namespace 等技术,对进程进行的封装隔离。

容器技术演化

Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上, 然后通过Socket连接从客户端访问Docker守护进程。

Docker 详细介绍可查看 Docker 指南

Dockers 核心技术

Linux 命名空间、控制组和 UnionFS(联合文件系统) 三大技术支撑了目前 Docker 的实现,也是 Docker 能够出现的最重要原因。

  • namespace:命名空间,容器隔离的基础,保证A容器看不到B容器.
  • cgroups(Control Group):控制组,容器资源统计和隔离。
  • UnionFS:联合文件系统,aufs/overlayfs,分层镜像实现的基础。

实际上 Docker 是使用了很多 Linux 的隔离功能,让容器看起来像一个轻量级虚拟机在独立运行,容器的本质是被限制了的 Namespaces,cgroup,具有逻辑上独立文件系统,网络的一个进程。

Docker 核心技术

Namespaces 命名空间

在Linux系统中,Namespace是在内核级别以一种抽象的形式来封装系统资源,通过将系统资源放在不同的Namespace中,来实现资源隔离的目的。不同的Namespace程序,可以享有一份独立的系统资源。

Linux 的命名空间机制提供了以下七种不同的命名空间,包括 :

Linux 命名空间

通过这七个选项, 我们能在创建新的进程时, 设置新进程应该在哪些资源上与宿主机器进行隔离。具体如下:

NamespaceFlagPageIsolates
CgroupCLONE_NEWCGROUPcgroup_namespacesCgroup root directory
IPCCLONE_NEWIPCipc_namespacesSystem V IPC,POSIX message queues 隔离进程间通信
NetworkCLONE_NEWNETnetwork_namespacesNetwork devices,stacks, ports, etc. 隔离网络资源
MountCLONE_NEWNSmount_namespacesMount points 隔离文件系统挂载点
PIDCLONE_NEWPIDpid_namespacesProcess IDs 隔离进程的ID
TimeCLONE_NEWTIMEtime_namespacesBoot and monotonic clocks
UserCLONE_NEWUSERuser_namespacesUser and group IDs 隔离用户和用户组的ID
UTSCLONE_NEWUTSuts_namespacesHostname and NIS domain name 隔离主机名和域名信息

对于docker来说,最为直接的,是PID隔离

PID隔离

Process ID

如果需要了解 PID的命名空间隔离,我们从基础的 linux 进程的 fork函数开始,尽管,系统调用函数fork()并不属于namespace的API。

当程序调用fork()函数时,系统会创建新的进程,为其分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的进程中,只有少量数值与原来的进程值不同,相当于克隆了一个自己。那么程序的后续代码逻辑要如何区分自己是新进程还是父进程呢?

fork()的神奇之处在于它仅仅被调用一次,却能够返回两次(父进程与子进程各返回一次),通过返回值的不同就可以进行区分父进程与子进程。它可能有三种不同的返回值:

  • 在父进程中,fork返回新创建子进程的进程ID
  • 在子进程中,fork返回0
  • 如果出现错误,fork返回一个负值

网络隔离

如果 Docker 的容器通过 Linux 的命名空间完成了与宿主机进程的网络隔离,但是却有没有办法通过宿主机的网络与整个互联网相连,就会产生很多限制。

所以 Docker 虽然可以通过命名空间创建一个隔离的网络环境,但是 Docker 中的服务仍然需要与外界相连才能发挥作用。
每一个使用 docker run 启动的容器其实都具有单独的网络命名空间,Docker 为我们提供了四种不同的网络模式:

  • Host、
  • Container、
  • None
  • Bridge 模式。

UnionFS 联合文件系统

什么是镜像?

那么问题来了,没有操作系统,怎么运行程序?

可以在Docker中创建一个centos的镜像文件,这样就能将centos系统集成到Docker中,运行的应用就都是centos的应用。

Image 是 Docker 部署的基本单位,一个 Image 包含了我们的程序文件,以及这个程序依赖的资源的环境。Docker Image 对外是以一个文件的形式展示的(更准确的说是一个 mount 点)。

从右侧以 UFS 的视角来看,lowerdir 和 upperdir 是两个不同的目录,UFS 将二者合并起来,得到 merged 层展示给调用方。从左侧的 docker 角度来理解,lowerdir 就是镜像,upperdir 就相当于是容器默认的可写层。

容器可以近似理解为镜像的运行时实例,默认情况下也算是在镜像层的基础上增加了一个可写层。所以,一般情况下如果你在容器内做出的修改,均包含在这个可写层中。

UnionFS 与AUFS

UnionFS 其实是一种为 Linux 操作系统设计的用于把多个文件系统『联合』到同一个挂载点的文件系统服务。

AUFS(Advanced UnionFS)其实就是 UnionFS 的升级版,它能够提供更优秀的性能和效率。

AUFS 作为先进联合文件系统,它能够将不同文件夹中的层联合(Union)到了同一个文件夹中,这些文件夹在 AUFS 中称作分支,整个『联合』的过程被称为联合挂载(Union Mount)。

总结:Dockers = LXC + AUFS

Docker为 LXC + AUFS 组合:

  • LXC 负责资源管理;
  • AUFS 负责镜像管理;

而LXC包括cgroup,namespace,chroot等组件,并通过cgroup资源管理,那么,从资源管理的角度来看,Docker、Lxc、Cgroup三者的关系是怎样的呢?

cgroup是在底层落实资源管理,LXC在cgroup上面封装了一层,随后,docker有在LXC封装了一层;

Cgroup其实就是linux提供的一种限制,记录,隔离进程组所使用的物理资源管理机制;也就是说,Cgroup是LXC为实现虚拟化所使用资源管理手段,我们可以这样说,底层没有cgroup支持,也就没有lxc,更别说docker的存在了,这是我们需要掌握和理解的,三者之间的关系概念

我们在把重心转移到LXC这个相当于中间件上,上述我们提到LXC是建立在cgroup基础上的,我们可以粗略的认为LXC=Cgroup+namespace+Chroot+veth+用户控制脚本;LXC利用内核的新特性(cgroup)来提供用户空间的对象,用来保证资源的隔离和对应用系统资源的限制;

Docker容器的文件系统最早是建立在Aufs基础上的,Aufs是一种Union FS,简单来说就是支持将不同的目录挂载到同一个虚拟文件系统之下

并实现一种laver的概念,

由于Aufs未能加入到linux内核中,考虑到兼容性的问题,便加入了Devicemapper的支持,Docker目前默认是建立在Devicemapper基础上,

**devicemapper用户控件相关部分主要负责配置具体的策略和控制逻辑,**比如逻辑设备和哪些物理设备建立映射,怎么建立这些映射关系等,而具体过滤和重定向IO请求的工作有内核中相关代码完成,因此整个device mapper机制由两部分组成--内核空间的device mapper驱动,用户控件的device mapper库以及它提供的dmsetup工具;

Docker Vs. VM

VM (VMware),虚拟机。

架构上:

  • Docker 有着比 VM 更少的抽象层
  • Docker 利用的是宿主机的内核,VM 需要的是 Guest OS ( 客机操作系统是指一个安装在虚拟机上的操作系统。)

Docker Vs VM

流程上:

  • VM(VMware )在宿主机器、宿主机器操作系统的基础上创建虚拟层、虚拟化的操作系统、虚拟化的仓库,然后再安装应用;
  • Docker容器(Container) ,在宿主机器、宿主机器操作系统上创建Docker引擎,在引擎的基础上再安装应用。

所以说,新建一个容器的时候,docker不需要像虚拟机一样重新加载一个操作系统,避免引导。docker是利用宿主机的操作系统,省略了这个复杂的过程,秒级;虚拟机是加载Guest OS ,这是分钟级别的。

总览

特性容器虚拟机
启动速度秒级分钟级
硬盘使用一般为MB一般为GB
性能接近原生弱于原生
系统支持量单机支持上千个容器一般几十个

扩展内容

Docker 镜像分层机制

Docker Image 是有一个层级结构的,最底层的 Layer 为 BaseImage(一般为一个操作系统的 ISO 镜像),然后顺序执行每一条指令,生成的 Layer 按照入栈的顺序逐渐累加,最终形成一个 Image。

镜像分层机制

Docker Image 如何而来呢?

简单来说,一个 Image 是通过一个 DockerFile 定义的,然后使用 docker build 命令构建它。

DockerFile 中的每一条命令的执行结果都会成为 Image 中的一个 Layer。

参照