第07课:Docker

第07课:Docker 底层原理初探

本文主要简单讲解 Docker 底层原理,包括控制组,命名空间和分层存储。

Docker 究竟做了什么?

为了理解 Docker 帮助我们做了什么,我们先来看看 Linux 内核做了什么。简单来说,Linux 内核做了下面几件事:

  • 对来自硬件的消息作出响应;
  • 启动和规划程序的运行;
  • 控制和组织存储;
  • 在程序之间传递消息;
  • 分配资源——内存,CPU,网络等;

Docker 做的也是这些事情:

Docker 是一个 Go 语言开发的程序,它利用了 Linux 内核的一些特性,比如控制组,命名空间等技术来为容器提供隔离,让容器看起来就是一个独立的系统。这些技术并不是 Docker 的原创,在 Docker 之前这些技术就已经存在了,不过除非你是 Linux 专家,否则很难完美地使用这些特性。Docker 的出现让这一切变得优雅又简单,你可以很方便地在自己的电脑使用 Docker 部署容器!

本文接下来的内容会比较详细地介绍一下这些 Docker 背后的技术,了解一下原理有助于大家对 Docker 有一个更加深刻的认识。让我们开始吧!

Docker 的 C/S 模型

Docker 采用了 C/S 架构,包括客户端(Client)和服务端(Server),服务端通过 socket 接受来自客户端的请求,这些请求可以是创建镜像,运行容器,终止容器等等。

enter image description here 图片来自网络

服务端既可以运行在本地主机,也可以运行在远程服务器或者云端,只要你可以访问 Docker 的服务端,你甚至可以在容器里运行容器。现在,我们来看看在 Docker 容器里运行 Docker 容器的例子:

enter image description here 图片来自网络

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 命令:

enter image description here

你可以看到,“net:v1.0” 本是我们自定义的镜像,存储在宿主主机里,之所以我们在容器里可以从这个镜像运行容器,是因为我们可以访问宿主主机的 Docker 服务端。所以只要我们可以访问宿主主机的 Docker 服务端,我们就可以从服务端存在的镜像运行容器。

总之,只要理解:Docker是C/S架构就可以了!

网络

在深入讲解 Docker 网络原理之前,不得不简单提一下网络有关的知识:

  • Ethernet(以太网):通过有线或者无线传递“帧”(frame)
  • IP Layer:在局域网内传递数据包
  • Routing(路由):在不同网络之间传递数据包
  • Ports(端口):寻址一台主机的特定程序,这里指的是某些程序监听某些端口

其实在之前学习 Docker 网络操作部分的时候,我们已经介绍过 Docker 网络的一些原理了。Docker 并不是像变魔术一样直接在容器之间传递包,而是运行的时候会自动在宿主主机上创建一个名为 docker0 的虚拟网桥,它就像软件交换机一样,在挂载到它的网口之间进行消息转发。运行一个 Docker 容器时,会创建 veth 对(Virtual Ethernet Pair)接口,这对接口一端在容器内,另一端挂载到 Docker 的网桥(默认 docker0,或者使用-- net 参数指定网络)。veth 总是成对出现,并且从一端进入的数据会从另一端流出,这样就可以实现挂载到同一网桥的容器间通信。

enter image description here

之前在学习 Docker 端口映射的时候,使用 -p 参数将宿主主机的端口映射到容器内部,这个过程用到了 Linux 的防火墙命令 iptable,iptable 会创建映射规则。现在,我们的主机上没有运行任何容器,让我们看看目前的端口映射规则:

sudo iptables -n -L -t nat

enter image description here

现在,运行一个容器,映射宿主机的8080端口:

sudo docker run --rm -p 8080:8080 -ti net:v1.0 bash

现在,再来看看端口映射情况:

enter image description here

可以从最后一行看到我们的映射规则“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

在我的电脑上结果是:

enter image description here

然后使用"kill 23483"命令,发现容器退出。

并且,Docker 使用 Linux 控制组(cgroup, control group) 来对容器进程进行隔离。cgroup 是 Linux 内核的特性之一,它保证所有在一个控制组内的进程组成一个私密的、隔离的空间。控制组内的进程有自己的进程号,并且无法访问所在控制组之外的进程。所以控制组可以把你的系统中的进程划分为若干相互隔离的区域,并且控制组内的父进程衍生的子进程依旧在这个控制组内。Docker 正是利用这个特性实现容器间进程隔离。同时,控制组还提供了资源限制,资源审计等功能,这些在 Docker 里都有所体现。

分层存储

在学习编写 Dockerfile 的部分,我们已经详细介绍过了 Docker 的分层存储架构,所以如果忘记了,请返回去复习相关内容。

接下来做什么?

本文主要简单介绍了 Docker 的底层原理。下一篇文章将会介绍一下如何使用 Docker 配置一个公用的 GPU 深度学习服务器。在配置 GPU 深度学习服务器的过程中,其实我们是把容器当成虚拟机来用了,纵然这样做有悖于 Docker 哲学,但是对初学者却是一个很好的系统复习之前学到的知识的例子。

上一篇
下一篇
目录