0x01 基础架构
docker
目前采用了标准的c/s
架构,包括客户端、服务端两大核心组件,同时通过镜像仓库来存储镜像。客户端和服务端既可以运行在一个机器上,也可通过socket
或者RESTful API
来进行通信。
服务端
docker
服务端一般在宿主机后台运行,用来处理来自客户客户端的请求,主要包括四个组件。
-
dockered
,为客户端提供RESTful API
,响应来自客户端的请求,采用模块化的架构,通过专门的Engine
模块来分发管理各个来自客户端的任务。 -
docker-proxy
,是dockered
的子进程,当容器需要使用端口映射时,它就会完成网络映射配置 -
containerd
,是,提供dockered
的子进程gRPC
接口响应来自dockered
的请求,对下管理runC
镜像和容器环境。 -
containerd-shim
,是,为containerd
的子进程runC
容器提供支持,同时作为容器内进程的根进程。
经过 Server Version: 20.10.3
测试,上述中的子进程均不正确,因为于2016
年,Docker
分拆了 containerd
,并将其捐赠给了社区。将这个组件分解为一个单独的项目,使得 docker 将容器的管理功能移出docker
的核心引擎并移入一个单独的守护进程(即containerd)
。
客户端
Docker
客户端为用户提供一系列可执行命令,使用这些命令可实现与Docker
服务端交互。
客户端程序在发送命令后,等待服务端返回,一旦收到返回后,客户端立刻执行结束并退出,当执行新的命令时,需要再次调用客户端命令。
镜像仓库
镜像是使用容器的基础,docker
使用镜像仓库registry
在大规模场景下存储和分发docker
镜像。
0x02 命名空间
命名空间namespace
是linux
内核的一个强大特性,为容器虚拟化的实现带来极大便利。利用这一特性,每个容器都可以拥有自己单独的命名空间,运行在其中的应用都像在独立的操作系统环境一样。
命名空间机制了容器之间彼此互不影响!
在操作系统中,包括内核、文件系统、网络、进程号、用户号、进程间通信IPC
等资源,所有的资源都是应用进程直接共享的。想要实现虚拟化,除了要对内存、CPU、网络IO、存储空间等的限制外,还要实现文件系统、网络、PID、UID、IPC等的相互隔离。前者相对容易实现,后者则需要宿主主机系统的深入支持。
docker窗口启动时,都通过调用func setNamespaces(daemon *Daemon,s *specs.Spec,c *container.Container) error
方法来完成对各个命名空间的配置
进程命名空间
目前docker上只运行了一个容器
[root@localhost ~]docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b42e6f99cfce lamp "/bin/bash run.sh" 39 hours ago Up 39 hours 0.0.0.0:49230->80/tcp lamp
再开一个容器
[root@localhost ~]docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
66dd5c696607 lamp "/bin/bash run.sh" 9 seconds ago Up 7 seconds 0.0.0.0:49231->80/tcp lamp2
b42e6f99cfce lamp "/bin/bash run.sh" 39 hours ago Up 39 hours 0.0.0.0:49230->80/tcp lamp
明显看到多出一个进程,如果使用pstree -p
效果明显,篇幅受限,可以自行查看。
IPC命名空间
容器中的进程交互还是采用了Linux
常见的进程间交互方法( Interprocess Communication,IPC)
,包括信号量、消息队列和共享内存等方式。PID
命名空间和IPC
命名空间可以组合起来一起使用,同一个IPC
命名空间内的进程可以彼此可见,允许进行交互,不同空间的进程则无法交互。
网络命名空间
Docker
采用虚拟网络设备VND
的方式,将不同命名空间的网络设备连接到一起。默认情况下,Docker
在宿主机上创建多个虚拟网桥,容器中的虚拟网卡通过网桥进行桥接。
通过docker network
可以观察情况,还可以通过brctl
工具看到更具体的信息。
[root@localhost ~]brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024265dd7da1 no veth68f987e
vetha7abc41
通过这下面案例进行对比,在本地上查看网卡信息
接着在其中一个容器中进行查看网卡信息
会发现很有规律,这两个网卡其实是一对出现的,称为veth-peer
,也就是说每产生一个容器,都会产生这样一对网卡,主机和容器之间通信就通过这对网卡走,但是容器和容器之前通信,即使也是同一个网段的,仍然也需要借助docker0
这块虚拟网卡走。
挂载命名空间
类似于chroot
,挂载(Mount,MNT
)命名空间可以将一个进程的根文件系统限制到一个特定的目录下。
挂载命名空间允许不同命名空间的进程看到的本地文件位于宿主机中不同路径下,每个命名空间中的进程所看到的文件目录彼此是隔离的。例如,不同命名空间中的进程,都认为自己独占了一个完整的根文件系统(rootfs)
,但实际上,不同命名空间中的文件彼此隔离,不会造成相互影响,同时也无法影响宿主机文件系统中的其他路径。
UTS命名空间
UTS (UNIX Time-sharing System)
命名空间允许每个容器拥有独立的主机名和域名从而可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一*立的主机一样。
如果没有手动指定主机名称,Docker容器的主机名就是返回的容器ID
的前6字节前缀,否则为指定的用户名:
[root@localhost ~]docker container inspect -f {{".Config.Hostname"}} lamp
b42e6f99cfce
用户命名空间
每个容器可以有不同的用户和组id,也就是说,可以在容器内使用特定的内部用户执行程序,而非本地系统上存在的用户。
每个容器内部都可以有最高权限的root帐号,但跟宿主主机不在一个命名空间。通过使用隔离的用户命名空间,可以提高安全性,避免容器内的进程获取到额外的权限;同时通过使用不同用户也可以进一步在容器内控制权限。
0x03 控制组
控制组(CGroups)
是Linux
内核的一个特性,主要用来对共享资源进行隔离、限制、审计等。只有将分配到容器的资源进行控制,才能避免多个容器同时运行时对宿主机系统的资源竞争。每个控制组是一组对资源的限制,支持层级化结构。
具体来看,CGroups
提供如下功能
-
资源限制( resource limiting)
:可将组设置一定的内存限制。比如:内存子系统可以为进程组设定一个内存使用上限,一旦进程组使用的内存达到限额再申请内存,就会出发Out of Memory警告。 -
优先级(prioritization)
:通过优先级让一些组优先得到更多的CPU等资源。 -
资源审计( accounting)
:用来统计系统实际上把多少资源用到适合的目的上,可以使用cpuacct子系统记录某个进程组使用的CPU时间。 -
隔离(isolation)
:为组隔离命名空间,这样使得一个组不会看到另一个组的进程、网络连接和文件系统。 -
控制(control)
:执行挂起、恢复和重启动等操作。
可以在目录/sys/fs/cgroup/memory/docker/
下看到对容器各方面的限制
[root@localhost ~]ls /sys/fs/cgroup/memory/docker/6873e4943c9f295c6bd2b4b177dd9466a0e698b0309da400083839a231b02623/
cgroup.clone_children memory.kmem.slabinfo memory.memsw.failcnt memory.soft_limit_in_bytes
cgroup.event_control memory.kmem.tcp.failcnt memory.memsw.limit_in_bytes memory.stat
cgroup.procs memory.kmem.tcp.limit_in_bytes memory.memsw.max_usage_in_bytes memory.swappiness
memory.failcnt memory.kmem.tcp.max_usage_in_bytes memory.memsw.usage_in_bytes memory.usage_in_bytes
memory.force_empty memory.kmem.tcp.usage_in_bytes memory.move_charge_at_immigrate memory.use_hierarchy
memory.kmem.failcnt memory.kmem.usage_in_bytes memory.numa_stat notify_on_release
memory.kmem.limit_in_bytes memory.limit_in_bytes memory.oom_control tasks
memory.kmem.max_usage_in_bytes memory.max_usage_in_bytes memory.pressure_level
0x04 联合文件系统
联合文件系统(UnionFS
)是一种轻量级的高性能分层文件系统,它支持将文件系统中的修改信息作为一次提交,并层层叠加,同时可以将不同目录挂载到同一个虚拟文件系统下,应用看到的是挂载的最终结果。联合文件系统是实现Docker镜像的技术基础。
Docker
镜像可以通过分层来继承,当用户分发镜像时,只需要分发有改动那一部分,使得镜像管理变得十分轻盈和快速。
具体可以看之前学习的文章,overlayFS文件系统
也可参照书上的总结
0x05 网络虚拟化
之前在网络命名空间里面也简单介绍了下,应该也有一定了解了,看下图。
网络创建过程如下:
- 创建一对虚拟接口,分别放到本地主机和新容器的命名空间中;
- 本地主机一端的虚拟接口连接到默认的
docker0
网桥或指定网桥上,并具有一个以veth
开头的唯一名字,如veth1234
; - 容器一端的虚拟接口将放到新创建的容器中,并修改名字作为
eth0
。这个接口只在容器的命名空间可见; - 从网桥可用地址段中获取一个空闲地址分配给容器的
eth0
,并配置默认路由网关为docker0
网卡的内部接口的IP
地址。
现在理解起来是不是更加深刻了,如果有问题,再去看看命名空间那一块吧!
[root@localhost ~]docker container run --network
bridge container: host ipvlan macvlan none null overlay
在目前来看,支持的网络类型还是非常多的
-
bridge
,默认就是这个网卡,也就是docker0
。 -
container:
,指的是将新建容器的网卡放去一个已有容器的网络栈中。 -
host
,容器使用本地的网络,权限比较大,如果加了--privileged
则可以修改主机的网络栈。 -
macvlan
,和bridge
类似,也是起子接口,但是省去了bridge
的过程,效率更高。 -
ipvlan
,暂时没找到合适的资料。。。。docker macvlan实战 -
none
,放入新的网络栈上,但是不进行网络配置,一般用户自行配置。 -
null
,禁用容器的网络 -
overlay
,overlay
网络模型在docker集群节点
间的加入了一层虚拟网络,它有独立的虚拟网段,意味着docker
容器发送的内容,会先发送到虚拟子网,再由虚拟子网包装为宿主机的真实网址进行发送。
其实除了上述的种类外,docker
还可以支持自定义网络,也是非常常用的一种方式,后面章节还有讲,这里就不详细说明了。