-
Namespaces(命名空间)
namespaces 是Linux 中用于分离 进程树、网络接口、挂载点以及进程间通信 等资源的方法。 在单服务器中,若启用多个服务,而未使用namespaces,服务间会相互干扰, 每个服务都能看到其他服务的进程,也可访问宿主机器上的任意文件。 这显然不是我们所乐意的,我们更愿意运行在同一台机器上的不同服务之间能做到 完全隔离, 就像运行在多台不同机器上一样。 毕竟,如果各个服务间能互相访问任意文件,那么一旦同一台机器上某个服务被入侵, 入侵者便能访问这台机器上所有的文件了。 Linux的命名空间机制提供了7种不同的命名空间: CLONE_NEWCGROUP、CLONE_NEWIPC、CLONE_NEWNET、 CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWUSER和CLONE_NEWUTS, 通过这7各选项,可以在创建新的进程时使新进程应该在哪些资源上与宿主机器进行隔离。
Docker 通过Linux 的Namespaces 对不同的容器实现了隔离。
-
进程
Docker 容器内的进程与宿主机器的进程相互隔离。在宿主机中看得到Docker中的进程; 而Docker容器中除本身运行进程外,看不到宿主机进程 (可以使用进程树的概念进行理解,Docker为宿主机器进程树中一棵子树) Docker 进程使用了Linux 中 namespaces 的CLONE_NEWPID, 使得Docker容器内部对宿主机进程一无所知。 当我们每次使用 docker run 或 docker start 命令时, 都会创建一个用于设置进程间隔离的Spec 所有的命名空间相关设置Spec最后都会作为Create函数的参数在创建容器时进行设置: (包括用户、网络、IPC以及UTS相关等)
-
网络
若 Docker 通过 Namespaces ,与宿主机器进行了网络隔离, 导致无法通过宿主网络与整个互联网连通,会带来很多不必要的麻烦。 所以Docker 虽然通过Namespaces 创建了一个隔离的网络环境, 但 Docker 服务仍需要与外界相连才能发挥作用: Dokcer 提供了四种不同的网络模式,Host、Container、None和Bridge (每次使用docker run 的网络命名空间都是独立的) Docker 的默认网络设置模式:Bridge 在Bridge下,除了分配隔离的网络命名空间外,Docker还会为所有的容器设置IP地址。 当Docker服务器在宿主机上启动后,会创建新的虚拟网桥 docker0, 随后在宿主机上启动的所有服务在默认情况下都与该网桥相连。 默认情况下,每个容器创建时都会创建一对虚拟网卡, 两个虚拟网卡组成了数据通道, 其中一个会放在创建容器中,加入到docker0 网桥中。 可以使用命令 brctl show 查看当前网桥的接口
docker0 会为每个容器分配一个新的IP地址 并将 docker0 IP地址设置成默认网关。
docker0 通过 iptables 中的配置与宿主机器上的网卡相连,所有符合条件的请求都会通过 iptables 转发到docker0 并由网桥分发给对应的机器。
即,当有 Docker 容器服务需要暴露给宿主机器时,就为容器分配一个IP地址,
同时向iptables 中追加一条新规则Docker 通过Namespaces实现网络隔离, 又借由网桥,通过iptables 进行数据包转发,
让Docker 容器能为宿主机或其他容器提供服务。 -
libnetwork
libnetwork 提供一个连接不同容器的实现, 同时也能够为应用给出一个提供一致的编程接口和网络层抽象的容器网络模型。 容器网络模型由3个重要部分组成:Sandbox、Endpoint、Network 在容器网络模型中,每个容器内部都包含一个Sandbox,用于存储当前容器的网络栈配置, 包括容器的接口、路由表和DNS配置,Linux使用namespaces 实现 Sandbox, 每个Sandbox中都可能会有多个Endpoint,在Linux中对应虚拟网络概念(veth), Sandbox 通过Endpoint 加入到对应的网络中
-
挂载点
Docker 进程无法访问宿主机器上的其他进程,并且网络访问受到了限制, 但是docker 容器进程仍能访问或修改宿主机器上的其他目录。解决方案: 在新的进程中创建隔离的挂载点命名空间需要在clone 函数传入 CLONE_NEWNS, 这样子进程就能得到父进程挂载点的拷贝,若不传入此参数, 子进程对文件系统的读写都会同步到父进程以及整个主机文件系统中。 若一个容器需要启动,那么它一定要提供一个根文件系统(rootfs)。容器 需要使用这个文件系统,来创建一个新的进程,所有二进制的执行都必须在 这个根文件系统中
若想正常启动一个容器,就需要在rootfs中挂载以上几个特定的目录,
另外还需建立一些符号链接保证系统IO不会出错。
为了保证当前容器进程无法访问宿主机上其他目录,
我们还需要通过libcontainer 提供的pivot_root 或 chroot 函数改变进程能够访问的文件根节点目录。
即,既将容器需要的目录挂载到容器中,又同时禁止当前的容器进程访问宿主机器
上其他的目录,保证不同文件系统的隔离。
-
chroot (change root)
Linux中,系统默认目录使用 / 即根目录开头,chroot 能改变当前的系统根目录结构, 改变当前系统根目录,以便限制用户权限,在新的根目录下并不能访问旧系统根目录的结构文件, 即 建立一个与原系统完全隔离目录结构
-
CGroups (Control Groups)
通过Namespaces为新创建进程隔离了文件系统、网络,并与宿主机器间进程相互隔离, 但是命名空间并不能为我们提供物理资源上的隔离。如CPU、内存、IO、网络带宽等 若在同一台机器上运行了多个对彼此一无所知的容器,但这些容器却共用一份宿主机器的物理资源, 若其中某个容器正在执行CPU密集型任务,那么会影响其他容器中任务的性能和效率,导致多个容器相互影响并抢占资源。 如何对多个容器的资源使用进行限制成了解决进程虚拟资源隔离之后的主要问题, 而CGroups就是用于隔离宿主机器上的物理资源: 每个CGroup 都是一组被相同白哦在和参数限制的进程, 不同CGroup间存在层级关系, 也就是说他们间可以从父类继承一些用于限制资源使用的标准和参数 CGroup提供以下几种功能: 在CGroup中,所有任务是一个系统进程,而CGroup 就是一组按某种标准划分的进程, 在这种机制下,所有资源的控制都是以 CGroup作为单位实现的, 每一个进程都可以随时加入一个CGroup也可以随时退出一个CGroup
-
UnionFS
解决镜像问题
-
存储驱动
Docker如何构建并存储镜像? Docker 的镜像是如何被每一个容器所使用的? Docker 中每个镜像都是由一系列只读层组成的,Dockerfile中的每个命令都会在已有的只读层上创建新层: 当镜像被 docker run 创建时, 会在镜像顶部添加一个可写层,即容器层, 所有对于运行时容器的修改都在这层发生 容器和镜像的区别主要在于,所有镜像都是只读的,而容器等于在镜像上加一个读写层,即一个镜像可以对应多容器。
-
AUFS (Advanced UnionFS)
UnionFS 是Linux 下将多个文件系统 归并到 同一个挂载点的文件系统服务。 而AUFS 是UnionFS 的优化版本,提供更为优秀的性能和效率 UnionFS 作为 联合文件系统,能将不同文件夹中的层 联合到同一个文件夹中, 这些文件夹在AUFS中称为分支,整个 联合过程 称为联合挂载 每个镜像层或容器层都是 /var/lib/docker/ 目录下的一个子文件夹;在Docker 中, 所有镜像层和容器层的内容都存在 /var/lib/docker/aufs/diff/ 目录下: 而 /var/lib/docker/aufs/layers/ 中存储着镜像层的元数据,每个文件都保存着镜像层的元数据, /var/lib/docker/aufs/mnt/ 包含镜像或者容器的挂载点,最终会被Docker通过联合方式组装。
-
其他存储驱动
除了AUFS存储驱动外,Docker也支持其他驱动,包括overlay2、devicemapper等, 在最新的Docker中,overlay2取代了aufs成为推荐驱动
总结:
1. 命名空间namespaces解决逻辑隔离,CGroups解决物理隔离
2. 以libnetwork为主使用网桥docker0解决网络问题
3. 使用AUFS(后被overlay2替代)解决多文件系统管理问题.
4. 容器建立在镜像之上,比容器多了一层读写层
本文转自博客(有做部分修改):https://draveness.me/docker