从操作系统功能上看,目前Docker底层依赖的核心技术主要包括Linux操作系统的命名空间(Namespace),控制组(Control Group),联合文件系统(Union File System)和Linux网络虚拟化支持
基础架构
标准C/S架构
客户端和服务器既可以运行在一个主机上,也可以运行在不同的主机上,通过socket或者RESTful API来进行通信
服务端
Docker Daemon一般在宿主机后台运行,作为服务端接收客户端发来的请求,并处理(创建,运行,分发容器)

Docker Daemon是一个模块化结构,通过专门的Engine模块来分发管理各个来自客户端的任务
Docker服务端默认监听本地的unix:///var/run/docker.sock套接字,只允许本地的root用户或docker用户组成员访问
可以通过-H选项修改监听的方式
1 | docker daemon -H IP:PORT |
客户端
Docker客户端为用户提供一系列可执行命令,用户使用这些命令与Docker daemon交互
客户端发送命令后,等待服务器返回,一旦收到返回后,客户端立即执行结束并退出。用户执行新的命令,需要再次调用客户端命令
同样,客户端默认通过本地的unix:///var/run/docker.sock套接字向服务端发送命令,如果服务端没有监听在默认的地址,则需要客户端在执行命令时显式指定服务端地址
1 | docker -H tcp://setver_IP:server_PORT command |
新的架构设计
Docker 1.11.0+中,开始将维护容器运行的任务放到一个单独的组件containerd中来管理,并且支持OCI的runc规范,对客户端API的支持仍然放在Docker Daemon中,通过解耦,大大减少对Docker Daemon的依赖
命名空间
命名空间(namespace)是Linux内核的一个强大特性
利用这一特性,每个容器都可以拥有自己单独的命名空间,运行在其中的应用都像是在独立的操作系统环境中一样。
命名空间机制保证了容器之间彼此互不影响
进程命名空间
Linux通过命名空间管理进程号,对于同一进程,在不同的命名空间中,看到的进程号不相同,每个进程命名空间有自己的进程号管理方法
进程命名空间是一个父子关系的结构,子空间中的进程对于父空间是可见的。新创建的进程在父命名空间和子命名空间将分别有一个进程号来对应
docker主进程是其创建所有容器的父进程
网络命名空间
通过网络命名空间,可以实现网络隔离
网络命名空间为进程提供了一个完全独立的网络协议栈的视图
Docker采用虚拟网络设备(Virtual Network Device)的方式,将不同命名空间的网络设备连接到一起
默认,容器中的虚拟网卡将同本地主机的docker0网桥连接在一起
1 | brctl show |
CentOS7 使用 brctl
1 | yum -y install bridge-utils |
IPC命名空间
容器中进程交互采用Linux常见的进程间交互方法(Interprocess Communication) IPC,
PID命名空间与IPC命名空间可以一起使用,同一个IPC命名空间中的进程彼此可见,可进行交互;不同IPC命名空间的进程无法交互
挂载命名空间
类似于chroot,将一个进程放到一个特定的目录执行
挂载命名空间允许不同命名空间的进程看到的文件结构不同,这样每个挂载命名空间中的进程所看到的文件目录是彼此隔离的
UTS命名空间
UTS(UNIX Time-sharing System)命名空间允许每个容器拥有独立的主机名和域名,从而可以虚拟出一个有独立主机名和网络空间的环境
默认,Docker容器主机名就是返回的容器ID
1 | docker inspect -f {{".Config.Hostname"}} container_id |
控制组
控制组(CGroups)是Linux内核的一个特性,用于对共享资源进行隔离,限制,审计等
控制组提供:
资源限制:设置最大内存限制
优先级:资源分配优先级
资源审计:用来统计系统实际上把多少资源用到合适的目的上,可以使用
cpuacct子系统记录某个进程组使用的CPU时间隔离:为组隔离命名空间
控制:挂起,恢复,重启等操作
可以在创建或启动容器时为每个容器指定资源的限制
1 | -c, --cpu-shares int CPU 权重 |
联合文件系统
联合文件系统(UnionFS)是一种轻量级的高性能分层文件系统,支持将文件系统中的修改信息作为一次提交,并层层叠加,同时可以将不同目录挂载到同一虚拟文件系统下,应用看到的是最终挂载的结果
联合文件系统是实现Docker镜像的技术基础,Docker镜像可以通过分层来进行继承
Linux网络虚拟化
基本原理
Docker中的网络接口默认都是虚拟的接口,虚拟接口的最大优势是转发效率极高
Linux通过在内核中进行数据复制来实现虚拟接口之间的数据转发,即发送接口的发送缓存中的数据包将被直接复制到接收接口的接收缓存中,而无需通过外部物理网络设备进行交换。

网络创建过程
创建一对虚拟接口,分别放在本地主机和新容器的命名空间中
本地主机一端的虚拟接口连接到默认的
docker0网桥或指定网桥上,并具有以veth开头的唯一名称容器一端的虚拟接口将放到新创建的容器中,名称为
eth0,这个接口只在容器的命名空间可见从
docker0网桥可用地址段中获取一个空闲地址分配给容器的eth0,并配置默认网关为docker0网卡的内部接口的IP地址
用户可以通过使用docker run命令启动容器时,使用--net参数指定容器的网络配置
1 | --net=bridge:默认值,在docker0上为容器创建新的网络栈 |
手动配置网络
使用--net=none参数情况
启动容器,指定--net=none参数
1 | docker run -it --rm --net=none ubuntu /bin/bash |

在本地主机查找容器的进程id,并为它创建网络命名空间
1 | pid=$(docker inspect -f '{{.State.Pid}}' container_id) |

本地主机检查docker0网卡IP和子网掩码信息
1 | ip addr show docker0 |

本地主机创建一对veth pair接口A和B,绑定A接口到docker0,并启用
1 | ip link add A type veth peer name B |

本地主机将B接口放到容器的网络命名空间,命名为eth0,启动它并配置一个可用的IP和默认网关
1 | ip link set B netns $pid |

当容器终止后,Docker会清空容器,容器内的网络接口会随网络命名空间一起被清除,A接口也会自动从docker0卸载并清除