docker 核心

1. 基本架构

    Docker 包括客户端,服务端两大组件

  1.1 服务端

    Docker 服务端一般在宿主主机后台运行,dockerd 作为服务端接受来自客户的请求,并 通过 containerd 具体处理与容器相关的请求,包括创建,运行,删除容器等 服务端主要包 括四个组件:

dockerd: 客户端提REST API , 响应来自客户端的请求,采用模块化的架构,通过专门的Engine模块来分发 理各个来自客户端的任务 可以单独升级;

docker-proxy:是dockerd 的子进程,当需要进行容器端口映射时,docker-proxy 完成网络映射配置

containerd:是dockerd 的子进程,gRPC 接口响应来自 dockerd 的请求,对下管理 runC 镜像和容器环境可以单独升级;

containerd-shim:是 containerd 的子进程,为runC 容器提供支持,同时作为容器内进程的根进程

    只允许本地的root用户或docker用户组成员访问。可以通过-H选项来修改监听方式

dockerd -H 127.0.0.1:1234

    Docker还支持通过TLS认证方式来验证

    docker-proxy只有当启动容器并且使用端口映射时候才会执行,

docker run -itd -p 80:80 ubuntu:latest /bin/bash

1.2 客户端

    用户使用的 Docker 可执行命令即为客户端程序。与Docker 服务端保持运行方式不同, 客户端发送命令后,等待服务端返回;一旦收到返回后,客户端立刻执行结束并退出。用户 执行新的命令,需要再次调用客户端命令

docker -H tcp://127.0.0.1:1234 info

