总体架构
Docker 采纳的是 C/S 架构,应用 REST API、UNIX 套接字或网络接口进行通信。个别客户端会和 Docker 服务运行在同一台机子上,像咱们平时应用的 docker build、pull、run 等命令就是发送到本地客户端上的,本地客户端再发送给 Docker 服务端。另外,客户端也能够独立部署,像 Docker Compose。
Docker 服务个别是以 守护过程 的模式运行,它会监听客户端的申请,并且进行容器的构建、运行和散发,上面即 Docker 的 总体架构:
Docker 守护过程
:侦听 Docker API 申请并治理 Docker 对象,例如镜像、容器、网络和卷。守护过程还能够与其余守护过程通信以治理 Docker 服务。Docker 客户端
:通过 Docker API 发送命令给 Docker 守护过程(dockerd
),让守护过程执行对应的命令动作,例如发送 docker run 命令。Docker Registry
:存储了 Docker 镜像。像 Docker Hub 就是一个任何人都能够应用的公共注册核心,Docker 会默认地从 Docker Hub 上查找镜像。当然,咱们也能够本人搭建个 Docker Registry。
容器的演变
一开始,Docker 是基于 Linux 内核提供的技术进行容器治理的,它将 Linux 简单的容器治理进行了 简化,造成了本人独有的一套命令体系。起初,Docker 将底层技术进行了形象,定义了一组接口,只有实现了这组接口,那么就能够进行容器的治理,这就是 Libcontainer
。
随着 Docker 的炽热,越来越多的公司退出容器技术的开发,在 2015 年谷歌、微软、Docker 等公司成立了 OCI 组织,致力于定制统一的容器规范。在 Libcontainer 的根底上推出了容器引擎:runC
。
可能大家会比拟好奇的是 windows 的容器架构又是怎么样的?其实在 windows 上也形象进去了 CGroup
和 Namespace
,它也是合乎 OCI 容器规范的,如下图:
(图片来自 Black Belt 在 DockerCon 的演讲:Docker 与 Windows 容器揭秘)
底层技术
Docker 是用 Go 语言编写的,所以天生就反对这种跨平台的部署。不过支流的服务器都是 Linux 零碎,所以咱们来看看对于 Linux 的容器底层技术:Namespaces
(资源隔离)、CGroups
(资源限度)、UnionFS
(镜像和容器分层)。
Namespaces(资源隔离)
Namespaces
是 Linux 内核在 2.4.19 版本后陆续引入的概念,它将零碎的全局资源通过形象划分,使得在同一 namespace 中的过程看起来领有本人的全局资源。以后 Linux 反对以下六种 Namespace
namespace | 隔离的系统资源 |
---|---|
Mount namespaces | 文件系统挂接点 |
IPC namespaces | 特定的过程间通信资源 |
UTS namespaces | nodename 和 domainname |
PID namespaces | 过程 ID |
Network namespaces | 网络相干的系统资源 |
User namespaces | 用户和组 ID 空间 |
咱们能够看到有 Network 网络的,也有用户 User 的 隔离。当容器被创立时,会创立下面对应的 Namespace 实例,而后将容器过程划分到此 Namespace 里, 以此实现了隔离性能。
CGroups(资源限度)
下面的 Namespace 为咱们提供了环境隔离的性能,但这还远远不够,因为各个过程所应用的资源还是没有限度的,比方 CPU、内存等。一旦某个容器超过下限,则有可能会被 kill。因而,对资源的限度应用就很重要了,而 Linux 内核的 CGroups
就提供了此性能。
当咱们创立了一个容器时,默认的会在 /sys/fs/cgroup
目录下生成对应的资源应用目录,比方 docker run nginx:test
,则会在 /sys/fs/cgroup/memory/docker/nginx 容器 ID
目录下有对应的资源形容文件:
应用命令 docker run --memory 1024M nginx:test
时,就能够进行内存资源的限度了。其余资源限度命令也相似。
UnionFS(镜像和容器分层)
Linux 的 UnionFS
(联结文件系统) 技术是用来将不同物理地位的目录合并挂载到同一个目录中。实际上 UnionFS 在不同的零碎上有不同的实现,当初支流的是 AUFS、Devicemapper 和 OverlayFS。在 Docker 中最罕用的是 AUFS,咱们次要来看看 AUFS 的相干常识。
首先,默认状况下 AUFS 有个特点,就是要联结的第一个文件是可读可写的,前面的文件目录则只能只读。例如,咱们将 teacher、student 目录联结到 mnt 目录下:
# 将 teacher 和 student 联结到 mnt
sudo mount -t aufs -o dirs=./teacher:./student none ./mnt
├── teacher
│ ├── A
│ └── C
└── student
├── B
└── C
# 查看./mnt
$ tree ./mnt
├── A
├── B
└── C
当咱们对 mnt 下的 C 目录批改后,会在 teacher 目录下同步看到批改,但 student 目录就不会被批改了,因为它是只读的。那这样的机制在 Docker 里有什么作用呢?
首先,Docker 将文件系统分为容器层和镜像层,这里的容器层相当于下面的 teacher 目录,镜像层相当于 student 目录。也就是容器层文件是可读可写,而镜像层是只读的。这样的话,有利于多个容器共享一个镜像文件。
而且 Docker 在一开始的时候并不会创立容器层,而是先应用镜像层文件,只有当容器里的文件产生了批改,此时才会真正的创立出可读可写的容器层,以保障不影响镜像层文件。而这种相似 写时复制
技术,为零碎节俭了很多不必要的存储文件。
Docker 的平安
在审查 Docker 的安全性时,次要从上面四个方面思考:
(一)Namespaces、CGroups 的平安
Docker 容器与 LXC 容器十分类似,它们具备一样的平安个性。Namespaces 提供了第一种也是最间接的隔离模式,使得在容器内运行的过程无奈看到在另一个容器或主机零碎中运行的过程。每个容器也有属于本人的网络堆栈,这意味着一个容器不能取得对另一个容器的套接字或接口的特权拜访。
CGroups 是 Linux 容器的另一个要害组件,能对资源进行核算和限度,提供了许多无效指标,确保每个容器取得偏心的资源应用(例如内存、CPU、磁盘 I/O),使得单个容器无奈耗尽系统资源。这在多租户平台(例如 PaaS)上尤为重要,能保障用户统一的失常运行性能。
(二)Docker 守护过程的安全性
运行 Docker 守护过程是须要 root 特权的,因而只有受信赖的 User 能力运行 Docker 守护过程。然而因为 Docker 是容许主机和容器共享文件夹的,如果咱们将系统文件映射到 Docker 容器里,那必定也是能冲破零碎防护的。不过,这次要取决于咱们关联的主机文件,个别还比拟好管制。
Docker 也须要避免某些非法申请创立了破坏性的容器。在 0.5.2 之后为了避免一些歹意用户的跨站脚本攻打,Docker 应用了本地的 UNIX 套接字而不是绑定在 127.0.0.1 上的 TCP 套接字,这样就容许用户进行本地权限查看,以进行平安拜访了。
(三)Linux 内核的平安
默认状况下,Docker 启动的是一组性能受限的容器,这使得容器中的“root”比真正的“root”领有更少的特权,例如:
- 禁止任何挂载操作;
- 禁止拜访本地套接字(以避免数据包坑骗);
- 禁止某些文件系统的操作,例更改文件所有者或属性;
- 禁止模块加载;
这使得入侵者设法降级到容器内的 root,也很难以对主机造成严重性的毁坏。
(四)其余内核平安个性
- 容许配置只能拉取指定秘钥签名的镜像仓库
- 应用 GRSEC 和 PAX 运行内核,在编译和运行时减少许多安全检查
- 应用具备平安个性的容器模板
-
自定义拜访控制策略
感兴趣的敌人能够搜一搜公众号「阅新技术」,关注更多的推送文章。
能够的话,就顺便点个赞、留个言、分享下,感激各位反对!
阅新技术,浏览更多的新常识。