目录
概述
Docker起源于LXC(Linux Container),0.9版本起转移到全新设计的libcontainer,以此为基础退出了runc项目。2015年6月将runc捐献,牵头成立了OCI,专注于运行时规范(runtime-spce)和镜像规范(imags-spec)。
OCI中包含了业界顶级企业,如Amazon,Cisco,Dell,Google,Hewlett Packard,Huawei,IBM,Intel,Microsoft,Oracle,Red Hat,SUSE,Vmware等等。
Docker底层依赖技术:
- Linux 命名空间 namespace
- 控制组 cgroup (control group)
- 联合文件系统 UFS (union file system)
- 网络虚拟化
其中部分技术可以更详细展开,后续会针对这些技术进行专门的整理。
基本架构
Docker采用了标准的C/S架构,包含客户端与服务端,同时通过镜像仓库存储镜像。客户端与服务端可以运行在同机器上,也可以通过socket或RESTful API进行通信。
服务端
在后台运行,由多个后台进程组成。
- dockerd 为客户端提供RESTful API,响应客户端的请求,通过Engine模块分发管理客户端的任务。
- docker-proxy 是dockerd的子进程,需要对容器进行端口映射时,有docker-proxy完成网络映射。
- containerd 是dockerd子进程,提供gRPC接口,响应dockerd的请求。管理runC镜像和容器环境。
- containerd-shim 是containerd的子进程,为runC容器提供支持,作为容器内进程的根进程。
通过pstree,可看到进程结构。
注:为何dockerd 与containerd平级,这里没有研究。
[root@VCentOS8-181 ~]# pstree
systemd─┬─ModemManager───2*[{ModemManager}]
├─NetworkManager───2*[{NetworkManager}]
├─VGAuthService
├─accounts-daemon───2*[{accounts-daemon}]
├─atd
├─auditd─┬─sedispatch
│ └─2*[{auditd}]
├─avahi-daemon───avahi-daemon
├─boltd───2*[{boltd}]
├─chronyd
├─colord───2*[{colord}]
├─containerd─┬─2*[containerd-shim─┬─java───40*[{java}]]
│ │ └─9*[{containerd-shim}]]
│ ├─containerd-shim─┬─java───40*[{java}]
│ │ └─10*[{containerd-shim}]
│ ├─containerd-shim─┬─bash
│ │ ├─java───40*[{java}]
│ │ └─9*[{containerd-shim}]
│ ├─containerd-shim─┬─portainer───14*[{portainer}]
│ │ └─9*[{containerd-shim}]
│ ├─containerd-shim─┬─nginx───8*[nginx]
│ │ └─9*[{containerd-shim}]
│ ├─containerd-shim─┬─redis-server───4*[{redis-server}]
│ │ └─10*[{containerd-shim}]
│ ├─containerd-shim─┬─redis-server───4*[{redis-server}]
│ │ └─9*[{containerd-shim}]
│ ├─4*[containerd-shim─┬─redis-server───6*[{redis-server}]]
│ │ └─9*[{containerd-shim}]]
│ └─32*[{containerd}]
├─crond
├─cupsd
├─dbus-daemon───{dbus-daemon}
├─dnsmasq───dnsmasq
├─dockerd─┬─9*[docker-proxy───7*[{docker-proxy}]]
│ ├─5*[docker-proxy───6*[{docker-proxy}]]
│ ├─docker-proxy───5*[{docker-proxy}]
│ └─21*[{dockerd}]
dockerd 默认监听本地的 unix:///var/run/docker.sock,只允许本地访问。样例中也可以看到docker-proxy对特定端口进行了监听。
[root@VCentOS8-181 ~]# netstat -nlp|grep docker
tcp6 0 0 :::16375 :::* LISTEN 16738/docker-proxy
tcp6 0 0 :::16376 :::* LISTEN 16887/docker-proxy
tcp6 0 0 :::4000 :::* LISTEN 13774/docker-proxy
tcp6 0 0 :::8000 :::* LISTEN 13222/docker-proxy
tcp6 0 0 :::6371 :::* LISTEN 16227/docker-proxy
tcp6 0 0 :::6372 :::* LISTEN 18148/docker-proxy
tcp6 0 0 :::6373 :::* LISTEN 16491/docker-proxy
tcp6 0 0 :::6374 :::* LISTEN 16620/docker-proxy
tcp6 0 0 :::6375 :::* LISTEN 16751/docker-proxy
tcp6 0 0 :::6376 :::* LISTEN 16900/docker-proxy
tcp6 0 0 :::9000 :::* LISTEN 13208/docker-proxy
tcp6 0 0 :::16371 :::* LISTEN 16214/docker-proxy
tcp6 0 0 :::16372 :::* LISTEN 18135/docker-proxy
tcp6 0 0 :::16373 :::* LISTEN 16477/docker-proxy
tcp6 0 0 :::16374 :::* LISTEN 16608/docker-proxy
unix 2 [ ACC ] STREAM LISTENING 59332 1/systemd /var/run/docker.sock
unix 2 [ ACC ] STREAM LISTENING 59864 8765/dockerd /var/run/docker/metrics.sock
unix 2 [ ACC ] STREAM LISTENING 62200 8765/dockerd /var/run/docker/libnetwork/ea85f98110c7.sock
可通过-H 修改dockerd 监听方式。如 dockerd -H 127.0.0.1:1234
[root@oracletest ~]# dockerd -H 192.168.130.133:1234
WARN[0000] [!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]
INFO[0000] libcontainerd: new containerd process, pid: 10833
WARN[0000] containerd: low RLIMIT_NOFILE changing to max current=1024 max=4096
INFO[0001] Graph migration to content-addressability took 0.00 seconds
INFO[0001] Loading containers: start.
INFO[0001] Firewalld running: true
INFO[0001] Default bridge (docker0) is assigned with an IP address 172.17.0.0/16. Daemon option --bip can be used to set a preferred IP address
INFO[0001] Loading containers: done.
WARN[0001] failed to retrieve docker-runc version: exec: "docker-runc": executable file not found in $PATH
WARN[0001] failed to retrieve docker-init version
INFO[0001] Daemon has completed initialization
INFO[0001] Docker daemon commit="0be3e21/1.13.1" graphdriver=overlay2 version=1.13.1
INFO[0001] API listen on 192.168.130.133:1234
INFO[0017] Firewalld running: false
查看监听
[root@oracletest ~]# netstat -nlp|grep docker
tcp 0 0 192.168.130.133:1234 0.0.0.0:* LISTEN 10825/dockerd-curre
unix 2 [ ACC ] STREAM LISTENING 6033699 10825/dockerd-curre /run/docker/libnetwork/af9e2c7fda086bce70047f2aee3bd882b5da10ab96b59408f3eeb6f124e5e2bd.sock
unix 2 [ ACC ] STREAM LISTENING 6034837 10833/docker-contai /var/run/docker/libcontainerd/docker-containerd.sock
可以看到,监听变为了网络监听,而不是默认的unix sock。
客户端
Docker客户端为用户提供一些列命令,使用这命令与服务端(dockerd)交互。
客户端发送命令,当接收到服务端的返回后,客户端将退出。
客户端模式使用unix:///var/run/docker.sock向服务端发送命令。如果服务端没有使用默认方式监听,客户端需要显式的指定服务端地址。如
[root@oracletest ~]# docker -H tcp://192.168.130.133:1234 info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 1.13.1
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host macvlan null overlay
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: (expected: aa8187dbd3b7ad67d8e5e3a15115d3eef43a7ed1)
runc version: N/A (expected: 9df8b306d01f59d3a8029be411de015b7304dd8f)
init version: N/A (expected: 949e6facb77383876aeff8a6944dde66b3089574)
Security Options:
seccomp
Profile: default
Kernel Version: 3.10.0-1127.19.1.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
Number of Docker Hooks: 3
CPUs: 4
Total Memory: 15.49 GiB
Name: oracletest
ID: RAZ6:NQ7W:HKFM:ZQRP:CF4B:MAD4:3CXW:RBTX:FYRW:TOLY:QTT3:PZHE
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Experimental: false
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false
Registries: docker.io (secure)
此方式也可实现跨主机访问docker信息,下边的样例是在主机VCentOS8-181上,使用客户端访问到主机oracletest上的dockerd。
[root@VCentOS8-181 ~]# docker -H tcp://192.168.130.133:1234 info
Client:
Debug Mode: false
Server:
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 1.13.1
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host macvlan null overlay
Log:
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: (expected: aa8187dbd3b7ad67d8e5e3a15115d3eef43a7ed1)
runc version: N/A (expected: 9df8b306d01f59d3a8029be411de015b7304dd8f)
init version: N/A (expected: 949e6facb77383876aeff8a6944dde66b3089574)
Security Options:
seccomp
Profile: default
Kernel Version: 3.10.0-1127.19.1.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
CPUs: 4
Total Memory: 15.49GiB
Name: oracletest
ID: RAZ6:NQ7W:HKFM:ZQRP:CF4B:MAD4:3CXW:RBTX:FYRW:TOLY:QTT3:PZHE
Docker Root Dir: /var/lib/docker
Debug Mode: false
Username: yeqiyu
Registry: https://index.docker.io/v1/
Experimental: false
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false
镜像仓库
镜像是容器的基础,Docker使用镜像仓库管理镜像。
- 支持不同后端存储
- 存放镜像文件
- 支持RESTful API
- 接收dockerd的命令
命名空间
命名空间(namespace)是Linux内核的强大特性,为容器化实现带来便利。操作系统中,内核、文件系统、网络、进程号(PID)、用户号(UID)、进程间通信(IPC)是应用程序进程共享的。除了实现资源限制,也需要进行资源隔离。
Docker容器启动时,通过调用setNamespaces方法完成命名空间配置。
1.进程命名空间
Linux通过进程命名空间管理进程号,同一进程在不同命名空间中,看到的进程号不相同。
启动容器时,docker-containerd会作为父进程,为每个容器启动一个docker-containered-shim进程,docker-containerd-shim进程将会作为容器内的根进程。
2.IPC命名空间
容器内进程交互采用了Linux常见的进程间交互方式(Interprocess Communication ,IPC),包括信号量、消息队列,共享内存等方式。
PID与IPC命名空间可以组合使用,同命名空间内彼此可见,允许交互;不同命名空间的进程隔离。
3.网络命名空间
网络命名空间为进程提供了完全独立的网络协议栈,包括网络设备接口、IPv4和IPv6协议栈、IP路由表、防火墙规则、socket等,使每个容器网络隔离开来。
Docker采用虚拟网络设备(Virtual Network Device,VND)方式,将不同命名空间网络设备连接到一起。Docker在宿主机创建多个网桥,容器中的虚拟网卡通过网桥进行连接。
容器发起的网络流量通过宿主机的iptables规则进行转发。
4.挂载命名空间
类似于chroot,挂载命名空间可将进程的根文件系统限制到一个特定的目录下。
5.UTS命名空间
UTS(UNIX Time-sharing System)允许每人各容器拥有独立的主机名和域名,可虚拟出一个有独立主机名和网络空间的环境。
如果没哟手动指定主机名称,Docker容器会使用容器ID的前六位。
在创建容器时,使用--hostname 指定容器主机名
如docker run --hostname tomcat01 tomcat
6.用户命名空间
每个容器有不通过的用户和组ID,可以在容器内使用容器内部运行程序,而非主机的用户。
每个容器内可拥有root帐号,但与主机隔离。可提高安全性。
控制组
控制组(CGroups)是Linux内核的一个特性,最早有Google的程序员在2006年提出,Linux内核自2.6.24开始源生支持,可提供对容器内存、CPU、磁盘IO等资源进行限制和计费管理。
最初设计目的是为了不同的应用情况提供统一的接口,从控制单一进程到系统级虚拟化。
可提供如下功能
资源限制(resource limiting)
可设置内存限制,当超出限额时,发出OOM警告
优先级(prioritization)
通过优先级得到更多的CPU资源
资源审计(accounting)
统计系统实际把多少资源用到适合的目的上,可使用cpuacct子系统记录某进程组的cpu使用时间。
隔离(ioslation)
为组隔离命名空间。
控制(control)
执行挂起、恢复、重启能操作
Docker容器启动时,通过调用setCapabilities方法完成配置。
安装完Docker后,可在/sys/fs/cgroup/memory/docker/目录看到各种限制项。
可能与书中原文环境不同,我是CentOS8内核4.18.0的环境,/sys/fs/cgroup/ 目录中,包含了各个需要被限制的资源,会在这些资源的目录中分别创建docker目录,从而进行限制。
记得在Linux kernel的某个版本之前,是通过子在进程pid目录下创建cgroup一众目录实现控制组。
[root@VCentOS8-181 cgroup]# pwd
/sys/fs/cgroup
[root@VCentOS8-181 cgroup]# ll
总用量 0
dr-xr-xr-x. 7 root root 0 11月 18 03:20 blkio
lrwxrwxrwx. 1 root root 11 11月 18 03:20 cpu -> cpu,cpuacct
lrwxrwxrwx. 1 root root 11 11月 18 03:20 cpuacct -> cpu,cpuacct
dr-xr-xr-x. 7 root root 0 11月 18 03:20 cpu,cpuacct
dr-xr-xr-x. 3 root root 0 11月 18 03:20 cpuset
dr-xr-xr-x. 7 root root 0 11月 18 03:20 devices
dr-xr-xr-x. 3 root root 0 11月 18 03:20 freezer
dr-xr-xr-x. 3 root root 0 11月 18 03:20 hugetlb
dr-xr-xr-x. 7 root root 0 11月 18 03:20 memory
lrwxrwxrwx. 1 root root 16 11月 18 03:20 net_cls -> net_cls,net_prio
dr-xr-xr-x. 3 root root 0 11月 18 03:20 net_cls,net_prio
lrwxrwxrwx. 1 root root 16 11月 18 03:20 net_prio -> net_cls,net_prio
dr-xr-xr-x. 3 root root 0 11月 18 03:20 perf_event
dr-xr-xr-x. 7 root root 0 11月 18 03:20 pids
dr-xr-xr-x. 2 root root 0 11月 18 03:20 rdma
dr-xr-xr-x. 7 root root 0 11月 18 03:20 systemd
查看当前主机容器
[root@VCentOS8-181 ebf70971fc3807d5b6d26eaf386cda15295c092b493c6427d5d17a5eada04586]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
223449356df1 redis:latest "docker-entrypoint.s…" 8 days ago Up 8 days 0.0.0.0:6376->6379/tcp, 0.0.0.0:16376->16379/tcp redis-6
59ca97fd6842 redis:latest "docker-entrypoint.s…" 8 days ago Up 8 days 0.0.0.0:6375->6379/tcp, 0.0.0.0:16375->16379/tcp redis-5
4b96a040e208 redis:latest "docker-entrypoint.s…" 8 days ago Up 8 days 0.0.0.0:6374->6379/tcp, 0.0.0.0:16374->16379/tcp redis-4
c287bfecf9a0 redis:latest "docker-entrypoint.s…" 8 days ago Up 8 days 0.0.0.0:6373->6379/tcp, 0.0.0.0:16373->16379/tcp redis-3
87ea5276b8fb redis:latest "docker-entrypoint.s…" 8 days ago Up 8 days 0.0.0.0:6372->6379/tcp, 0.0.0.0:16372->16379/tcp redis-2
ad66d0928bcf redis:latest "docker-entrypoint.s…" 8 days ago Up 8 days 0.0.0.0:6371->6379/tcp, 0.0.0.0:16371->16379/tcp redis-1
ce5c8a5e165a ccr.ccs.tencentyun.com/dockerpracticesig/docker_practice:vuepress "/docker-entrypoint.…" 8 days ago Up 8 days 0.0.0.0:4000->80/tcp boring_zhukovsky
86e393613dcd portainer/portainer-ce "/portainer" 8 days ago Up 8 days 0.0.0.0:8000->8000/tcp, 0.0.0.0:9000->9000/tcp portainer
95806cb49b15 tomcat "catalina.sh run" 8 days ago Up 8 days 8080/tcp tomcat-02
ebf70971fc38 tomcat "catalina.sh run" 8 days ago Up 8 days 8080/tcp tomcat-01
47f5bc43ebd5 tomcat "catalina.sh run" 8 days ago Up 8 days 8080/tcp tomcat-net-02
ba3ac1ef16dd tomcat "catalina.sh run" 8 days ago Up 8 days 8080/tcp tomcat-net-01
我们进入到其中一个容器的cgroup目录内
[root@VCentOS8-181 ebf70971fc3807d5b6d26eaf386cda15295c092b493c6427d5d17a5eada04586]# pwd
/sys/fs/cgroup/memory/docker/ebf70971fc3807d5b6d26eaf386cda15295c092b493c6427d5d17a5eada04586
可以看到可以对容器在内存方面进行如下控制
[root@VCentOS8-181 ebf70971fc3807d5b6d26eaf386cda15295c092b493c6427d5d17a5eada04586]# ls
cgroup.clone_children memory.kmem.failcnt memory.kmem.tcp.limit_in_bytes memory.max_usage_in_bytes memory.move_charge_at_immigrate memory.stat tasks
cgroup.event_control memory.kmem.limit_in_bytes memory.kmem.tcp.max_usage_in_bytes memory.memsw.failcnt memory.numa_stat memory.swappiness
cgroup.procs memory.kmem.max_usage_in_bytes memory.kmem.tcp.usage_in_bytes memory.memsw.limit_in_bytes memory.oom_control memory.usage_in_bytes
memory.failcnt memory.kmem.slabinfo memory.kmem.usage_in_bytes memory.memsw.max_usage_in_bytes memory.pressure_level memory.use_hierarchy
memory.force_empty memory.kmem.tcp.failcnt memory.limit_in_bytes memory.memsw.usage_in_bytes memory.soft_limit_in_bytes notify_on_release
查看其中oom的控制
[root@VCentOS8-181 ebf70971fc3807d5b6d26eaf386cda15295c092b493c6427d5d17a5eada04586]# cat memory.oom_control
oom_kill_disable 0
under_oom 0
oom_kill 0
联合文件系统
联合文件系统(UnionFS)是一种轻量级的高性能分层文件系统,支持将文件系统中的修改信息作为一次提交,并层层叠加。同时可以将不同目录挂载到同一个虚拟文件系统。是Docker镜像的基础技术。
1.Docker存储原理
Docker通过插件化方式支持多种文件系统后端。Debian/Ubuntu上的AUFS就是一种联合文件系统实现。AUFS支持为每个成员目录设定只读、读写或写出权限,同时AUFS有一个分层的概念,对只读权限的分支可以逻辑上进行增量修改。
Docker镜像是由多个文件层组成,每层有基于内容的唯一编号(层ID)。可通过docker history查看。
对于镜像来说,层内容是不可修改、只读的。当利用镜像启动一个容器后,将在镜像的顶层再挂载一个可读写的层给容器。当容器需要修改中间某层时,需要先复制到最上层。当数据对象较大时,需要较大的IO。因此IO型应用,推荐使用卷挂载方式。
2.Docker存储结构
所有镜像和容器都存储在Docker目录下,默认路径为 /var/lib/docker/。
这个目录下包含了容器的各种信息。
[root@VCentOS8-181 docker]# pwd
/var/lib/docker
[root@VCentOS8-181 docker]# ll
总用量 20
drwx------. 2 root root 24 11月 18 03:29 builder
drwx--x--x. 4 root root 92 11月 18 03:29 buildkit
drwx------. 22 root root 4096 11月 25 05:07 containers
drwx------. 3 root root 22 11月 18 03:29 image
drwxr-x---. 3 root root 19 11月 18 03:29 network
drwx------. 96 root root 8192 11月 25 05:07 overlay2
drwx------. 4 root root 32 11月 18 03:29 plugins
drwx------. 2 root root 6 11月 18 03:32 runtimes
drwx------. 2 root root 6 11月 18 03:29 swarm
drwx------. 2 root root 6 11月 25 05:00 tmp
drwx------. 2 root root 6 11月 18 03:29 trust
drwx------. 8 root root 4096 11月 25 04:22 volumes
其中容器或镜像的三个重要目录。
- layers 层属性文件
保存镜像各个层元数据,某镜像层下边包括哪些层。
- diff 层内容子目录
保存所有镜像的内容数据
- mnt 个容器最终挂载点
3.多种文件系统比较
Docker支持多种文件系统,包括:
AUFS
最早支持的文件系统,对Debain/Ubuntu支持好,没有合并到Linux内核中。
成熟度高
btrfs
参考zfs特性的文件系统,由Linux社区开发,视图取代Device Mapper。
成熟度一般
Device Mapper
RedHat与Docker一起开发用于支持RHEL的文件系统,内核支持,性能略慢。
成熟度高。
overlay
类似于AUFS的文件系统,性能更好。
overlay2
原生支持128层,效率比overlay更好。内核在4.0以上版本支持。
vfs
基于普通文件系统的中间层抽象,性能差,空间占用高,成熟度一般。
zfs
Solaris 10的写文件系统,不够成熟。
网络虚拟化
Docker本地网络实现,是利用了Linux网络命名空间和虚拟网络设备(veth pair)。
1.基本原理
Docker中的网络都是虚拟接口,最大优势是转发效率极高。
这是因为Linux通过在内核中进行数据复制来实现虚拟接口之间的数据转发,即发送接口的发送缓存中的数据包奖杯直接复制到接收接口的接收缓存中,无须通过外部物理网络设备交换。对于本地系统与容器内系统来说,虚拟接口与物理接口无区别,只是速度要快的多。
在创建容器时,分别在容器内和主机创建虚拟接口veth并联通。
2.网络创建过程
1.创建一对儿虚拟接口,分别放在本地主机和容器的命名空间中。
2.主机端虚拟接口连接到默认的docker0网桥,并具有一个与veth开头的名称。
3.容器端的虚拟接口将放到新的容器中,并修改名字为eth0。只在容器内可见。
4.从网桥可用地址段中获取一个空闲地址分配给容器的eth0(如172.17.0.2/16),配置默认路由网关为docker0网卡的内部接口docker0的IP地址(如172.17.0.1/16)。
也可通过docker run 启动时指定容器网络配置。
--net-bridge
--net=none 将容器隔离在网络栈中,但不进行网络配置。
--net=container:NAME/ID 让新建容器的进程放到一个已经存在的容器的网络栈中。新容器进程有自己的文件系统、进程、资源限制,但会和已存在的容器共享IP地址和端口等网络资源。
--net=host 不需要容器化容器内的网络,此时容器使用本地主机的网络,将拥有完全的本地接口访问权限。
--net=user_defined_network 用户自行创建的网络,使容器连接到该网络。
3.手动配置网络
使用--net=none后,容器不对网络进行配置。
需要手工将容器、主机的接口连接到网桥。
暂时未对此配置进行实验。