本文主要简单讲解 Docker 底层原理,包括控制组,命名空间和分层存储。
为了理解 Docker 帮助我们做了什么,我们先来看看 Linux 内核做了什么。简单来说,Linux 内核做了下面几件事:
Docker 做的也是这些事情:
Docker 是一个 Go 语言开发的程序,它利用了 Linux 内核的一些特性,比如控制组,命名空间等技术来为容器提供隔离,让容器看起来就是一个独立的系统。这些技术并不是 Docker 的原创,在 Docker 之前这些技术就已经存在了,不过除非你是 Linux 专家,否则很难完美地使用这些特性。Docker 的出现让这一切变得优雅又简单,你可以很方便地在自己的电脑使用 Docker 部署容器!
本文接下来的内容会比较详细地介绍一下这些 Docker 背后的技术,了解一下原理有助于大家对 Docker 有一个更加深刻的认识。让我们开始吧!
Docker 采用了 C/S 架构,包括客户端(Client)和服务端(Server),服务端通过 socket 接受来自客户端的请求,这些请求可以是创建镜像,运行容器,终止容器等等。
服务端既可以运行在本地主机,也可以运行在远程服务器或者云端,只要你可以访问 Docker 的服务端,你甚至可以在容器里运行容器。现在,我们来看看在 Docker 容器里运行 Docker 容器的例子:
Docker 官方有一个名为“docker”的镜像,使用这个镜像运行容器的话,就可以在容器里运行 Docker 命令。现在,我们让客户端运行在这个容器里面,服务端运行在宿主主机,所以需要把宿主主机的“/var/run/docker.sock”挂载到容器里的“/var/run/docker.sock”:
sudo docker run --rm -ti -v /var/run/docker.sock:/var/run/docker.sock docker sh
然后,我们可以在这个容器里运行 Docker 命令:
你可以看到,“net:v1.0” 本是我们自定义的镜像,存储在宿主主机里,之所以我们在容器里可以从这个镜像运行容器,是因为我们可以访问宿主主机的 Docker 服务端。所以只要我们可以访问宿主主机的 Docker 服务端,我们就可以从服务端存在的镜像运行容器。
总之,只要理解:Docker是C/S架构就可以了!
在深入讲解 Docker 网络原理之前,不得不简单提一下网络有关的知识:
其实在之前学习 Docker 网络操作部分的时候,我们已经介绍过 Docker 网络的一些原理了。Docker 并不是像变魔术一样直接在容器之间传递包,而是运行的时候会自动在宿主主机上创建一个名为 docker0 的虚拟网桥,它就像软件交换机一样,在挂载到它的网口之间进行消息转发。运行一个 Docker 容器时,会创建 veth 对(Virtual Ethernet Pair)接口,这对接口一端在容器内,另一端挂载到 Docker 的网桥(默认 docker0,或者使用-- net 参数指定网络)。veth 总是成对出现,并且从一端进入的数据会从另一端流出,这样就可以实现挂载到同一网桥的容器间通信。
之前在学习 Docker 端口映射的时候,使用 -p 参数将宿主主机的端口映射到容器内部,这个过程用到了 Linux 的防火墙命令 iptable,iptable 会创建映射规则。现在,我们的主机上没有运行任何容器,让我们看看目前的端口映射规则:
sudo iptables -n -L -t nat
现在,运行一个容器,映射宿主机的8080端口:
sudo docker run --rm -p 8080:8080 -ti net:v1.0 bash
现在,再来看看端口映射情况:
可以从最后一行看到我们的映射规则“tcp dpt:8080 to 172.17.0.2:8080”。
先来简单描述一下 Linux 进程有关知识。
Linux 的进程都是来自一个父进程,所以进程之间是父子关系。当一个子进程结束的时候,会返回一个退出代码给父进程。在众多进程中,有一个进程是特殊的,它就是初始化进程(init),进程号为0,这个进程负责启动所有其它进程。
使用 Docker 运行容器时,容器启动的时候也有一个初始化进程,当这个进程终止的时候,对应的容器也就终止了。下面以一个具体例子加深理解:
首先,运行一个容器:
sudo docker run --name process --rm -ti ubuntu:16.04 bash
然后,查看容器进程号:
sudo docker inspect --format '{{.State.Pid}}' process
在我的电脑上结果是:
然后使用"kill 23483"命令,发现容器退出。
并且,Docker 使用 Linux 控制组(cgroup, control group) 来对容器进程进行隔离。cgroup 是 Linux 内核的特性之一,它保证所有在一个控制组内的进程组成一个私密的、隔离的空间。控制组内的进程有自己的进程号,并且无法访问所在控制组之外的进程。所以控制组可以把你的系统中的进程划分为若干相互隔离的区域,并且控制组内的父进程衍生的子进程依旧在这个控制组内。Docker 正是利用这个特性实现容器间进程隔离。同时,控制组还提供了资源限制,资源审计等功能,这些在 Docker 里都有所体现。
在学习编写 Dockerfile 的部分,我们已经详细介绍过了 Docker 的分层存储架构,所以如果忘记了,请返回去复习相关内容。
本文主要简单介绍了 Docker 的底层原理。下一篇文章将会介绍一下如何使用 Docker 配置一个公用的 GPU 深度学习服务器。在配置 GPU 深度学习服务器的过程中,其实我们是把容器当成虚拟机来用了,纵然这样做有悖于 Docker 哲学,但是对初学者却是一个很好的系统复习之前学到的知识的例子。