十八、docker核心技术
docker是一种容器虚拟化技术。
自0.9后,docker除了继续支持LXC格式之外,还引入了自家的lbicontainer,试图打造更通用的底层容器虚拟化库。
从操作系统功能上看,docker底层依赖的核心技术主要包括Linux操作系统的命名空间(namespace)、控制组(control groups)、联合文件系统(union file system)和Linux虚拟网络支持。
18.1 基本架构
docker采用了标准的C/S架构,包括客户端和服务端两大部分。
客户端和服务端既可以运行在一个机器上,也可以通过socket或者RESTful API来进行通信。
1.服务端:
docker daemon一般在宿主主机后台运行,作为服务端接受来自客户的请求,并处理这些请求(创建、运行、分发容器)。在设计上,docker daemon是一个非常松耦合的架构,通过专门的Engine模块来分发管理各个客户端的任务。
docker服务端默认监听本地的unix:///var/run/docker.sock套接字,只允许本地的root用户访问。可通过-H选项来修改监听的方式。如,让服务端监听本地的TCP连接1234端口:
sudo docker daemon -H 0.0.0.0:1234
此外docker还支持通过HTTPS认证方式来验证访问。
PS. 从网上找的方法修改服务端监听地址和端口:
1)修改配置文件/usr/lib/systemd/system/docker.service
2)在[service]下的ExecStart=后加一行 -H 0.0.0.0:2375 \ #这里的端口自选
3)systemctl daemon-reload && systemctl restart docker
4)ps -ef | grep docker
5)netstat -nlp | grep docker
6)之后如果直接在本地用docker images会出错,可以用docker -H x.x.x.x:port images
如果要加上本地的 -H unix:///var/run.docker.sock 则会使远程和本地都可以连接docker服务端。
2. 客户端
docker客户的哈则为用户提供一系列可执行命令,用户用这些命令实现与docker daemon的交互。
用户使用的docker可执行命令即为客户端程序。客户端发送命令后,等待服务端返回,一旦收到返回后,客户端立刻执行结束并退出。用户执行新的命令,需要再次调用客户端命令。
同样,客户端默认通过本地的unix:///var/run/docker.sock套接字向服务端发送命令。如果服务端没有监听到默认套接字,则需要客户端在执行命令的时候显式指定。
例如,假定服务端监听在本地的TCP连接1234端口,只有通过-H参数指定了正确的信息才能连接到服务端:
docker version #查看版本
docker -H tcp://127.0.0.1:1234 version
18.2 命名空间
命名空间(Namespace)是Linux内核针对实现容器虚拟化而引入的一个强大特性。
每个容器都可以拥有自己单独的命名空间,运行在其中的应用都像是在独立地操作系统中运行一样,命名空间保证了容器之间彼此互不影响。
在操作系统中,包括内核、文件系统、网络、PID、UID、IPC、内存、硬盘、CPU等资源,所有的资源都是应用进程直接共享的。要想实现虚拟化,除了要实现对内存、CPU、网络IO、硬盘IO、存储空间等的限制外,还要实现文件系统、网络、PID、UID、IPC等等的相互隔离。
Linux系统命名空间的逐步完善,可以让某些进程在彼此隔离的命名空间中运行。虽然这些进程都共用一个内核和某些运行时环境(runtime,例如一些系统命令和系统库),但是彼此是不可见的–它们都认为自己是独占系统的。
1. 进程命名空间
Linux通过命名空间管理进程号,对于同一进程(同一个task_truct),在不同的命名空间中,看到的进程号不相同,每个进程命名空间有一套自己的进程号管理方法。进程命名空间是一个父子关系的结构,子空间中的进程对于父空间是可见的。新fork出的进程在父命名空间和子命名空间将分别有一个进程号来对应。
2. 网络命名空间
如果有了PID命名空间,那么每个名字空间中的进程就可以相互隔离,但是网络端口还是共享本地系统的端口。
通过网络命名空间,可以实现网络隔离。一个网络命名空间为进程提供了一个完全独立的网络协议栈的视图。包括网络设备接口、IPv4和IPv6协议栈、IP路由表、防火墙规则,sockets等等。
这样每个容器的网络就能隔离开来。docker采用虚拟网络设备(virtual network device)的方式,将不同的命名空间的网络设备连接到一起。默认情况下,容器中的虚拟网卡将同本地主机上的docker0网桥连接在一起。
使用brctl工具,则可以看到桥接宿主主机docker0网桥上的虚拟网口。
3. IPC命名空间
容器中进程交互还是采用了Linux常见的进程间交互方法(interprocess communication – IPC),包括信号量、消息队列、共享内存等。PID命名空间和IPC命名空间可以组合起来一起使用,同一个IPC名字空间内的进程可以彼此可见,允许进行交互;不同空间的进程则无法交互。
4. 挂载命名空间
类似chroot,将一个进程放到一个特定的目录执行。挂载命名空间允许不同命名空间的进程看到的文件结构不同,这样每个命名空间中的进程所看到的文件目录彼此被隔离。
5. UTS命名空间
UTS(UNIX time-sharing system)命名空间允许每个容器拥有独立的主机名和域名,从而可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样。
默认情况下,docker容器的主机名就是返回的容器ID。
6. 用户命名空间
每个容器可以有不同的用户和组ID,也就是说可以在容器内使用特定的内部用户执行程序,而非本地系统上存在的用户。
18.3 控制组
控制组(CGropus)是Linux内核的一个特性,主要用来对共享资源进进行隔离、限制、审计等。只有能控制分配到容器的资源,docker才能避免多个容器同时运行时的系统资源竞争。
控制组可以提供对容器的内存、CPU、磁盘IO等资源进行限制和计费管理。
控制组的设计目标是为不同的应用情况提供统一的接口,从控制单一进程到系统级虚拟化。
具体来说,控制组提供如下功能:
1)资源限制 组可以设置为不超过设定的内存限制。比如,内存子系统可以为进程组设定一个内存使用上限,一旦进程使用的内存达到限额再申请内存,就会发出Out of Memory警告。
2)优先级 通过优先级让一些组优先得到更多的CPU等资源。
3)资源审计 用来统计系统实际上把多少资源用到合适的目的上,可以使用cpuacct子系统记录某个进程组使用的CPU时间。
4)隔离 为组隔离名字空间,这样一个组不会看到另一个组的进程、网络连接和文件系统。
5)控制 挂起、恢复和重启动等操作。
安装docker后,用户可以在/sys/fs/cgroup/memory/docker/目录下看到对docker组应用的各种限制项。
可以修改这些文件值来控制组限制docker应用资源。
18.4 联合文件系统
联合文件系统(UnionFS)是一种轻量级的高性能分层文件系统,它支持将文件系统中的修改信息作为一次提交,并层层叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。
联合文件系统是实现docker镜像的技术基础。镜像可以通过分层来进行继承。用户基于本地镜像的容器commit一个新的镜像。这些镜像共享同一个基础镜像,提高了存储效率。此外,当用户改变了一个docker镜像,则一个新的层(layer)会被创建。因此,用户不用替换整个原镜像或者重新建立,只需要添加新层即可。用户分发镜像的时候,也只需要分发被改动的新层内容(增量部分),这让docker镜像管理变得十分轻量级。
docker中使用的AUFS(Another Union File System)就是一种联合文件系统。AUFS支持为每一个成员目录(类似Git的分支)设定只读(readonly)、读写(readwrite)和写出(whiteout-able)权限,同时AUFS里有一个类似分层的概念,对只读权限的分支可以逻辑上进行增量地修改(不影响只读部分)。
当docker利用镜像启动一个容器时,将利用镜像分配文件系统并且挂载一个新的可读可写的层被添加到镜像中。
docker目前支持的联合文件系统种类包括AUFS、btrfs、vfs和DeviceMapper等。
18.5 docker网络实现
docker的网络实现其实就是利用了Linux上的网络命名空间和虚拟网络设备(特别是veth pair)。
1. 基本原理
要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)与外界相通,并且可以收发数据包;此外,如果不同子网之间要进行通信,需要额外的路由机制。
docekr中的网络接口默认都是虚拟的接口。虚拟接口的最大优势就是转发效率极高。因为Linux通过在内核中进行数据复制来实现虚拟接口之间的数据转发,即发送接口的发送缓存中的数据包被直接复制到接受接口的接收缓存中,而无需通过外部物理网络设备进行交换。对于本地系统和容器内系统来看,虚拟接口跟一个正常的以太网卡相比并无区别,只是它速度要快的多。
docker容器网络在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通(这样的一对接口叫做veth pair)。
2. 网络创建过程
docker创建一个容器的时候,会具体执行如下操作:
1)创建一对虚拟接口,分别放到本地主机和新容器的命名空间中。
2)本地主机一端的虚拟接口连接到默认的docker0网桥或指定网桥上,并具有一个以veth开头的唯一名字,如veth1234。
3)容器一端的虚拟接口将放到新创建的容器中,并修改名字作为eth0。这个接口只在容器的命名空间可见。
4)从网桥可用地址段中获取一个空闲地址分配给容器的eth0(例如172.17.0.2/16),并配置默认路由网关为docker0网卡的内部接口docker0的IP地址(例如172.17.42.1/16)。
完成这些之后,容器就可以使用它所能看到的eth0虚拟网卡来连接其他容器和访问外部网络。
另外,可以在docker 运行的时候通过–net参数来指定容器的网络配置,有4个可选值bridge、host、container和none:
1)–net=bridge:默认值,在docker网桥上为容器创建新的网络栈。
2)–net=host:告诉docker不要将容器放到隔离的命名空间中,即不要容器化容器内的网络。此时容器使用本地主机的网络,它拥有完全的本地主机接口访问权限。容器进程可以和其它主机root进程一样打开低范围的端口,可以访问本地网络服务如D-bus,还可以让容器做一些影响整个主机系统的事情,比如重启主机。
3)–net=container:让docker将新建容器的进程放到一个已存在容器的网络栈中,新容器进程有自己的文件系统、进程列表和资源限制,但会和已存在的容器共享IP地址和端口等网络资源,两者进程可以直接通过lo环回接口通信。
4)–net=none:让docker将新容器放到隔离的网络栈中,但是不进行网络配置。之后,用户可以自己进行配置。
3. 网络配置细节
用户使用–net=none后,docker将不对容器网络进行配置。
下面手动完成配置网络的整个过程。
原文链接:https://www.cnblogs.com/cjj-ggboy/p/12635176.html
原创文章,作者:优速盾-小U,如若转载,请注明出处:https://www.cdnb.net/bbs/archives/17596