2. 命名空间

    命名空间(namespace)是Linux内核的一个强大特性,每个容器都可以拥有自己单独的命名空间,运行在其中的应用都像是在独立的操作系统环境中一样。

    在操作系统中,包括内核,文件系统、网络、进程号( Process ID, PID )、用户号( User ID, UID 进程间通信( InterProcess Communication, IPC )等资源,所有的资源都是应用进程直接共享的。

2.1 进程命名空间

    Linux 通过进程命名空间管理进程号,对于同一进程(同一个task struct ),在不同的命名空间中,看到的进程号不相同。 每个进程命名空间有一套自己的进程号管理方法 进程命 名空间是一个父子关系的结构,子空间中的进程对于父 间是可见的

root@9c991b075087:/# ps -ef |grep docker 
root        36    26  0 08:25 pts/1    00:00:00 grep --color=auto docker

    新建一个ubuntu容器,执行sleep名。此时,docker-containerd进程作为父进程,会为每个容器启动一个docker-containerd-shim进程,作为该容器内所有进程的根进程 

docker run --name test -d ubuntu sleep 9999

从宿主机上查看新建容器的进程的父进程

ps -ef|grep sleep

2.2 IPC命名空间

    容器中的进程交互采用Linux常见的进程交互方法(Interprocess Communication,IPC)包括信号量,信息队列和共享内存等方式。PID命名空间和IPC命名空间可以组合起来一起使用,同个IPC命名空间内的进程可以彼此可见,允许交互;不同空间的进程则无法交互

2.3 网络命名空间

    有了进程命名空间后,不同命名空间中的进程号可以相互隔离,但是网络端口端口还是共享本地系统的端口。

    通过网络命名空间,可以实现网络隔离。一个网络命名空间为进程提供了一个完全独立的网络协议栈的视图。包括网络设备接口,IPV4和IPV6协议栈,IP路由表,防火墙规则,sockets等,这样每个容器的网络就能隔离开来。

    Docker 采用虚拟网络社保(Virtual Network Device, VND)的方式,讲不同命名空间的网络设备连接到一起。默认情况下,Docker在宿主机上创建过个虚拟机网桥(默认网桥docker0)容器中的虚拟网卡通过网桥进行连接

     使用docker network ls命令可以查看当前系统网桥

docker network ls

    使用brctl工具(需安装bridge-utils工具包),还可以看到连接到网桥上的虚拟网口的信息。每个容器默认分配一个网桥的虚拟网口,并将docker0的IP地址设置为默认的网关,容器发起的网络流量通过宿主机的iptables规则进行转发

yum install bridge-utils
brctl show 

2.4 挂载命名空间

    类似于chroot,挂载(Mount,MNT)命名空间可以将一个进程的根文件系统限制到一个特定的目录下。

    瓜子啊命名空间允许不同命名空间的进程看到的本地文件位于宿主机中不同路径下,每个命名空间的进程所看到的文件目录彼此是隔离的。例如,不同命名空间中的进程,都文卫自己独占了一个完成的跟文件系统(rootfs),但实际上,不同命名空间中的文件彼此隔离,不会造成相互影响 ,同时也无法影响宿主机文件系统中的 他路径。

2.5 UTS命名空间

    UTS (UNIX Time-sharing System )命名空间允许每个容器拥有独立的主机名和域名,从而可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样。

    如果没有于动指定主机名称,Docker容器的主机名就是返回的容器 ID 的前6字节前缀,否则为指定的用户名:     

[root@kubernetes ~]# docker run --name test1 -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1;done"
f3bd04e73b4c7cd49101a7f1cba2bd94ccf9996bab391cf9ce1e696ec339334f

[root@kubernetes ~]# docker  inspect -f {{".Config.Hostname"}} test1 
f3bd04e73b4c
[root@kubernetes ~]# docker run --hostname test2 --name test2 -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1;done"
a82b4b257e50b2423d2b4f46165ca435b508c9f4efeca3e68816a23d0b6da2b5

[root@kubernetes ~]# docker inspect -f {{".Config.Hostname"}} test2
test2

2.6 用户命名空间

    每个容器可以有不同的用户和组 id 也就是说,可以在容器内使用特定的内部用户执行 程序,而非本地系统上存在的用户

    每个容器内部都可以有最高权限的 root 帐号,但跟宿主主机不在一个命名空间。 通过使用隔离的用户命名空间,可以提高安全 性,避 容器内的进程获取 外的权限;同时通过使用不同用户也可以进一步在容器内控制权限

    在容器内创建了 test 用户,只有普通权限,无法访问更高权限的资源:

[root@kubernetes ~]# docker run --rm -it ubuntu bash
root@2dc7d7a868d0:/# cat /proc/1/e
environ  exe      
root@2dc7d7a868d0:/# cat /proc/1/e
environ  exe      
root@2dc7d7a868d0:/# cat /proc/1/environ 
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=2dc7d7a868d0TERM=xtermHOME=/root
root@2dc7d7a868d0:/# useradd -ms /bin/bash test 
root@2dc7d7a868d0:/# su test 
test@2dc7d7a868d0:/$ cat /proc/1
1/  17/ 18/ 
test@2dc7d7a868d0:/$ cat /proc/1
1/  17/ 18/ 
test@2dc7d7a868d0:/$ cat /proc/1/e
environ  exe      
test@2dc7d7a868d0:/$ cat /proc/1/e
environ  exe      
test@2dc7d7a868d0:/$ cat /proc/1/environ 
cat: /proc/1/environ: Permission denied

3. 控制组

    控制组(CGroups)是 Linux 内核的一个特性,主要用来对共享资源进行隔离、限制、审 计等 只有将分配到容器的资源进行控制,才能避免多个容器同时运行时对宿主机系统的资 源竞争。每个控制组是一组对资源的限制,支持层级化结构

资源限制( resource limiting ):可将组设置一定的内存限制 比如:内存子系统可以为进程组设定一个内存使用上限,一旦进程组使用的内存达到限额再申请内存,就会出发 Out of Memory 警告。

优先级(prioritization ):通过优先级让一些组优先得到更多的 CPU 等资源

资源审计( accounting ):用来统计系统实际上把多少资源用到适合的目的上,可以使用cpuacct 子系统记录某个进程组使用的 CPU 时间

隔离( isolation ):为组隔离命名空间,这样使得一个组不会看到另一个组的进程网络连接和文件系统。

控制(control ):执行挂起 恢复和重启动等操作

可在/sys/fs/cgroup/memory/docker/目录下看到对Docker组应用的各种限制项,包括全局限制和位于子目录中对某个容器单独限制。

ls /sys/fs/cgroup/memory/docker/

用户可以通过修改这些稳健值来控制组,从而限制Docker应用资源。如:限制docker组中的所有进程使用的物理的物理内存总量不超过100MB

echo 104857600 > /sys/fs/cgroup/memory/docker/memory.limit_in_bytes

进容器文件夹,查看对应容器的限制和使用状态

cd /sys/fs/cgroup/memory/docker/容器ID
cat memory.stat

4. 联合文件系统

     联合文件系统(UnionFS )是一种轻量级的高性能分层文件系统,它支持将文件系统中的 修改信息作为一次提交,并层层叠加,同时可以将不同目录挂载到同一个虚拟文件系统下, 应用看到的是挂载的最终结果 联合文件系统是实现 Docker 镜像的技术基础

     Docker 镜像可以通过分层来进行继承 例如,用户基于基 镜像(用来生成其他镜像 础,往往没有父镜像)来制作各种不同的应用镜像 这些镜像共享同一个基础镜像层,提 高了存储效率 此外, 用户改变了一 Docker 镜像(比如升级程序到新的版本),则 建一个新的层( layer 因此,用户不用替换整个原镜像或者重新建立,只需要添加新层即 用户分发镜像的时候,也只需要分发被改动的新层内容(增量部分) 这让 Docker 像管理变得十分轻量和快速

4.1 docker存储原理

    Docker 目前通过插件化方式支持多种文件系统后端 Debian/Ubuntu 上成熟的 (Another Union File System ,或 v2 版本往后的 Advanced multi lay red Unification File System), 就是一种联合文件系统实现 支持为每一个成员目录(类 Git 的分支)设定只读 ( readonly 、读写( readwrite )或写出( whiteout-able )权限,同时 里有 似分 概念,对只读权限的分支可以逻辑上进行增量地修改(不影响只读部分的)

    Docker 镜像自身就是由多个文件层组成,每一层有基于 容的唯一的编号(层 ID 以通过 docker history 查看一个镜像由哪些层组成 

[root@kubernetes ~]# docker history ubuntu:latest
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
4e5021d210f6        9 days ago          /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>           9 days ago          /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B                  
<missing>           9 days ago          /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B                
<missing>           9 days ago          /bin/sh -c [ -z "$(apt-get indextargets)" ]     987kB               
<missing>           9 days ago          /bin/sh -c #(nop) ADD file:594fa35cf803361e6…   63.2MB  

    对于 Docker 镜像来说,这些层的内容都是不可修改的 、只读的。 而当 Docker 利用镜像 启动一个容器时,将在镜像文件系统的最顶端再挂载一个新的可读写的层给容器 容器中的 内容更新将会发生在可读写层 当所操作对象位于较深的某层时,需要先复制到最上层的可 读写层 当数据对象较大时,往往意味着较差 IO 性能 因此,对于 IO 敏感型应用,一般 推荐将容器修改的数据通过 volume 方式挂载,而不是直接修改镜像内数据

    对于频繁启停 Docker 容器的场景下,文件系统的 IO 性能也将十分关键

4.2 docker 存储结构

    所有的镜像和容器都存储都在 Docker 指定的存储目录下,,默 认路径是/ ar lib docker 在这个目录下面,存储由 Docker 镜像和容器运行相关的文件和 目录,可能包括 build er containerd containers image network ufs /ov lay2 plugins run times swarm tmp trust volumes 等。

    其中,如果使用 AUFS 存储后端,则最关键的就是 aufs 目录,保存 Docker 镜像和容器 相关数据和信息 包括 layers,diff 和mnt 三个子目录

    layers 子目录包含层属性文件,用来保存各个镜像层的元数据:某镜像的某层下面包括 哪些层

    mnt 子目录下面的子目录是各个容器最终的挂载点,所有相关的 AUFS 层在这里挂载到 一起,形成最终效果 一个运行中容器的根文件系统就挂载在这下面的子目录上

4.3 多种文件系统比较

    Docker 目前支持的联合文件系统种类包括 AUFS 、怕也、 Device Mapp町、 overlay over lay2 vfs zfs 等,

AUFS :最早支持的文件系统,对 Debian/Ubuntu 支持好,虽然没有合并到 Linux内核中,但成熟度很高;

btrfs 参考 zfs 等特性设计的文件系统,由 Linux 社区开发,试图未来取代 DeviceMapper ,成熟度有待提高;

Device Mapper : RedHat 公司和 Docker 团队一起开发用于支持阳fEL 的文件系统,内核支持,性能 咯慢,成熟度高;

overlay :类似于 AUFS 的层次化文件系统,性能更好,从 Linux 3.18 开始已经合并到内核,但成熟度有待提高;

overlay 2 : Docker 1.12 后推出,原生支持 128 层,效率比 OverlayPS 高,较新版本的Docker 支持,要求内核大于 4.0;

vfs 基于普通文件系统( ext nfs 等)的中间层抽象,性能差,比较占用空间,成熟度也一般

zfs :最初设计为 Solaris 10 上的写时文件系统,拥有不少好的特性,但对 Linux 支持还不够成熟

5. LIux网络虚拟化

    Docker 的本地网络实现其实就是利用了 Linux 上的网络命名空间和虚拟网络设备(特别是 veth pair)

    5.1 基本原理

    直观上看,要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)与外 界相通,并可以收发数据包;此外,如果不同子网之间要进行通信,还需要额外的路由机制

    Docker 中的网络接口默认都是虚拟接口 虚拟接口的最大优势就是转发效率极高 这是 因为 Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发,即发送接口的发送 缓存中的数据包将被直接复制到接收接口的接收缓存中,而无须通过外部物理网络设备进行 交换 对于本地系统和容器内系统来看,虚拟接口跟一个正常的以太网卡相比并无区别,只是它的速度要快得多

    Docker 容器网络就很好地利用了 Linux 拟网络技术,它在本地主机和容器内分别创建 一个虚拟接口 veth 并连通(这样的一对虚拟 接口 叫做 veth pair )

    5.2 网络创建过程

    一般情况下, Docker 建一个容器的时候, 会具体执行如下操作

    1 建一对虚拟接口,分别放到本地主 机和新容器的命名空间中;

    2. 本地主机一气端 的虚拟接口连接到默认 dockerO 网桥或指定网桥上,并具有一个以 veth 开头的唯一名字,如 veth1234;

    3 )器一端的虚拟接口将放到新创建的容器中,并修改名字作为 ethO 这个接口只在容 器的命名空间可见;

   4 )从网桥可用地址段中获取 个空闲地址分配给容器的 ethO (例如 172.17.0.2/16 ),并 配置默认路由网关为 dockerO 网卡的内部接口 dockerO IP 地址(例如 172 17.42.1/16)

    在使用 docker [container] run 命令启动容器的时候,可以通过一口et 参数来指 定容器的网络配置,有5个可选值 bridge ,none,container host 和用户定义的网络

net=bridge: 默认值,在 Docker 网桥 docker0 上为容器创建新的网络栈;

net=none:让 Docker 将新容器放到隔离的网络栈中,但是不进行网络配置。之后,用户可以自行配置

net=container:NAME_or_ID :让 Docker 将新建容器的进程放到一个已存在容器的网络校中,新容器进程有自己的文件系统、进程列表和资源限制,但会和已存在的容器共享 地址和端口等网络资源,两者进程可以直接通过 lo 环回接口通信;

net=host :告诉 Docker 不要将容器网络放到隔离的命名 间中,即不要容器化容器内的网络 此时容器使用本地主机的网络,它拥有完全的本地主机接口访问权容器进程跟主机其他 root 进程一样可以打开低范围的端口,可以访问本地网络服务(比如 D-bus ),还可以让容器做一些影响整个主机系统的事情, 比如重启主机
因此使用这个选项的时候要非常小心 如果进一步使用一privil eged=true数,容器甚至会被允许直接配置主机的网络技;

net=user_defined_network :用户自行用 network 相关命令创建一个网络,之后将容器连接到指定的己创建网络上去

5.3 手动配置网络

    使用--net=none 后, Docker 将不对容器网络进行配置

docker run -i -t --rm --net=none ubuntu /bin/bash

在本地主机查找容器的进程 id,并为它创建网络命名空间:

[root@kubernetes /var/lib/docker]# docker inspect -f '{{.State.Pid}}' 01a20a2350fd
26513
[root@kubernetes /var/lib/docker]# pid=26513
[root@kubernetes /var/lib/docker]# mkdir -p /var/run/netns

[root@kubernetes /var/lib/docker]# ln -s /proc/$pid/ns/net /var/run/netns/$pid

检查桥接网卡的IP和子网掩码信息

[root@kubernetes /var/lib/docker]# ip addr show docker0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:26:29:1d:89 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

创建一对"veth pair" 接口A和B,绑定A接口到网桥docker0 并启用它:

ip link addr A type veth peer name B

brctl addif docker0 A

ip link set A up

将B接口放到容器的网络命名空间,命名为eth0,启动它并配置一个可用IP(桥接网段)和默认网关;

ip link set B netns $pid

ip netns exec $pid ip link set dev B name eth0

ip netns exec $pid ip link set eth0 up

ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0

ip netns exec $pid ip route add default via 172.17.42.1

    当容器终止后, Dock 会清空容器,容器内的网络接口会随网络命名空间 起被清除, A接口也被自动从 dockerO 卸载并清除, 此外,在删除 /var/run/netns 下的内容之前,用户可 以使用 ip netns exec 命令在指定网络命名空间中进行配置, 从而更新 器内的网络配置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章