Docker容器到底是怎么回事

Database and Ruby, Python, History


Docker 容器其实是利用 Linux 的 Namespace 技术,实现了进程,挂载点,网络等隔离,本质上还是一个特殊的进程。这样,在容器里面,只能够看到自己 Namespace 空间里面的进程和资源,比如只能够看到 PID 为 1 的进程。同时,这也意味着,一个容器无法同时跑两个不同的应用(除非用supervisord)。Docker 这样的技术,脱离了整个虚拟机,实现了类似于进程级别的虚拟化。

而另外一方面,Cgroups 用来为进程设置资源限制,比如写过如何现在 Docker 的资源分配

对于运行目录的隔离,Docker 通过chroot命令改变进程的根目录到指定位置,这样容器里面就只能够看到指定目录的内容了。而根目录为容器进程提供隔离后执行环境的文件系统,即rootfs。也正是rootfs,使得容器有了“一致性”,使得一次打包,所有的机器上跑起来都是一样的,告别了每个虚拟机都可能不一致的困扰。

针对于rootfs以后的增量变更,Docker 又引入了layer的概念。通过 AuFS(Advance UnionFS), 将多个文件夹合并为一个新的目录。一般一个镜像会有 3 种层,分别是只读层,Init 层和可读写层。只读层一般只包含操作系统文件,Init 层包含/etec/hosts等信息,这一层在 docker commit 的时候不会被提交到镜像里面。而可读写层实际上是空的,只有在修改的时候,会把文件覆盖到这一层再修改,然后类似于遮罩一样,让你看不到只读层的文件。

至于ENTRYPOINTCMD,docker 默认有一个 ENTRYPOINT /bin/sh -c。所以,如果不指定ENTRYPOINTCMD 以及后面的参数都会被当做/bin/sh -c的参数传进去;如果指定了ENTRYPOINT,则用新的参数。(注意区分execshell模式)

docker exec 是如何工作的

docker 容器实际上是在宿主机上的一个进程,在/proc/{PID}/ns下可以看到该 Namespace 下的所有文件,有了这些文件,我们就可以通过共享文件的方式“进入”该容器。

docker –net=host 是如何工作的

在启动的时候,取消 network namespace,即不隔离网络,就可以实现和宿主机共享网络了。

docker commit 是如何工作的

docker 会把只读层和可读写层一起打包成一个新的镜像,因为只读层是共享的,所以他们不会占用额外的空间。如果你对只读层做了修改,它们会被复制到可读写层、修改、再“遮罩”。

docker volume 是如何工作的

实际上是将目录挂载到可读写层的对应目录/var/lib/docker/aufs/mnt/[layer ID]/[folder_name]下即可,它们会通过 AuFS 联合挂载到容器里面去。该操作只对容器可见,宿主机看不到挂载操作。

docker volume 挂载之后的改动会被提交到 image 里面去么

不会。因为上面的目录实际上是一个空壳,会被“软链接”到真实的目录下,对于宿主机,这个目录是空的。

docker 容器是如何和宿主机通信的

宿主机上会有一个 docker0 的 bridge,每个容器上会有一个 Veth Pair,这个 Veth Pair 的一端会被‘插’在 docker0 上,另外一端‘插’在容器上。对于容器,这个 Veth Pair 就是 eth0,当需要访问宿主机的时候,就需要通过这个网卡,留到宿主机的 docker0 上,进而进入了宿主机。

同一台宿主机上的多个 docker 容器之间是如何通信的

如下图,两个容器的通信依旧是通过 docker0 网桥,这个时候 docker0 就相当于交换机(IP 和 Mac 地址的绑定关系),将数据发往另外一个容器。

docker 容器如何访问其他主机

如下图,容器会先访问到宿主机的 docker0,再通过宿主机的网卡 eth0,跳到其他主机的网卡上。

docker 容器之间如何跨主机访问

这里需要 overlay network,把所有容器连通起来的,构建一个虚拟网络。

Flannel 是如何实现的

Flannel 是 overlay network 的一个实现。Flannel 有三种实现方式,分别是 UDP,XVLAN 和 host-gw。

针对 UDP,Flannel 管理的容器里,一个宿主机的所有容器被分配到一个子网内,并且在每个宿主机内部加入了 IP 规则。这样的结果就是,当宿主机 A 内的容器 A1 访问宿主机 B 内的容器 B1,会这样操作:

  1. 容器 A1 通过容器内的 eth0,流向宿主机 A 的 docker0 网桥
  2. 根据宿主机 A 上的 IP 规则, 该数据包被转发到 Flannel 的 TUN 设备。
  3. TUN 设备工作在网络第三层,数据会交给 Flannel 的进程。
  4. Flannel 根据自己管理的子网,判断出目标容器 B1 是在宿主机 B 上,将数据转发到宿主机 B 上的 Flannel 监听的端口 8285
  5. 宿主机 B 上的 Flanneld 进程将这个数据包转发给 TUN 设备
  6. 宿主机 B 上的 TUN 设备根据 IP 规则,转发给宿主机 B 的 docker0 网桥
  7. docker0 网桥又继续把数据转发给容器 B1 对应的 Veth Pair

UDP 性能很差,主要原因就是数据包会在内核态和用户态中转换多次。

由此而引出了 XVLAN 方式。XVLAN 是利用 Linux 的网络虚拟化技术,在每个宿主机上创建一个 VTEP 设备,工作在网络第二层,并加入其他 VTEP 设备的 IP 规则,实现数据包在网络第二层就实现转发。

性能最高的 host-gw 模式其实就是直接加入 IP 规则,将数据包转发到下一个子网对应的宿主机(修改目标地址的 MAC 地址),前提是这些宿主机是二层连通的。